7f7da15965
Completed: - Phase 0: Project Setup (T001-T003) - Docker Compose, FastAPI skeleton, React SPA - Phase 1: Auth System (T004-T008) - DB models, JWT auth, RBAC middleware, user management - Phase 2: Contacts & Tags (T009-T011) - CRUD API + UI - Phase 3: Equipment Catalog (T012-T014) - Models, API, UI with barcode/QR - Phase 4: Crew Management (T015-T017) - Models, availability, UI - Phase 5: Vehicle Fleet (T018-T020) - Models, assignments, UI - Phase 6: Projects (T021-T023) - Project hierarchy models, CRUD API, list/detail UI
731 lines
28 KiB
Markdown
731 lines
28 KiB
Markdown
# Database Design – Rentman.io Clone
|
||
|
||
> **Version:** 1.0
|
||
> **Date:** 2026-05-31
|
||
> **Project:** Rentman.io-Nachbau
|
||
> **Architect:** Solution Architect (A0 Software Orchestrator)
|
||
|
||
---
|
||
|
||
## 1. Entity-Relationship Diagram (Textual)
|
||
|
||
```
|
||
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||
│ Account │ │ User │ │ Role │
|
||
│ (tenant) │ │ │ │ │
|
||
│ id (PK) │1───*│ id (PK) │*───*│ id (PK) │
|
||
│ name │ │ account_id (FK) │ │ name │
|
||
│ created_at │ │ email │ │ permissions (JSON│
|
||
└──────────────────┘ │ full_name │ └──────────────────┘
|
||
│ password_hash │
|
||
│ role_id (FK) │
|
||
└────────────────────┘
|
||
|
||
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||
│ Tag │ │ Contact │ │ Document │
|
||
│ id (PK) │*───*│ id (PK) │1───*│ id (PK) │
|
||
│ name │ │ account_id (FK) │ │ account_id (FK) │
|
||
│ color │ │ type │ │ name │
|
||
└──────────────────┘ │ company_name │ │ file_path │
|
||
│ first_name │ │ mime_type │
|
||
│ last_name │ │ size_bytes │
|
||
│ email, phone, mobile│ │ project_id (FK) │
|
||
│ street, number, │ │ uploaded_by (FK) │
|
||
│ postalcode, city, │ │ created_at │
|
||
│ country, tax_number│ └──────────────────┘
|
||
│ note │
|
||
└───────────────────┘
|
||
|
||
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||
│ Project │ │ Subproject │ │ ProjectFunctionGroup│
|
||
│ id (PK) │1───*│ id (PK) │1───*│ id (PK) │
|
||
│ account_id (FK) │ │ project_id (FK) │ │ project_id (FK) │
|
||
│ name │ │ name │ │ subproject_id (FK)│
|
||
│ reference │ │ custom_fields (JSON)│ │ name │
|
||
│ number │ └──────────────────┘ │ usageperiod_start/end│
|
||
│ customer_id (FK) │ │ planperiod_start/end│
|
||
│ contact_person_id │ │ remark │
|
||
│ account_manager_id│ └──────┬───────────┘
|
||
│ project_type │ │
|
||
│ planperiod_start/end│ │
|
||
│ usageperiod_start/end│ │
|
||
│ status │ ┌──────▼──────────┐
|
||
│ location_* │ │ ProjectFunction │
|
||
│ remark │ │ id (PK) │
|
||
│ price, cost │ │ project_id (FK) │
|
||
│ created_at, updated_at│ │ subproject_id (FK)│
|
||
│ archived │ │ group_id (FK) │
|
||
└──────┬────────────┘ │ type (enum) │
|
||
│ │ amount, usg/plan│
|
||
│ │ cost_rate, price_rate│
|
||
│ │ remark │
|
||
│ └──────┬──────────┘
|
||
│ │
|
||
│ ┌──────▼──────────┐
|
||
├───────────────────────────────────────────►│ ProjectCrew │
|
||
│ │ id (PK) │
|
||
│ ProjectEquipmentGroup │ project_id (FK) │
|
||
├───────────────────────────────────────────►│ function_id (FK) │
|
||
│ │ crew_id (FK) │
|
||
│ ProjectEquipment (within group) │ start, end │
|
||
│ └──────────────────┘
|
||
│
|
||
├──► Quote
|
||
│ (id, project_id, number, status, valid_until,
|
||
│ template, content_html, total_net, total_gross, created_at)
|
||
│
|
||
├──► Invoice
|
||
│ (id, project_id, number, status, due_date,
|
||
│ total_net, total_gross, created_at)
|
||
│
|
||
├──► ProjectRequest (id, name, contact fields, location, periods, status, remark)
|
||
│ │
|
||
│ └──► ProjectRequestEquipment (id, request_id, name, quantity, unit_price, linked_equipment_id)
|
||
│
|
||
└──► VehicleAssignment
|
||
(id, vehicle_id, project_id, start, end, driver_id)
|
||
|
||
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||
│ Equipment │ │ Crew │ │ Vehicle │
|
||
│ id (PK) │ │ id (PK) │ │ id (PK) │
|
||
│ account_id (FK) │ │ account_id (FK) │ │ account_id (FK) │
|
||
│ name │ │ first_name │ │ name │
|
||
│ category │ │ last_name │ │ license_plate │
|
||
│ serial_number │ │ email, phone │ │ type │
|
||
│ barcode, qr_code │ │ type (internal/ │ │ payload_kg │
|
||
│ stock_location_id │ │ freelancer) │ │ volume_m3 │
|
||
│ status │ │ hourly_rate │ │ is_active │
|
||
│ purchase_price │ │ daily_rate │ └──────────────────┘
|
||
│ current_value │ │ skills (JSON) │
|
||
│ weight_kg, dims, │ │ note │
|
||
│ power_watt, notes │ │ user_id (FK) │
|
||
│ custom_fields, img│ │ is_active │
|
||
└──────────────────┘ └────────┬──────────┘
|
||
│
|
||
┌──────────────────┐ ┌────────▼──────────┐
|
||
│ EquipmentGroup │ │ CrewAvailability │
|
||
│ (Bundle) │ │ id (PK) │
|
||
│ id (PK) │ │ crew_id (FK) │
|
||
│ account_id (FK) │ │ start, end │
|
||
│ name │ │ type (avail/unavail│
|
||
│ items (JSON/M2M) │ │ reason │
|
||
└──────────────────┘ └───────────────────┘
|
||
|
||
┌───────────────────┐
|
||
│ StockLocation │
|
||
│ id (PK) │
|
||
│ account_id (FK) │
|
||
│ name │
|
||
│ address │
|
||
│ is_default │
|
||
└───────────────────┘
|
||
|
||
┌───────────────────┐
|
||
│ AuditLog │
|
||
│ id (PK) │
|
||
│ account_id (FK) │
|
||
│ user_id (FK) │
|
||
│ action │
|
||
│ entity_type │
|
||
│ entity_id │
|
||
│ details (JSON) │
|
||
│ timestamp │
|
||
└───────────────────┘
|
||
```
|
||
|
||
## 2. Table Definitions
|
||
|
||
### 2.1 Account
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PRIMARY KEY, DEFAULT gen_random_uuid() (on PG) |
|
||
| name | VARCHAR(255) | NOT NULL |
|
||
| created_at | TIMESTAMP WITH TIME ZONE | NOT NULL, DEFAULT now() |
|
||
|
||
**Indexes:** None extra beyond PK.
|
||
|
||
---
|
||
|
||
### 2.2 User
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| email | VARCHAR(255) | NOT NULL, UNIQUE |
|
||
| full_name | VARCHAR(255) | NOT NULL |
|
||
| password_hash | VARCHAR(255) | NOT NULL |
|
||
| role_id | UUID | NOT NULL, FK → Role(id) |
|
||
| is_active | BOOLEAN | NOT NULL, DEFAULT TRUE |
|
||
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() |
|
||
| updated_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() |
|
||
|
||
**Indexes:**
|
||
- `ix_user_account_id` on `account_id`
|
||
- `ix_user_email` UNIQUE on `email`
|
||
|
||
---
|
||
|
||
### 2.3 Role
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| name | VARCHAR(100) | NOT NULL |
|
||
| permissions | JSONB | NOT NULL (array of strings like `["projects:read", "projects:write"]`) |
|
||
|
||
**Indexes:**
|
||
- `ix_role_account_id` on `account_id`
|
||
|
||
---
|
||
|
||
### 2.4 Tag
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| name | VARCHAR(100) | NOT NULL |
|
||
| color | VARCHAR(7) | NULLABLE (hex color) |
|
||
|
||
**Indexes:**
|
||
- `ix_tag_account_id` on `account_id`
|
||
|
||
**Relationships:**
|
||
- Many-to-Many with Equipment, Project, Crew (via join tables: `equipment_tags`, `project_tags`, `crew_tags`).
|
||
|
||
---
|
||
|
||
### 2.5 Contact
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| type | ENUM('company','person') | NOT NULL |
|
||
| company_name | VARCHAR(255) | NULLABLE |
|
||
| first_name | VARCHAR(100) | NULLABLE |
|
||
| last_name | VARCHAR(100) | NULLABLE |
|
||
| email | VARCHAR(255) | NULLABLE |
|
||
| phone | VARCHAR(50) | NULLABLE |
|
||
| mobile | VARCHAR(50) | NULLABLE |
|
||
| street | VARCHAR(255) | NULLABLE |
|
||
| number | VARCHAR(20) | NULLABLE |
|
||
| postalcode | VARCHAR(20) | NULLABLE |
|
||
| city | VARCHAR(100) | NULLABLE |
|
||
| country | VARCHAR(100) | NULLABLE |
|
||
| tax_number | VARCHAR(50) | NULLABLE |
|
||
| note | TEXT | NULLABLE |
|
||
|
||
**Indexes:**
|
||
- `ix_contact_account_id` on `account_id`
|
||
- `ix_contact_type` on `type`
|
||
|
||
---
|
||
|
||
### 2.6 Document
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| name | VARCHAR(255) | NOT NULL |
|
||
| original_filename | VARCHAR(255) | NOT NULL |
|
||
| file_path | VARCHAR(1024) | NOT NULL (relative to storage root) |
|
||
| mime_type | VARCHAR(127) | NOT NULL |
|
||
| size_bytes | INTEGER | NOT NULL |
|
||
| project_id | UUID | NULLABLE, FK → Project(id) |
|
||
| uploaded_by | UUID | NOT NULL, FK → User(id) |
|
||
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() |
|
||
|
||
**Indexes:**
|
||
- `ix_document_account_id` on `account_id`
|
||
- `ix_document_project_id` on `project_id`
|
||
|
||
---
|
||
|
||
### 2.7 Project
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| name | VARCHAR(255) | NOT NULL |
|
||
| reference | VARCHAR(50) | NULLABLE |
|
||
| number | VARCHAR(50) | NOT NULL, automatically generated (prefix + sequential) |
|
||
| displayname | VARCHAR(512) | GENERATED (name + number) |
|
||
| customer_id | UUID | NULLABLE, FK → Contact(id) |
|
||
| contact_person_id | UUID | NULLABLE, FK → Contact(id) |
|
||
| account_manager_id | UUID | NULLABLE, FK → User(id) |
|
||
| project_type | ENUM('rental','service','sale') | NULLABLE |
|
||
| planperiod_start | TIMESTAMPTZ | NOT NULL |
|
||
| planperiod_end | TIMESTAMPTZ | NOT NULL |
|
||
| usageperiod_start | TIMESTAMPTZ | NOT NULL |
|
||
| usageperiod_end | TIMESTAMPTZ | NOT NULL |
|
||
| status | ENUM('request','planned','confirmed','active','completed','cancelled') | DEFAULT 'planned' |
|
||
| location_name | VARCHAR(255) | NULLABLE |
|
||
| location_street | VARCHAR(255) | NULLABLE |
|
||
| location_city | VARCHAR(100) | NULLABLE |
|
||
| location_postalcode | VARCHAR(20) | NULLABLE |
|
||
| location_country | VARCHAR(100) | NULLABLE |
|
||
| remark | TEXT | NULLABLE |
|
||
| price | DECIMAL(12,2) | NULLABLE (calculated field, updated by triggers) |
|
||
| cost | DECIMAL(12,2) | NULLABLE |
|
||
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() |
|
||
| updated_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() |
|
||
| archived | BOOLEAN | NOT NULL, DEFAULT FALSE |
|
||
|
||
**Indexes:**
|
||
- `ix_project_account_id` on `account_id`
|
||
- `ix_project_status` on `status`
|
||
- `ix_project_planperiod` on `planperiod_start`, `planperiod_end` (for availability queries)
|
||
- `ix_project_customer_id` on `customer_id`
|
||
|
||
---
|
||
|
||
### 2.8 Subproject
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| project_id | UUID | NOT NULL, FK → Project(id) ON DELETE CASCADE |
|
||
| name | VARCHAR(255) | NOT NULL |
|
||
| custom_fields | JSONB | NULLABLE |
|
||
|
||
**Indexes:**
|
||
- `ix_subproject_project_id` on `project_id`
|
||
|
||
---
|
||
|
||
### 2.9 ProjectFunctionGroup
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| project_id | UUID | NOT NULL, FK → Project(id) ON DELETE CASCADE |
|
||
| subproject_id | UUID | NULLABLE, FK → Subproject(id) |
|
||
| name | VARCHAR(255) | NOT NULL |
|
||
| usageperiod_start | TIMESTAMPTZ | NULLABLE |
|
||
| usageperiod_end | TIMESTAMPTZ | NULLABLE |
|
||
| planperiod_start | TIMESTAMPTZ | NULLABLE |
|
||
| planperiod_end | TIMESTAMPTZ | NULLABLE |
|
||
| remark | TEXT | NULLABLE |
|
||
|
||
**Indexes:**
|
||
- `ix_pfg_project_id` on `project_id`
|
||
|
||
---
|
||
|
||
### 2.10 ProjectFunction
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| project_id | UUID | NOT NULL, FK → Project(id) ON DELETE CASCADE |
|
||
| subproject_id | UUID | NULLABLE, FK → Subproject(id) |
|
||
| group_id | UUID | NULLABLE, FK → ProjectFunctionGroup(id) |
|
||
| name | VARCHAR(255) | NOT NULL |
|
||
| type | ENUM('crew_function','equipment_function','transport_function') | NOT NULL |
|
||
| amount | INTEGER | DEFAULT 1 |
|
||
| usageperiod_start | TIMESTAMPTZ | NULLABLE |
|
||
| usageperiod_end | TIMESTAMPTZ | NULLABLE |
|
||
| planperiod_start | TIMESTAMPTZ | NULLABLE |
|
||
| planperiod_end | TIMESTAMPTZ | NULLABLE |
|
||
| cost_rate | DECIMAL(10,2) | NULLABLE |
|
||
| price_rate | DECIMAL(10,2) | NULLABLE |
|
||
| remark | TEXT | NULLABLE |
|
||
|
||
**Indexes:**
|
||
- `ix_projectfunction_project_id` on `project_id`
|
||
- `ix_projectfunction_group_id` on `group_id`
|
||
|
||
---
|
||
|
||
### 2.11 ProjectCrew
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| project_id | UUID | NOT NULL, FK → Project(id) |
|
||
| function_id | UUID | NOT NULL, FK → ProjectFunction(id) ON DELETE CASCADE |
|
||
| crew_id | UUID | NOT NULL, FK → Crew(id) |
|
||
| start | TIMESTAMPTZ | NULLABLE (can default to project usage period) |
|
||
| end | TIMESTAMPTZ | NULLABLE |
|
||
|
||
**Indexes:**
|
||
- `ix_projectcrew_project_id` on `project_id`
|
||
- `ix_projectcrew_function_id` on `function_id`
|
||
- `ix_projectcrew_crew_id` on `crew_id`
|
||
|
||
---
|
||
|
||
### 2.12 ProjectEquipmentGroup
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| project_id | UUID | NOT NULL, FK → Project(id) ON DELETE CASCADE |
|
||
| subproject_id | UUID | NULLABLE, FK → Subproject(id) |
|
||
| name | VARCHAR(255) | NOT NULL |
|
||
| usageperiod_start | TIMESTAMPTZ | NULLABLE |
|
||
| usageperiod_end | TIMESTAMPTZ | NULLABLE |
|
||
| planperiod_start | TIMESTAMPTZ | NULLABLE |
|
||
| planperiod_end | TIMESTAMPTZ | NULLABLE |
|
||
| in_price_calculation | BOOLEAN | DEFAULT TRUE |
|
||
| remark | TEXT | NULLABLE |
|
||
|
||
**Indexes:**
|
||
- `ix_peg_project_id` on `project_id`
|
||
|
||
---
|
||
|
||
### 2.13 ProjectEquipment
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| project_id | UUID | NOT NULL, FK → Project(id) |
|
||
| equipment_group_id | UUID | NOT NULL, FK → ProjectEquipmentGroup(id) ON DELETE CASCADE |
|
||
| equipment_id | UUID | NULLABLE, FK → Equipment(id) |
|
||
| name | VARCHAR(255) | NOT NULL (can be free-text if equipment_id is NULL) |
|
||
| quantity | INTEGER | NOT NULL, DEFAULT 1 |
|
||
| quantity_total | INTEGER | NULLABLE (available inventory count) |
|
||
| unit_price | DECIMAL(10,2) | NULLABLE |
|
||
| discount | DECIMAL(5,2) | NULLABLE (percentage) |
|
||
| external_remark | TEXT | NULLABLE |
|
||
| internal_remark | TEXT | NULLABLE |
|
||
|
||
**Indexes:**
|
||
- `ix_projectequipment_group_id` on `equipment_group_id`
|
||
- `ix_projectequipment_equipment_id` on `equipment_id`
|
||
|
||
---
|
||
|
||
### 2.14 Equipment (Catalog)
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| name | VARCHAR(255) | NOT NULL |
|
||
| category | VARCHAR(100) | NULLABLE |
|
||
| brand | VARCHAR(100) | NULLABLE |
|
||
| model | VARCHAR(100) | NULLABLE |
|
||
| serial_number | VARCHAR(100) | NULLABLE |
|
||
| barcode | VARCHAR(100) | NULLABLE, UNIQUE |
|
||
| qr_code | VARCHAR(100) | NULLABLE, UNIQUE |
|
||
| stock_location_id | UUID | NULLABLE, FK → StockLocation(id) |
|
||
| status | ENUM('available','maintenance','defect','retired') | DEFAULT 'available' |
|
||
| purchase_price | DECIMAL(10,2) | NULLABLE |
|
||
| current_value | DECIMAL(10,2) | NULLABLE |
|
||
| weight_kg | DECIMAL(10,2) | NULLABLE |
|
||
| dimensions_cm | VARCHAR(50) | NULLABLE |
|
||
| power_watt | INTEGER | NULLABLE |
|
||
| custom_fields | JSONB | NULLABLE |
|
||
| notes | TEXT | NULLABLE |
|
||
| image_url | VARCHAR(2048) | NULLABLE |
|
||
|
||
**Indexes:**
|
||
- `ix_equipment_account_id` on `account_id`
|
||
- `ix_equipment_category` on `category`
|
||
- `ix_equipment_status` on `status`
|
||
- `ix_equipment_barcode` UNIQUE on `barcode`
|
||
- `ix_equipment_qrcode` UNIQUE on `qr_code`
|
||
|
||
---
|
||
|
||
### 2.15 StockLocation
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| name | VARCHAR(100) | NOT NULL |
|
||
| address | VARCHAR(255) | NULLABLE |
|
||
| is_default | BOOLEAN | DEFAULT FALSE |
|
||
|
||
**Indexes:**
|
||
- `ix_stocklocation_account_id` on `account_id`
|
||
|
||
---
|
||
|
||
### 2.16 Crew
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| first_name | VARCHAR(100) | NOT NULL |
|
||
| last_name | VARCHAR(100) | NOT NULL |
|
||
| email | VARCHAR(255) | NULLABLE |
|
||
| phone | VARCHAR(50) | NULLABLE |
|
||
| type | ENUM('internal','freelancer') | NOT NULL |
|
||
| user_id | UUID | NULLABLE, FK → User(id) (if they have login) |
|
||
| hourly_rate | DECIMAL(10,2) | NULLABLE |
|
||
| daily_rate | DECIMAL(10,2) | NULLABLE |
|
||
| skills | JSONB | NULLABLE (array of strings) |
|
||
| is_active | BOOLEAN | DEFAULT TRUE |
|
||
| note | TEXT | NULLABLE |
|
||
|
||
**Indexes:**
|
||
- `ix_crew_account_id` on `account_id`
|
||
- `ix_crew_type` on `type`
|
||
- `ix_crew_is_active` on `is_active`
|
||
|
||
---
|
||
|
||
### 2.17 CrewAvailability
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| crew_id | UUID | NOT NULL, FK → Crew(id) ON DELETE CASCADE |
|
||
| start | TIMESTAMPTZ | NOT NULL |
|
||
| end | TIMESTAMPTZ | NOT NULL |
|
||
| type | ENUM('available','unavailable','tentative') | DEFAULT 'unavailable' |
|
||
| reason | VARCHAR(255) | NULLABLE |
|
||
|
||
**Indexes:**
|
||
- `ix_crewavailability_crew_id` on `crew_id`
|
||
- `ix_crewavailability_period` on `start`, `end` (for overlap checks)
|
||
|
||
---
|
||
|
||
### 2.18 Vehicle
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| name | VARCHAR(100) | NOT NULL |
|
||
| license_plate | VARCHAR(20) | NULLABLE |
|
||
| type | VARCHAR(50) | NULLABLE |
|
||
| payload_kg | INTEGER | NULLABLE |
|
||
| volume_m3 | DECIMAL(10,2) | NULLABLE |
|
||
| is_active | BOOLEAN | DEFAULT TRUE |
|
||
|
||
**Indexes:**
|
||
- `ix_vehicle_account_id` on `account_id`
|
||
|
||
---
|
||
|
||
### 2.19 VehicleAssignment
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| vehicle_id | UUID | NOT NULL, FK → Vehicle(id) |
|
||
| project_id | UUID | NOT NULL, FK → Project(id) |
|
||
| start | TIMESTAMPTZ | NOT NULL |
|
||
| end | TIMESTAMPTZ | NOT NULL |
|
||
| driver_id | UUID | NULLABLE, FK → Crew(id) |
|
||
|
||
**Indexes:**
|
||
- `ix_vehicleassignment_vehicle_id` on `vehicle_id`
|
||
- `ix_vehicleassignment_project_id` on `project_id`
|
||
- `ix_vehicleassignment_period` on `start`, `end`
|
||
|
||
---
|
||
|
||
### 2.20 ProjectRequest
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| name | VARCHAR(255) | NOT NULL |
|
||
| contact_name | VARCHAR(255) | NULLABLE |
|
||
| contact_person_first_name | VARCHAR(100) | NULLABLE |
|
||
| contact_person_lastname | VARCHAR(100) | NULLABLE |
|
||
| contact_person_email | VARCHAR(255) | NULLABLE |
|
||
| location_name | VARCHAR(255) | NULLABLE |
|
||
| location_mailing_street | VARCHAR(255) | NULLABLE |
|
||
| location_mailing_number | VARCHAR(20) | NULLABLE |
|
||
| location_mailing_postalcode | VARCHAR(20) | NULLABLE |
|
||
| location_mailing_city | VARCHAR(100) | NULLABLE |
|
||
| location_mailing_country | VARCHAR(100) | NULLABLE |
|
||
| planperiod_start | TIMESTAMPTZ | NULLABLE |
|
||
| planperiod_end | TIMESTAMPTZ | NULLABLE |
|
||
| usageperiod_start | TIMESTAMPTZ | NULLABLE |
|
||
| usageperiod_end | TIMESTAMPTZ | NULLABLE |
|
||
| status | ENUM('new','processing','converted','rejected') | DEFAULT 'new' |
|
||
| remark | TEXT | NULLABLE |
|
||
| created_at | TIMESTAMPTZ | DEFAULT now() |
|
||
|
||
**Indexes:**
|
||
- `ix_projectrequest_account_id` on `account_id`
|
||
- `ix_projectrequest_status` on `status`
|
||
|
||
---
|
||
|
||
### 2.21 ProjectRequestEquipment
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| project_request_id | UUID | NOT NULL, FK → ProjectRequest(id) ON DELETE CASCADE |
|
||
| name | VARCHAR(255) | NOT NULL |
|
||
| quantity | INTEGER | NOT NULL |
|
||
| quantity_total | INTEGER | NULLABLE |
|
||
| unit_price | DECIMAL(10,2) | NULLABLE |
|
||
| linked_equipment_id | UUID | NULLABLE, FK → Equipment(id) |
|
||
|
||
**Indexes:**
|
||
- `ix_prequestequip_request_id` on `project_request_id`
|
||
|
||
---
|
||
|
||
### 2.22 Quote
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| project_id | UUID | NOT NULL, FK → Project(id) |
|
||
| number | VARCHAR(50) | NOT NULL (auto-generated) |
|
||
| status | ENUM('draft','sent','accepted','rejected') | DEFAULT 'draft' |
|
||
| valid_until | DATE | NULLABLE |
|
||
| template | VARCHAR(50) | NULLABLE |
|
||
| content_html | TEXT | NULLABLE (generated PDF content) |
|
||
| total_net | DECIMAL(12,2) | NULLABLE |
|
||
| total_gross | DECIMAL(12,2) | NULLABLE |
|
||
| created_at | TIMESTAMPTZ | DEFAULT now() |
|
||
|
||
**Indexes:**
|
||
- `ix_quote_account_id` on `account_id`
|
||
- `ix_quote_project_id` on `project_id`
|
||
- `ix_quote_status` on `status`
|
||
|
||
---
|
||
|
||
### 2.23 Invoice
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| project_id | UUID | NOT NULL, FK → Project(id) |
|
||
| number | VARCHAR(50) | NOT NULL (auto-generated) |
|
||
| status | ENUM('draft','sent','paid','overdue','cancelled') | DEFAULT 'draft' |
|
||
| due_date | DATE | NULLABLE |
|
||
| total_net | DECIMAL(12,2) | NULLABLE |
|
||
| total_gross | DECIMAL(12,2) | NULLABLE |
|
||
| created_at | TIMESTAMPTZ | DEFAULT now() |
|
||
|
||
**Indexes:**
|
||
- `ix_invoice_account_id` on `account_id`
|
||
- `ix_invoice_project_id` on `project_id`
|
||
- `ix_invoice_status` on `status`
|
||
|
||
---
|
||
|
||
### 2.24 Template
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| type | ENUM('quote','invoice','packing_list','delivery_note') | NOT NULL |
|
||
| name | VARCHAR(100) | NOT NULL |
|
||
| content_html | TEXT | NOT NULL |
|
||
| is_default | BOOLEAN | DEFAULT FALSE |
|
||
|
||
**Indexes:**
|
||
- `ix_template_account_id` on `account_id`
|
||
|
||
---
|
||
|
||
### 2.25 Webhook
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| url | VARCHAR(2048) | NOT NULL |
|
||
| secret | VARCHAR(255) | NOT NULL |
|
||
| events | JSONB | NOT NULL (array of event names) |
|
||
| is_active | BOOLEAN | DEFAULT TRUE |
|
||
|
||
**Indexes:**
|
||
- `ix_webhook_account_id` on `account_id`
|
||
|
||
---
|
||
|
||
### 2.26 WebhookLog
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| webhook_id | UUID | NOT NULL, FK → Webhook(id) ON DELETE CASCADE |
|
||
| event | VARCHAR(100) | NOT NULL |
|
||
| request_body | JSONB | NOT NULL |
|
||
| response_status | INTEGER | NOT NULL |
|
||
| response_body | TEXT | NULLABLE |
|
||
| success | BOOLEAN | NOT NULL |
|
||
| created_at | TIMESTAMPTZ | DEFAULT now() |
|
||
|
||
**Indexes:**
|
||
- `ix_webhooklog_webhook_id` on `webhook_id`
|
||
|
||
---
|
||
|
||
### 2.27 AuditLog
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| user_id | UUID | NOT NULL, FK → User(id) |
|
||
| action | VARCHAR(50) | NOT NULL (e.g., 'create', 'update', 'delete') |
|
||
| entity_type | VARCHAR(50) | NOT NULL (e.g., 'project', 'equipment') |
|
||
| entity_id | UUID | NOT NULL |
|
||
| details | JSONB | NULLABLE (changed fields) |
|
||
| timestamp | TIMESTAMPTZ | NOT NULL, DEFAULT now() |
|
||
|
||
**Indexes:**
|
||
- `ix_auditlog_account_id` on `account_id`
|
||
- `ix_auditlog_user_id` on `user_id`
|
||
- `ix_auditlog_entity` on `entity_type`, `entity_id`
|
||
- `ix_auditlog_timestamp` on `timestamp`
|
||
|
||
---
|
||
|
||
### 2.28 EquipmentGroup (Bundle)
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| id | UUID | PK |
|
||
| account_id | UUID | NOT NULL, FK → Account(id) |
|
||
| name | VARCHAR(255) | NOT NULL |
|
||
|
||
**Relationships:**
|
||
- Items stored in junction table `equipment_group_items` with columns `equipment_group_id (FK)`, `equipment_id (FK)`, `quantity` (INTEGER).
|
||
|
||
---
|
||
|
||
## 3. Junction / Many-to-Many Tables
|
||
|
||
### 3.1 EquipmentGroupItems
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| equipment_group_id | UUID | NOT NULL, FK → EquipmentGroup(id) ON DELETE CASCADE |
|
||
| equipment_id | UUID | NOT NULL, FK → Equipment(id) ON DELETE CASCADE |
|
||
| quantity | INTEGER | NOT NULL |
|
||
| PRIMARY KEY (equipment_group_id, equipment_id) |
|
||
|
||
### 3.2 EquipmentTags
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| equipment_id | UUID | NOT NULL, FK → Equipment(id) ON DELETE CASCADE |
|
||
| tag_id | UUID | NOT NULL, FK → Tag(id) ON DELETE CASCADE |
|
||
| PRIMARY KEY (equipment_id, tag_id) |
|
||
|
||
### 3.3 ProjectTags
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| project_id | UUID | NOT NULL, FK → Project(id) ON DELETE CASCADE |
|
||
| tag_id | UUID | NOT NULL, FK → Tag(id) ON DELETE CASCADE |
|
||
| PRIMARY KEY (project_id, tag_id) |
|
||
|
||
### 3.4 CrewTags
|
||
| Column | Type | Constraints |
|
||
|--------|------|-------------|
|
||
| crew_id | UUID | NOT NULL, FK → Crew(id) ON DELETE CASCADE |
|
||
| tag_id | UUID | NOT NULL, FK → Tag(id) ON DELETE CASCADE |
|
||
| PRIMARY KEY (crew_id, tag_id) |
|
||
|
||
---
|
||
|
||
## 4. Migration Strategy
|
||
|
||
We use Alembic for database migrations. The migration environment is configured with `async` support for SQLAlchemy (since our backend uses async sessions).
|
||
|
||
Initial migration creates all tables with UUID generation appropriate to the dialect:
|
||
- PostgreSQL: `server_default=func.gen_random_uuid()`
|
||
- SQLite: a Python side UUID generation on insert (via model default)
|
||
|
||
For development, SQLite will be used; however, tests should run against PostgreSQL to catch dialect-specific issues early.
|
||
|
||
---
|
||
|
||
## 5. Key Database Constraints
|
||
|
||
- **Unique constraints:** User `email` globally; Equipment `barcode` and `qr_code` per account (not globally, since multi-tenant). We'll make `(account_id, barcode)` unique if needed.
|
||
- **Foreign key cascades:** Sub-objects (subprojects, functions, equipment groups, crew assignments) should be deleted when the parent project is deleted (CASCADE). AuditLog and other history are kept (NO ACTION or SET NULL).
|
||
- **Check constraints:** Project `planperiod_end >= planperiod_start`, etc.
|
||
- **Default values:** Most ENUMs have a sensible default.
|