Files
rentman-clone/.a0/design.md
T
Agent Zero 7f7da15965 Initial commit: Rentman Clone - Phase 0-6 (T001-T023)
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
2026-05-31 20:36:42 +00:00

28 KiB
Raw Blame History

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.