Merge lane/abrechnung in feature/craftvia-mvp
Konflikte gelöst (additiv): Benachrichtigungstexte (Planung + Meilensteine), Event-Typen, Navigation, Job-Queues/Prozessoren (geocode-site, planning-watch, billing-pdf), OpenAPI-Tags, Smoke-Prüfungen (zweiter Mandant: Planung + Abrechnung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -750,6 +750,8 @@ model WorkOrder {
|
||||
voiceNotes VoiceNote[]
|
||||
reports Report[]
|
||||
documents Document[]
|
||||
milestones WorkOrderMilestone[] // L14
|
||||
billingRecords BillingRecord[] // L14
|
||||
|
||||
@@unique([tenantId, number])
|
||||
@@index([tenantId, status])
|
||||
@@ -872,6 +874,8 @@ model MaterialUsage {
|
||||
photoId String? @map("photo_id")
|
||||
recordedById String? @map("recorded_by_id")
|
||||
clientId String? @map("client_id") // offline local id
|
||||
/// L14: billed billing record containing this position (set on „abgerechnet", cleared on void)
|
||||
billingRecordId String? @map("billing_record_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@ -879,6 +883,7 @@ model MaterialUsage {
|
||||
materialPlan MaterialPlan? @relation(fields: [materialPlanId], references: [id], onDelete: SetNull)
|
||||
workSession WorkSession? @relation(fields: [workSessionId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([tenantId, billingRecordId])
|
||||
@@index([tenantId, workOrderId])
|
||||
@@unique([tenantId, clientId])
|
||||
@@map("material_usages")
|
||||
@@ -966,11 +971,14 @@ model TimeEntry {
|
||||
/// proposed correction of this entry {startedAt, endedAt, type, reason, requestedAt}; old values stay valid until approval
|
||||
pendingChange Json? @map("pending_change")
|
||||
note String?
|
||||
/// L14: billed billing record containing this entry (set on „abgerechnet", cleared on void)
|
||||
billingRecordId String? @map("billing_record_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
workSession WorkSession @relation(fields: [workSessionId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([tenantId, billingRecordId])
|
||||
@@index([tenantId, workSessionId])
|
||||
@@index([tenantId, userId, startedAt])
|
||||
@@index([tenantId, approvalStatus])
|
||||
@@ -1341,3 +1349,87 @@ model AiGeneration {
|
||||
@@index([tenantId, entityType, entityId])
|
||||
@@map("ai_generations")
|
||||
}
|
||||
|
||||
// ---------- L14 Abrechnungsübersicht (keine Buchhaltung: keine Preise/Beträge/Steuern) ----------
|
||||
|
||||
enum MilestoneStatus {
|
||||
open
|
||||
reached // reported by technician / team lead
|
||||
confirmed // confirmed by back office → billing record
|
||||
billed
|
||||
}
|
||||
|
||||
enum BillingRecordKind {
|
||||
order_completion
|
||||
milestone
|
||||
daily_report
|
||||
}
|
||||
|
||||
enum BillingRecordStatus {
|
||||
open
|
||||
billed
|
||||
voided
|
||||
}
|
||||
|
||||
/// Optional billing section of a work order (defined by back office, reported by the field, confirmed by back office).
|
||||
model WorkOrderMilestone {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
workOrderId String @map("work_order_id")
|
||||
title String
|
||||
description String?
|
||||
sortOrder Int @default(0) @map("sort_order")
|
||||
status MilestoneStatus @default(open)
|
||||
reachedById String? @map("reached_by_id")
|
||||
reachedAt DateTime? @map("reached_at")
|
||||
reachedNote String? @map("reached_note")
|
||||
confirmedById String? @map("confirmed_by_id")
|
||||
confirmedAt DateTime? @map("confirmed_at")
|
||||
rejectedById String? @map("rejected_by_id")
|
||||
rejectedAt DateTime? @map("rejected_at")
|
||||
rejectionReason String? @map("rejection_reason")
|
||||
/// billed record this milestone belongs to (plain id, no FK)
|
||||
billingRecordId String? @map("billing_record_id")
|
||||
createdById String? @map("created_by_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
|
||||
workOrder WorkOrder @relation(fields: [workOrderId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([tenantId, workOrderId])
|
||||
@@index([tenantId, status])
|
||||
@@map("work_order_milestones")
|
||||
}
|
||||
|
||||
/// Billing overview entry: an order completion, a confirmed milestone or an approved daily report that the
|
||||
/// accounting (outside Craftvia) can invoice. `snapshot` freezes the statement when marked as billed.
|
||||
/// Partial unique indexes (SQL, migration `*_abrechnung`): one non-voided record per source.
|
||||
model BillingRecord {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
workOrderId String @map("work_order_id")
|
||||
kind BillingRecordKind
|
||||
milestoneId String? @map("milestone_id")
|
||||
reportId String? @map("report_id")
|
||||
periodFrom DateTime @map("period_from")
|
||||
periodTo DateTime @map("period_to")
|
||||
status BillingRecordStatus @default(open)
|
||||
snapshot Json?
|
||||
pdfDocumentId String? @map("pdf_document_id")
|
||||
pdfChecksum String? @map("pdf_checksum")
|
||||
invoiceNumber String? @map("invoice_number")
|
||||
billedById String? @map("billed_by_id")
|
||||
billedAt DateTime? @map("billed_at")
|
||||
voidedById String? @map("voided_by_id")
|
||||
voidedAt DateTime? @map("voided_at")
|
||||
voidReason String? @map("void_reason")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
workOrder WorkOrder @relation(fields: [workOrderId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([tenantId, status, createdAt])
|
||||
@@index([tenantId, workOrderId])
|
||||
@@map("billing_records")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user