361 lines
12 KiB
Markdown
361 lines
12 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
## System Overview
|
||
|
||
ScalesApp is a three-tier weighing scale management system with real-time data synchronization:
|
||
- **Django Backend** (REST API + Server-Sent Events) on port 8000
|
||
- **React Frontend** (SPA) on port 3000
|
||
- **Serial Bridge** (Flask SSE server) on port 5000 for COM port reading
|
||
|
||
## Development Commands
|
||
|
||
### Backend (Django)
|
||
|
||
```bash
|
||
cd backend
|
||
python -m venv venv
|
||
venv\Scripts\activate # Windows: venv\Scripts\activate | Unix: source venv/bin/activate
|
||
pip install -r requirements.txt
|
||
|
||
# Database operations
|
||
python manage.py makemigrations
|
||
python manage.py migrate
|
||
python manage.py createsuperuser
|
||
|
||
# Run server
|
||
python manage.py runserver # Development (WSGI)
|
||
uvicorn scalesapp.asgi:application --reload # Development (ASGI - for SSE)
|
||
|
||
# Testing (when implemented)
|
||
python manage.py test
|
||
python manage.py test api.tests.test_models # Single test file
|
||
```
|
||
|
||
### Frontend (React)
|
||
|
||
```bash
|
||
cd frontend
|
||
npm install
|
||
npm start # Development server
|
||
npm run build # Production build
|
||
npm test # Run tests
|
||
```
|
||
|
||
### Serial Bridge
|
||
|
||
```bash
|
||
cd serial_bridge
|
||
python -m venv venv
|
||
venv\Scripts\activate
|
||
pip install -r requirements.txt
|
||
python app.py # Run Flask SSE server
|
||
|
||
# Build standalone executable
|
||
pyinstaller serial_bridge.spec
|
||
```
|
||
|
||
### Testing COM Port (requires com0com)
|
||
|
||
```bash
|
||
cd test_comport_writer
|
||
python test_writer.py # Sends test data to virtual COM port
|
||
```
|
||
|
||
## Architecture
|
||
|
||
### Django Backend Structure
|
||
|
||
**Three Django Apps:**
|
||
- **`api/`** - Core API, user management, COM port readings, report templates
|
||
- **`vehicles/`** - Vehicle weighing management with dynamic nomenclature data
|
||
- **`nomenclatures/`** - Runtime-configurable schema system (lookup tables and custom fields)
|
||
|
||
**Custom User Model:** `api.User` (extends AbstractUser)
|
||
- Fields: `role` (employee/viewer), `is_admin` (boolean)
|
||
- All authentication requires JWT tokens
|
||
|
||
**Database:** PostgreSQL (production), SQLite (development fallback)
|
||
|
||
**Key Models:**
|
||
- `User` - Custom user with roles
|
||
- `ComPortReading` - Serial port data log
|
||
- `Report` - Report templates (JSONField stores element definitions)
|
||
- `Vehicle` - Core weighing entity (vehicle_number, tare, gross, with user/timestamp tracking)
|
||
- `VehicleExtra` - OneToOne with Vehicle, JSONField for dynamic nomenclature data
|
||
- `Nomenclature` - Configurable data types (applies_to: vehicle/container, kind: lookup/field)
|
||
- `NomenclatureField` - Field definitions (text, number, bool, choice types)
|
||
- `NomenclatureEntry` - Data entries for lookup tables
|
||
|
||
**ViewSet Pattern:** All views use DRF's ModelViewSet with custom actions via `@action` decorator
|
||
- Examples: `@action(detail=True, methods=['post']) def set_tare()`
|
||
|
||
### Real-Time Communication (SSE)
|
||
|
||
**Implementation:** `backend/scalesapp/sse.py`
|
||
- ASGI-based async SSE endpoint: `/sse-connect/`
|
||
- JWT authentication via query parameter or Authorization header
|
||
- Global `updates_queue` with 100-event buffer
|
||
- Event ID tracking for reconnection/replay of missed events
|
||
- `sse_broadcast_update(operation, model, data)` called from views after mutations
|
||
- Auto-reconnect with Last-Event-ID header support
|
||
|
||
**Usage in Views:**
|
||
```python
|
||
from scalesapp.sse import sse_broadcast_update
|
||
|
||
def create(self, request):
|
||
# ... create logic ...
|
||
sse_broadcast_update('insert', 'vehicle', serializer.data)
|
||
```
|
||
|
||
### Nomenclature System (Dynamic Schema)
|
||
|
||
**Purpose:** Runtime-configurable extra fields without database migrations
|
||
|
||
**Flow:**
|
||
1. Admin creates `Nomenclature` (e.g., "CARGO_TYPE", kind=lookup, applies_to=vehicle)
|
||
2. Defines `NomenclatureField`s (e.g., name: text, code: text)
|
||
3. Creates `NomenclatureEntry` records (e.g., "Coal", "Iron Ore")
|
||
4. Frontend loads nomenclatures on mount via REST API
|
||
5. User selects from dropdown → stores entry ID in `vehicle.extra.data.CARGO_TYPE` (JSONField)
|
||
6. Validation in `VehicleExtraSerializer` enforces type constraints
|
||
|
||
### React Frontend Structure
|
||
|
||
**Context Providers (State Management):**
|
||
- **`AuthContext`** - JWT token management, auto-refresh every 14 minutes, axios interceptor for 401 handling
|
||
- **`NomenclatureContext`** - Centralized data cache with SSE subscription to Django `/sse-connect/`
|
||
- Real-time updates (insert/update operations)
|
||
- Maintains sorted arrays for all nomenclatures
|
||
- Auto-reconnect on connection loss
|
||
- Parallel loading of all nomenclature endpoints on mount
|
||
- **`DataContext`** - Report editor data fetching (for preview mode)
|
||
- **`BandContext`** - Report editor band iteration state
|
||
|
||
**Key Components:**
|
||
- **`Main.jsx`** - Primary weighing interface
|
||
- Left panel: Vehicle list with search
|
||
- Right panel: Selected vehicle details with dynamic nomenclature fields
|
||
- Real-time weight display from serial bridge
|
||
- Tare/Gross weight setting with user/timestamp tracking
|
||
- SSE-based automatic UI updates
|
||
- **`ReportEditor/`** - Custom report builder (see Report Editor section)
|
||
|
||
**API Communication:** `services/api.js`
|
||
- Axios instance with JWT interceptor
|
||
- Token refresh queue (pauses failed requests during refresh)
|
||
- Auto-logout on refresh failure
|
||
|
||
**Routing:**
|
||
- `/` - Main weighing interface (protected)
|
||
- `/report-editor` - Report designer (protected)
|
||
- Shows Login component if not authenticated
|
||
|
||
### Serial Bridge Architecture
|
||
|
||
**Components:**
|
||
- `app.py` - Flask SSE server on port 5000
|
||
- `serial_reader.py` - SerialPortReader class with threading
|
||
- `tray_icon.py` - Windows system tray integration
|
||
|
||
**Data Flow:**
|
||
1. Thread reads from COM port continuously (pyserial)
|
||
2. Data queued in global `data_queue`
|
||
3. SSE endpoint `/events` streams to frontend
|
||
4. Frontend's `useSerialData` hook receives updates
|
||
5. **Note:** Serial bridge is independent of Django (direct SSE to frontend)
|
||
|
||
**Configuration:** `.env` file with COM_PORT, BAUD_RATE, TIMEOUT, READ_INTERVAL
|
||
|
||
### Real-Time Data Flow Example
|
||
|
||
```
|
||
COM Port → Serial Bridge (Flask SSE :5000) → Frontend useSerialData hook
|
||
↓
|
||
Main.jsx displays weight
|
||
↓
|
||
User clicks "Set Tare"
|
||
↓
|
||
POST /api/vehicles/{id}/set-tare/
|
||
↓
|
||
Django sse_broadcast_update()
|
||
↓
|
||
SSE → Frontend NomenclatureContext
|
||
↓
|
||
Vehicle list auto-updates
|
||
```
|
||
|
||
## Report Editor System
|
||
|
||
**Architecture:** Custom drag-drop report designer with grid-based layout
|
||
|
||
**Grid System:**
|
||
- Page dimensions: 80 columns × 66 rows (character grid)
|
||
- All elements positioned in grid coordinates
|
||
- Pixel conversion: `x * charWidth`, `y * charHeight`
|
||
|
||
**Element Types (OOP Class Hierarchy):**
|
||
```
|
||
Element (base class)
|
||
├── TextElement (static text)
|
||
├── DBTextElement (data-bound field with objectKey/fieldPath)
|
||
├── FrameElement (borders with box-drawing characters)
|
||
├── HorizontalLineElement
|
||
├── VerticalLineElement
|
||
├── SymbolElement (single character)
|
||
└── BandElement (repeating section with children[])
|
||
```
|
||
|
||
**Band Types:**
|
||
- `header` - Page header
|
||
- `footer` - Page footer
|
||
- `detail` - Main repeating data section
|
||
- `subdetail` - Nested repeating (child of detail)
|
||
- `summary` - Summary/totals section
|
||
|
||
**Key Components:**
|
||
- `ReportEditor.jsx` - Main orchestrator with save/load to Django `/api/reports/`
|
||
- `EditorCanvas.jsx` - Canvas rendering, drag/drop, multi-select
|
||
- `Toolbar.jsx` - Tool selection (text, frame, lines, bands, etc.)
|
||
- `ObjectInspector.jsx` - Property editor for selected elements
|
||
- `ConfigPanel.jsx` - API endpoint configuration
|
||
- `CharacterPalette.jsx` - Box-drawing character picker
|
||
- `models/Element.jsx` - Element class definitions
|
||
|
||
**Data Binding:**
|
||
- `DBTextElement` has `objectKey` and `fieldPath`
|
||
- Example: objectKey="invoice", fieldPath="customer.name"
|
||
- Preview mode fetches data from configured `apiEndpoint`
|
||
- BandContext provides current item during band iteration
|
||
|
||
**Persistence:**
|
||
- Entire report serialized to JSON
|
||
- Saved to Django `Report` model (JSONField stores elements array)
|
||
|
||
**Features:**
|
||
- Multi-select with drag/drop
|
||
- Resize handles
|
||
- Keyboard shortcuts (Delete, Ctrl+S, etc.)
|
||
- Grid snapping
|
||
- Character palette for box-drawing
|
||
- Preview mode with live data
|
||
|
||
## Authentication & Security
|
||
|
||
**JWT Token Flow:**
|
||
- Access token: 15 minutes lifetime
|
||
- Refresh token: 7 days lifetime
|
||
- Stored in localStorage (XSS risk - consider httpOnly cookies for production)
|
||
- Frontend auto-refreshes tokens every 14 minutes
|
||
- All API requests require Bearer token (except `/api/token/` and `/api/token/refresh/`)
|
||
- SSE connection authenticated via query param: `/sse-connect/?token=<jwt>` or Authorization header
|
||
|
||
**CORS Configuration:**
|
||
- Allowed origins: http://localhost:3000, http://127.0.0.1:3000, http://localhost:5174
|
||
- Credentials allowed (for cookies if needed)
|
||
|
||
**User Roles:**
|
||
- `employee` - Full access to weighing operations
|
||
- `viewer` - Read-only access
|
||
- `is_admin` - Access to Django admin panel
|
||
|
||
## Key Technical Decisions
|
||
|
||
**Why SSE instead of WebSockets?**
|
||
- Simpler server implementation (one-way communication sufficient)
|
||
- Auto-reconnect built into EventSource API
|
||
- Event ID-based replay for missed events
|
||
- HTTP-based (easier to proxy/firewall)
|
||
|
||
**Why ASGI?**
|
||
- Required for async SSE endpoint
|
||
- Uses uvicorn server
|
||
- `sync_to_async` wrapper for ORM queries in SSE handler
|
||
|
||
**Why Separate Serial Bridge?**
|
||
- Independence from Django (can restart Django without losing COM connection)
|
||
- System tray integration for user control
|
||
- Portable executable deployment
|
||
- Dedicated threading for serial I/O
|
||
|
||
**Why Nomenclature System?**
|
||
- Avoid schema migrations for custom fields
|
||
- Different deployments have different requirements
|
||
- User-configurable without code changes
|
||
- Validation still enforced via serializers
|
||
|
||
## Important Files for Understanding Architecture
|
||
|
||
1. `backend/scalesapp/settings.py` - Django configuration, JWT, CORS, database
|
||
2. `backend/scalesapp/sse.py` - Real-time SSE implementation
|
||
3. `backend/api/models.py` - Core models (User, ComPortReading, Report)
|
||
4. `backend/vehicles/models.py` - Vehicle and VehicleExtra with JSONField
|
||
5. `backend/nomenclatures/models.py` - Dynamic schema system
|
||
6. `frontend/src/App.jsx` - Routing and AuthProvider setup
|
||
7. `frontend/src/contexts/NomenclatureContext.jsx` - SSE client and data cache
|
||
8. `frontend/src/contexts/AuthContext.jsx` - JWT token management
|
||
9. `frontend/src/components/Main.jsx` - Primary weighing interface
|
||
10. `frontend/src/components/ReportEditor/ReportEditor.jsx` - Report builder
|
||
11. `serial_bridge/app.py` - Flask SSE server for COM port
|
||
|
||
## Configuration Files
|
||
|
||
**Backend:** `backend/.env`
|
||
```
|
||
SECRET_KEY=your-secret-key
|
||
DEBUG=True
|
||
ALLOWED_HOSTS=localhost,127.0.0.1
|
||
DB_NAME=scalesapp
|
||
DB_USER=postgres
|
||
DB_PASSWORD=
|
||
DB_HOST=localhost
|
||
DB_PORT=5432
|
||
```
|
||
|
||
**Frontend:** `frontend/.env`
|
||
```
|
||
REACT_APP_API_URL=http://localhost:8000
|
||
```
|
||
|
||
**Serial Bridge:** `serial_bridge/.env`
|
||
```
|
||
COM_PORT=COM1
|
||
BAUD_RATE=9600
|
||
BACKEND_URL=http://localhost:8000
|
||
AUTO_CONNECT=True
|
||
DEBUG=False
|
||
```
|
||
|
||
## Running the Full Application
|
||
|
||
1. Start Django: `cd backend && python manage.py runserver`
|
||
2. Start Serial Bridge: `cd serial_bridge && python app.py`
|
||
3. Start React: `cd frontend && npm start`
|
||
|
||
For SSE to work properly in Django, use uvicorn instead: `uvicorn scalesapp.asgi:application --reload`
|
||
|
||
## Common Patterns
|
||
|
||
**Adding a new model:**
|
||
1. Define model in appropriate app (api/vehicles/nomenclatures)
|
||
2. Create serializer in `serializers.py`
|
||
3. Create ViewSet in `views.py` with `sse_broadcast_update()` calls
|
||
4. Register route in `urls.py`
|
||
5. Run `python manage.py makemigrations && python manage.py migrate`
|
||
6. Update frontend Context to subscribe to SSE updates
|
||
|
||
**Adding a new nomenclature field:**
|
||
1. Create Nomenclature via Django admin or API
|
||
2. Define NomenclatureFields
|
||
3. Frontend auto-loads on next mount
|
||
4. No code changes needed (dynamic rendering in Main.jsx)
|
||
|
||
**Adding a report element type:**
|
||
1. Create new Element subclass in `models/Element.jsx`
|
||
2. Add component file (e.g., `MyElement.jsx`)
|
||
3. Register in `EditorCanvas.jsx` render switch
|
||
4. Add tool button in `Toolbar.jsx`
|
||
5. Add properties in `ObjectInspector.jsx`
|