generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" } enum RoleCode { employee manager ceo finance finance_mgr administration hr admin } enum EmployeeStatus { active archived } enum DelegationGrantScope { reimbursement_submit all } enum ReimbursementStatus { draft reviewing approved paid rejected cancelled } enum LeaveRequestStatus { draft reviewing approved rejected cancelled } enum PurchaseRequestStatus { draft reviewing approved rejected cancelled } enum TravelRequestStatus { draft reviewing approved rejected } enum TravelTripType { domestic international } enum TravelSegmentRole { outbound return connection } enum TravelTransportType { flight train car bus other } enum AssetCardStatus { in_stock in_use under_repair retired archived } enum InventoryCheckScopeType { all legal_entity cost_center location } enum InventoryCheckStatus { draft counting completed } enum InventoryCheckResult { matched missing extra damaged } enum LeaveDaySession { am pm } enum LeaveLedgerEntryType { grant reserve consume release adjust } enum InvoicePoolStatus { pending_ocr ocr_failed available bound archived } enum InvoiceArchivedReason { used_complete unused_timeout manual } enum OcrProvider { tencent } enum OcrType { vat train flight taxi general } enum DocumentResetPeriod { none daily monthly yearly } enum DocumentCategoryEntityType { reimbursement leave purchase_request travel_request asset_card general } enum HolidayCalendarDayType { holiday workday adjusted_workday } enum WorkflowInstanceStatus { pending approved rejected cancelled terminated } enum WorkflowTaskStatus { pending completed rejected cancelled skipped } enum WorkflowTaskOutcome { approved rejected returned start transfer skipped } enum ApprovalTransferStatus { active cancelled } enum ExpenseCategory { travel rd production office business assets other } model Department { id String @id @default(uuid()) @db.Uuid name String parent_id String? @db.Uuid archived_at DateTime? archived_by String? @db.Uuid created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid parent Department? @relation("DepartmentToParent", fields: [parent_id], references: [id], onDelete: Restrict) children Department[] @relation("DepartmentToParent") employees Employee[] reimbursement_forms ReimbursementForm[] leave_requests LeaveRequest[] purchase_requests PurchaseRequest[] travel_requests TravelRequest[] workflow_instances WorkflowInstance[] budget_pools BudgetPool[] travel_orders TravelOrder[] @@index([parent_id]) @@map("departments") } model Employee { id String @id @default(uuid()) @db.Uuid employee_no String @unique name String phone String @unique email String @unique department_id String @db.Uuid legal_entity_id String? @db.Uuid title String direct_manager_id String? @db.Uuid hire_date DateTime @db.Date status EmployeeStatus @default(active) bank_account_encrypted String bank_name String id_number_encrypted String? password_hash String token_version Int @default(0) must_change_password Boolean @default(false) login_failed_count Int @default(0) login_failed_first_at DateTime? login_locked_until DateTime? archived_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid department Department @relation(fields: [department_id], references: [id], onDelete: Restrict) legal_entity LegalEntity? @relation(fields: [legal_entity_id], references: [id], onDelete: SetNull) direct_manager Employee? @relation("EmployeeToManager", fields: [direct_manager_id], references: [id], onDelete: Restrict) subordinates Employee[] @relation("EmployeeToManager") roles EmployeeRole[] delegation_grants_as_principal DelegationGrant[] @relation("DelegationPrincipal") delegation_grants_as_delegate DelegationGrant[] @relation("DelegationDelegate") auth_sessions AuthSession[] audit_logs AuditLog[] @relation("AuditActor") notifications Notification[] submitted_reimbursements ReimbursementForm[] @relation("ReimbursementSubmitter") owned_reimbursements ReimbursementForm[] @relation("ReimbursementOwner") paid_reimbursements ReimbursementForm[] @relation("ReimbursementPaidBy") leave_requests LeaveRequest[] leave_ledgers LeaveLedger[] owned_cost_centers CostCenter[] @relation("CostCenterOwner") purchase_requests_applicant PurchaseRequest[] @relation("PurchaseRequestApplicant") travel_requests_applicant TravelRequest[] @relation("TravelRequestApplicant") travel_requests_submitted TravelRequest[] @relation("TravelRequestSubmitter") purchase_requests_submitted PurchaseRequest[] @relation("PurchaseRequestSubmitter") purchase_attachments_uploaded PurchaseAttachment[] @relation("PurchaseAttachmentUploader") purchase_order_archives PurchaseOrderArchive[] @relation("PurchaseOrderArchiveArchivedBy") assigned_asset_cards AssetCard[] @relation("AssetCardAssignee") asset_assignments AssetAssignment[] asset_transfers_from AssetTransfer[] @relation("AssetTransferFromEmployee") asset_transfers_to AssetTransfer[] @relation("AssetTransferToEmployee") owned_invoices InvoicePool[] @relation("InvoiceOwner") uploaded_invoices InvoicePool[] @relation("InvoiceUploader") overridden_invoices InvoicePool[] @relation("InvoiceOverrideBy") requested_invoice_verifications InvoiceVerificationRecord[] @relation("InvoiceVerificationRequester") travel_orders TravelOrder[] workflow_instances_started WorkflowInstance[] @relation("WorkflowInstanceSubmitter") workflow_history_actions WorkflowHistory[] @relation("WorkflowHistoryActor") workflow_tasks_assigned WorkflowTask[] @relation("WorkflowTaskAssignee") workflow_tasks_completed WorkflowTask[] @relation("WorkflowTaskCompleter") approval_transfers_from ApprovalTransfer[] @relation("TransferFrom") approval_transfers_to ApprovalTransfer[] @relation("TransferTo") password_reset_codes PasswordResetCode[] @@index([department_id]) @@index([legal_entity_id], map: "idx_employees_legal_entity") @@index([direct_manager_id]) @@map("employees") } model PasswordResetCode { id String @id @default(uuid()) @db.Uuid employee_id String @db.Uuid code_hash String expires_at DateTime used_at DateTime? created_at DateTime @default(now()) created_ip String? created_user_agent String? employee Employee @relation(fields: [employee_id], references: [id], onDelete: Cascade) @@index([employee_id, created_at]) @@index([expires_at]) @@map("password_reset_codes") } model EmployeeRole { employee_id String @db.Uuid role_code RoleCode created_at DateTime @default(now()) created_by String? @db.Uuid employee Employee @relation(fields: [employee_id], references: [id], onDelete: Cascade) @@id([employee_id, role_code]) @@map("employee_roles") } model DelegationGrant { id String @id @default(uuid()) @db.Uuid principal_employee_id String @db.Uuid delegate_employee_id String @db.Uuid scope DelegationGrantScope valid_from DateTime @default(now()) valid_to DateTime? revoked_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid principal_employee Employee @relation("DelegationPrincipal", fields: [principal_employee_id], references: [id], onDelete: Restrict) delegate_employee Employee @relation("DelegationDelegate", fields: [delegate_employee_id], references: [id], onDelete: Restrict) @@index([principal_employee_id]) @@index([delegate_employee_id]) @@map("delegation_grants") } model AuthSession { id String @id @default(uuid()) @db.Uuid employee_id String @db.Uuid refresh_token_hash String device_fingerprint String ip_address String user_agent String created_at DateTime @default(now()) last_seen_at DateTime @default(now()) expires_at DateTime revoked_at DateTime? employee Employee @relation(fields: [employee_id], references: [id], onDelete: Cascade) @@index([employee_id]) @@index([expires_at]) @@index([revoked_at]) @@map("auth_sessions") } model AuditLog { id String @id @default(uuid()) @db.Uuid request_id String actor_id String @db.Uuid action String target_type String target_id String? @db.Uuid ip_address String? user_agent String? extra Json created_at DateTime @default(now()) actor Employee @relation("AuditActor", fields: [actor_id], references: [id], onDelete: Restrict) @@index([request_id]) @@index([actor_id, created_at]) @@index([created_at, actor_id, action], map: "idx_audit_logs_query") @@index([target_type, created_at], map: "idx_audit_logs_target_type_created_at") @@map("audit_logs") } model Notification { id String @id @default(uuid()) @db.Uuid recipient_employee_id String @db.Uuid type String @db.VarChar(64) title String @db.VarChar(200) body String? target_type String? @db.VarChar(64) target_id String? @db.Uuid read_at DateTime? created_at DateTime @default(now()) recipient Employee @relation(fields: [recipient_employee_id], references: [id], onDelete: Cascade) @@index([recipient_employee_id, created_at]) @@index([recipient_employee_id, read_at, created_at], map: "idx_notifications_unread_query") @@map("notifications") } model ExpenseType { id String @id @default(uuid()) @db.Uuid code String @unique name String description String? category ExpenseCategory sort_order Int account_code_default String? is_system_default Boolean @default(false) enabled Boolean @default(true) created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid reimbursement_items ReimbursementItem[] @@index([enabled, sort_order]) @@map("expense_types") } model DocumentSequence { document_type String @id prefix String date_format String separator String number_length Int @default(4) reset_period DocumentResetPeriod current_number Int @default(0) last_reset_date DateTime? updated_at DateTime @updatedAt updated_by String? @db.Uuid @@map("document_sequences") } model LegalEntity { id String @id @default(uuid()) @db.Uuid code String @unique name String short_name String? currency_code String @default("CNY") @db.VarChar(8) tax_no String? default_holiday_calendar_id String? @db.Uuid enabled Boolean @default(true) archived_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid default_holiday_calendar HolidayCalendar? @relation("LegalEntityDefaultHolidayCalendar", fields: [default_holiday_calendar_id], references: [id], onDelete: SetNull) cost_centers CostCenter[] employees Employee[] holiday_calendars HolidayCalendar[] @relation("HolidayCalendarLegalEntity") suppliers Supplier[] purchase_requests PurchaseRequest[] travel_requests TravelRequest[] asset_cards AssetCard[] budget_pools BudgetPool[] @relation("BudgetPoolLegalEntity") @@index([enabled, archived_at], map: "idx_legal_entities_status") @@index([default_holiday_calendar_id], map: "idx_legal_entities_default_calendar") @@map("legal_entities") } model CostCenter { id String @id @default(uuid()) @db.Uuid legal_entity_id String @db.Uuid code String name String parent_id String? @db.Uuid owner_employee_id String? @db.Uuid enabled Boolean @default(true) archived_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid legal_entity LegalEntity @relation(fields: [legal_entity_id], references: [id], onDelete: Restrict) parent CostCenter? @relation("CostCenterToParent", fields: [parent_id], references: [id], onDelete: Restrict) children CostCenter[] @relation("CostCenterToParent") owner_employee Employee? @relation("CostCenterOwner", fields: [owner_employee_id], references: [id], onDelete: SetNull) budget_pools BudgetPool[] @relation("BudgetPoolCostCenter") purchase_requests PurchaseRequest[] travel_requests TravelRequest[] asset_cards AssetCard[] @@unique([legal_entity_id, code], map: "legal_entity_cost_center_code_ux") @@index([legal_entity_id, enabled, archived_at], map: "idx_cost_centers_legal_entity_status") @@index([parent_id], map: "idx_cost_centers_parent") @@index([owner_employee_id], map: "idx_cost_centers_owner") @@map("cost_centers") } model DocumentCategory { id String @id @default(uuid()) @db.Uuid code String @unique name String entity_type DocumentCategoryEntityType sequence_prefix String number_length Int @default(4) reset_period DocumentResetPeriod default_workflow_key String? enabled Boolean @default(true) archived_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid purchase_requests PurchaseRequest[] asset_categories AssetCategory[] @@index([entity_type, enabled, archived_at], map: "idx_document_categories_entity_status") @@map("document_categories") } model HolidayCalendar { id String @id @default(uuid()) @db.Uuid code String @unique name String timezone String @default("Asia/Shanghai") @db.VarChar(64) legal_entity_id String? @db.Uuid enabled Boolean @default(true) archived_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid legal_entity LegalEntity? @relation("HolidayCalendarLegalEntity", fields: [legal_entity_id], references: [id], onDelete: SetNull) days HolidayCalendarDay[] default_for_legal_entities LegalEntity[] @relation("LegalEntityDefaultHolidayCalendar") @@index([legal_entity_id, enabled, archived_at], map: "idx_holiday_calendars_legal_entity_status") @@map("holiday_calendars") } model HolidayCalendarDay { id String @id @default(uuid()) @db.Uuid calendar_id String @db.Uuid date DateTime @db.Date day_type HolidayCalendarDayType name String note String? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid calendar HolidayCalendar @relation(fields: [calendar_id], references: [id], onDelete: Cascade) @@unique([calendar_id, date], map: "holiday_calendar_day_ux") @@index([calendar_id, date], map: "idx_holiday_calendar_days_calendar_date") @@map("holiday_calendar_days") } model Supplier { id String @id @default(uuid()) @db.Uuid code String @unique name String short_name String? tax_no String? contact_name String? contact_phone String? payment_terms String? legal_entity_id String? @db.Uuid enabled Boolean @default(true) archived_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid legal_entity LegalEntity? @relation(fields: [legal_entity_id], references: [id], onDelete: SetNull) purchase_requests PurchaseRequest[] @@index([legal_entity_id, enabled, archived_at], map: "idx_suppliers_legal_entity_status") @@index([name], map: "idx_suppliers_name") @@map("suppliers") } model PurchaseRequest { id String @id @default(uuid()) @db.Uuid request_no String? @unique applicant_id String @db.Uuid submitter_id String? @db.Uuid department_id String @db.Uuid legal_entity_id String? @db.Uuid cost_center_id String? @db.Uuid supplier_id String? @db.Uuid document_category_id String? @db.Uuid budget_pool_id String? @db.Uuid title String @default("") reason String? currency_code String @default("CNY") @db.VarChar(8) total_amount Decimal @default(0) @db.Decimal(14, 2) expected_delivery_date DateTime? @db.Date status PurchaseRequestStatus @default(draft) created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid applicant Employee @relation("PurchaseRequestApplicant", fields: [applicant_id], references: [id], onDelete: Restrict) submitter Employee? @relation("PurchaseRequestSubmitter", fields: [submitter_id], references: [id], onDelete: SetNull) department Department @relation(fields: [department_id], references: [id], onDelete: Restrict) legal_entity LegalEntity? @relation(fields: [legal_entity_id], references: [id], onDelete: SetNull) cost_center CostCenter? @relation(fields: [cost_center_id], references: [id], onDelete: SetNull) supplier Supplier? @relation(fields: [supplier_id], references: [id], onDelete: SetNull) document_category DocumentCategory? @relation(fields: [document_category_id], references: [id], onDelete: SetNull) budget_pool BudgetPool? @relation(fields: [budget_pool_id], references: [id], onDelete: SetNull) items PurchaseRequestItem[] attachments PurchaseAttachment[] order_archive PurchaseOrderArchive? asset_cards AssetCard[] workflow_instances WorkflowInstance[] @@index([applicant_id, status, updated_at], map: "idx_purchase_requests_applicant_status_updated") @@index([submitter_id], map: "idx_purchase_requests_submitter") @@index([department_id], map: "idx_purchase_requests_department") @@index([legal_entity_id], map: "idx_purchase_requests_legal_entity") @@index([cost_center_id], map: "idx_purchase_requests_cost_center") @@index([supplier_id], map: "idx_purchase_requests_supplier") @@index([document_category_id], map: "idx_purchase_requests_document_category") @@index([budget_pool_id], map: "idx_purchase_requests_budget_pool") @@index([status], map: "idx_purchase_requests_status") @@map("purchase_requests") } model PurchaseRequestItem { id String @id @default(uuid()) @db.Uuid purchase_request_id String @db.Uuid description String specification String? quantity Decimal @db.Decimal(14, 2) unit_price Decimal @db.Decimal(14, 2) line_amount Decimal @db.Decimal(14, 2) is_asset_candidate Boolean @default(false) created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid purchase_request PurchaseRequest @relation(fields: [purchase_request_id], references: [id], onDelete: Cascade) @@index([purchase_request_id], map: "idx_purchase_request_items_request") @@map("purchase_request_items") } model PurchaseAttachment { id String @id @default(uuid()) @db.Uuid purchase_request_id String @db.Uuid storage_path String original_name String mime_type String file_size Int uploaded_by String @db.Uuid created_at DateTime @default(now()) purchase_request PurchaseRequest @relation(fields: [purchase_request_id], references: [id], onDelete: Cascade) uploader Employee @relation("PurchaseAttachmentUploader", fields: [uploaded_by], references: [id], onDelete: Restrict) @@index([purchase_request_id, created_at], map: "idx_purchase_attachments_request_created") @@index([uploaded_by], map: "idx_purchase_attachments_uploader") @@map("purchase_attachments") } model PurchaseOrderArchive { id String @id @default(uuid()) @db.Uuid purchase_request_id String @unique @db.Uuid po_no String contract_no String? archived_files Json? archived_at DateTime @default(now()) archived_by String @db.Uuid purchase_request PurchaseRequest @relation(fields: [purchase_request_id], references: [id], onDelete: Cascade) archived_by_employee Employee @relation("PurchaseOrderArchiveArchivedBy", fields: [archived_by], references: [id], onDelete: Restrict) @@index([archived_by], map: "idx_purchase_order_archives_archived_by") @@index([archived_at], map: "idx_purchase_order_archives_archived_at") @@map("purchase_order_archives") } model AssetCategory { id String @id @default(uuid()) @db.Uuid code String @unique name String document_category_id String? @db.Uuid enabled Boolean @default(true) archived_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid document_category DocumentCategory? @relation(fields: [document_category_id], references: [id], onDelete: SetNull) asset_cards AssetCard[] @@index([enabled, archived_at], map: "idx_asset_categories_status") @@index([document_category_id], map: "idx_asset_categories_document_category") @@map("asset_categories") } model AssetCard { id String @id @default(uuid()) @db.Uuid asset_no String @unique name String category_id String @db.Uuid legal_entity_id String @db.Uuid cost_center_id String @db.Uuid purchase_request_id String? @db.Uuid serial_no String? location String? status AssetCardStatus @default(in_stock) assignee_employee_id String? @db.Uuid activated_at DateTime? @db.Date retired_at DateTime? @db.Date created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid category AssetCategory @relation(fields: [category_id], references: [id], onDelete: Restrict) legal_entity LegalEntity @relation(fields: [legal_entity_id], references: [id], onDelete: Restrict) cost_center CostCenter @relation(fields: [cost_center_id], references: [id], onDelete: Restrict) purchase_request PurchaseRequest? @relation(fields: [purchase_request_id], references: [id], onDelete: SetNull) assignee Employee? @relation("AssetCardAssignee", fields: [assignee_employee_id], references: [id], onDelete: SetNull) assignments AssetAssignment[] transfers AssetTransfer[] inventory_items InventoryCheckItem[] @@index([category_id], map: "idx_asset_cards_category") @@index([legal_entity_id], map: "idx_asset_cards_legal_entity") @@index([cost_center_id], map: "idx_asset_cards_cost_center") @@index([purchase_request_id], map: "idx_asset_cards_purchase_request") @@index([assignee_employee_id], map: "idx_asset_cards_assignee") @@index([status], map: "idx_asset_cards_status") @@map("asset_cards") } model AssetAssignment { id String @id @default(uuid()) @db.Uuid asset_id String @db.Uuid employee_id String @db.Uuid assigned_at DateTime @default(now()) returned_at DateTime? note String? asset AssetCard @relation(fields: [asset_id], references: [id], onDelete: Cascade) employee Employee @relation(fields: [employee_id], references: [id], onDelete: Restrict) @@index([asset_id, returned_at], map: "idx_asset_assignments_asset_open") @@index([employee_id, returned_at], map: "idx_asset_assignments_employee_open") @@map("asset_assignments") } model AssetTransfer { id String @id @default(uuid()) @db.Uuid asset_id String @db.Uuid from_employee_id String? @db.Uuid to_employee_id String? @db.Uuid from_location String? to_location String? transferred_at DateTime @default(now()) note String? asset AssetCard @relation(fields: [asset_id], references: [id], onDelete: Cascade) from_employee Employee? @relation("AssetTransferFromEmployee", fields: [from_employee_id], references: [id], onDelete: SetNull) to_employee Employee? @relation("AssetTransferToEmployee", fields: [to_employee_id], references: [id], onDelete: SetNull) @@index([asset_id, transferred_at], map: "idx_asset_transfers_asset_time") @@index([from_employee_id], map: "idx_asset_transfers_from_employee") @@index([to_employee_id], map: "idx_asset_transfers_to_employee") @@map("asset_transfers") } model InventoryCheck { id String @id @default(uuid()) @db.Uuid check_no String @unique scope_type InventoryCheckScopeType scope_ref_id String? @db.VarChar(200) status InventoryCheckStatus @default(draft) started_at DateTime? completed_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid items InventoryCheckItem[] @@index([status, created_at], map: "idx_inventory_checks_status_created") @@index([scope_type, scope_ref_id], map: "idx_inventory_checks_scope") @@map("inventory_checks") } model InventoryCheckItem { id String @id @default(uuid()) @db.Uuid inventory_check_id String @db.Uuid asset_id String @db.Uuid result InventoryCheckResult note String? inventory_check InventoryCheck @relation(fields: [inventory_check_id], references: [id], onDelete: Cascade) asset AssetCard @relation(fields: [asset_id], references: [id], onDelete: Restrict) @@unique([inventory_check_id, asset_id], map: "inventory_check_asset_ux") @@index([asset_id], map: "idx_inventory_check_items_asset") @@map("inventory_check_items") } model ReimbursementForm { id String @id @default(uuid()) @db.Uuid form_number String? @unique submitter_id String @db.Uuid owner_id String @db.Uuid department_id String @db.Uuid reimbursement_date DateTime @db.Date total_amount Decimal @default(0) @db.Decimal(14, 2) status ReimbursementStatus @default(draft) description String? payment_voucher_no String? @db.VarChar(50) payment_date DateTime? @db.Date payment_channel String? @db.VarChar(20) paid_at DateTime? paid_by String? @db.Uuid created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid submitter Employee @relation("ReimbursementSubmitter", fields: [submitter_id], references: [id], onDelete: Restrict) owner Employee @relation("ReimbursementOwner", fields: [owner_id], references: [id], onDelete: Restrict) paid_by_employee Employee? @relation("ReimbursementPaidBy", fields: [paid_by], references: [id], onDelete: SetNull) department Department @relation(fields: [department_id], references: [id], onDelete: Restrict) items ReimbursementItem[] invoice_pool_items InvoicePool[] workflow_instances WorkflowInstance[] approval_transfers ApprovalTransfer[] budget_ledger_entries BudgetLedgerEntry[] travel_orders TravelOrder[] @@index([owner_id]) @@index([submitter_id]) @@index([department_id]) @@index([paid_by]) @@index([status]) @@index([reimbursement_date]) @@index([owner_id, status, updated_at], map: "idx_reimbursement_owner_status_updated_at") @@map("reimbursement_forms") } model ReimbursementItem { id String @id @default(uuid()) @db.Uuid reimbursement_id String @db.Uuid expense_type_id String @db.Uuid expense_date DateTime @db.Date amount Decimal @db.Decimal(14, 2) description String created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid reimbursement_form ReimbursementForm @relation(fields: [reimbursement_id], references: [id], onDelete: Cascade) expense_type ExpenseType @relation(fields: [expense_type_id], references: [id], onDelete: Restrict) invoice_pool_items InvoicePool[] @@index([reimbursement_id]) @@index([expense_type_id]) @@index([expense_date]) @@map("reimbursement_items") } model LeaveType { id String @id @default(uuid()) @db.Uuid code String @unique name String description String? enabled Boolean @default(true) requires_attachment Boolean @default(false) attachment_mime_types Json? sort_order Int @default(100) created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid leave_policy LeavePolicy? leave_requests LeaveRequest[] leave_ledgers LeaveLedger[] @@index([enabled, sort_order], map: "idx_leave_types_enabled_sort_order") @@map("leave_types") } model LeavePolicy { id String @id @default(uuid()) @db.Uuid leave_type_id String @unique @db.Uuid annual_quota_days Decimal @db.Decimal(8, 1) enabled Boolean @default(true) description String? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid leave_type LeaveType @relation(fields: [leave_type_id], references: [id], onDelete: Cascade) leave_ledgers LeaveLedger[] @@index([enabled], map: "idx_leave_policies_enabled") @@map("leave_policies") } model LeaveRequest { id String @id @default(uuid()) @db.Uuid owner_id String @db.Uuid department_id String @db.Uuid leave_type_id String @db.Uuid start_date DateTime @db.Date end_date DateTime @db.Date start_session LeaveDaySession @default(am) end_session LeaveDaySession @default(pm) requested_days Decimal @db.Decimal(8, 1) reason String? attachment_storage_path String? attachment_original_name String? attachment_mime_type String? status LeaveRequestStatus @default(draft) created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid owner Employee @relation(fields: [owner_id], references: [id], onDelete: Restrict) department Department @relation(fields: [department_id], references: [id], onDelete: Restrict) leave_type LeaveType @relation(fields: [leave_type_id], references: [id], onDelete: Restrict) leave_ledgers LeaveLedger[] workflow_instances WorkflowInstance[] @@index([owner_id]) @@index([department_id]) @@index([leave_type_id]) @@index([status]) @@index([owner_id, status, updated_at], map: "idx_leave_owner_status_updated_at") @@index([owner_id, start_date, end_date], map: "idx_leave_owner_date_range") @@map("leave_requests") } model LeaveLedger { id String @id @default(uuid()) @db.Uuid employee_id String @db.Uuid leave_type_id String @db.Uuid leave_policy_id String? @db.Uuid leave_request_id String? @db.Uuid entry_type LeaveLedgerEntryType days Decimal @db.Decimal(8, 1) period_start DateTime? @db.Date period_end DateTime? @db.Date note String? payload Json? created_at DateTime @default(now()) created_by String? @db.Uuid employee Employee @relation(fields: [employee_id], references: [id], onDelete: Restrict) leave_type LeaveType @relation(fields: [leave_type_id], references: [id], onDelete: Restrict) leave_policy LeavePolicy? @relation(fields: [leave_policy_id], references: [id], onDelete: SetNull) leave_request LeaveRequest? @relation(fields: [leave_request_id], references: [id], onDelete: SetNull) @@index([employee_id, leave_type_id, created_at], map: "idx_leave_ledger_employee_type_created_at") @@index([leave_request_id]) @@index([period_start, period_end], map: "idx_leave_ledger_period") @@index([entry_type, employee_id], map: "idx_leave_ledger_entry_employee") @@map("leave_ledgers") } model OcrResult { id String @id @default(uuid()) @db.Uuid file_sha256 String ocr_provider OcrProvider @default(tencent) ocr_type OcrType result_json Json confidence Int? created_at DateTime @default(now()) invoice_pool_items InvoicePool[] @@unique([file_sha256, ocr_provider, ocr_type]) @@index([file_sha256]) @@map("ocr_results") } model InvoicePool { id String @id @default(uuid()) @db.Uuid owner_employee_id String @db.Uuid uploaded_by_employee_id String @db.Uuid storage_path String original_name String mime_type String file_size Int file_sha256 String status InvoicePoolStatus @default(pending_ocr) archived_reason InvoiceArchivedReason? archived_at DateTime? linked_reimbursement_id String? @db.Uuid linked_item_id String? @db.Uuid remarks String? ocr_provider OcrProvider @default(tencent) ocr_type OcrType @default(general) ocr_result_id String? @db.Uuid ocr_attempts Int @default(0) ocr_last_requested_at DateTime? ocr_last_error String? ocr_completed_at DateTime? ocr_locked_at DateTime? ocr_lock_owner String? @db.VarChar(128) invoice_code String? invoice_number String? invoice_date DateTime? @db.Date seller_name String? buyer_name String? amount Decimal? @db.Decimal(14, 2) tax_amount Decimal? @db.Decimal(14, 2) total_amount Decimal? @db.Decimal(14, 2) is_duplicate_override Boolean @default(false) override_ref_form_number String? override_reason String? overridden_by String? @db.Uuid created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid owner_employee Employee @relation("InvoiceOwner", fields: [owner_employee_id], references: [id], onDelete: Restrict) uploaded_by_employee Employee @relation("InvoiceUploader", fields: [uploaded_by_employee_id], references: [id], onDelete: Restrict) overridden_by_employee Employee? @relation("InvoiceOverrideBy", fields: [overridden_by], references: [id], onDelete: Restrict) ocr_result OcrResult? @relation(fields: [ocr_result_id], references: [id], onDelete: SetNull) linked_reimbursement ReimbursementForm? @relation(fields: [linked_reimbursement_id], references: [id], onDelete: SetNull) linked_item ReimbursementItem? @relation(fields: [linked_item_id], references: [id], onDelete: SetNull) verification_records InvoiceVerificationRecord[] @@index([owner_employee_id]) @@index([uploaded_by_employee_id]) @@index([file_sha256]) @@index([invoice_code, invoice_number]) @@index([status]) @@index([status, ocr_locked_at, created_at], map: "idx_invoice_pool_ocr_queue") @@index([owner_employee_id, status, created_at], map: "idx_invoice_pool_owner_status_created_at") @@index([linked_reimbursement_id]) @@map("invoice_pool") } model InvoiceVerificationRecord { id String @id @default(uuid()) @db.Uuid invoice_id String @db.Uuid provider String @db.VarChar(32) status String @db.VarChar(16) request_payload Json? result_payload Json? result_code String? @db.VarChar(64) result_message String? error_code String? @db.VarChar(64) error_message String? duration_ms Int? requested_by String? @db.Uuid requested_at DateTime @default(now()) completed_at DateTime? retry_of_id String? @db.Uuid attempts Int @default(1) created_at DateTime @default(now()) updated_at DateTime @updatedAt invoice InvoicePool @relation(fields: [invoice_id], references: [id], onDelete: Cascade) requester Employee? @relation("InvoiceVerificationRequester", fields: [requested_by], references: [id], onDelete: SetNull) retry_of InvoiceVerificationRecord? @relation("InvoiceVerificationRetry", fields: [retry_of_id], references: [id], onDelete: SetNull) retries InvoiceVerificationRecord[] @relation("InvoiceVerificationRetry") @@index([invoice_id, created_at], map: "idx_invoice_verification_invoice_created_at") @@index([status, requested_at], map: "idx_invoice_verification_status_requested_at") @@index([requested_by]) @@map("invoice_verification_records") } model BudgetPool { id String @id @default(uuid()) @db.Uuid code String @unique name String department_id String? @db.Uuid legal_entity_id String? @db.Uuid cost_center_id String? @db.Uuid status String @default("active") @db.VarChar(16) period_start DateTime @db.Date period_end DateTime @db.Date allocated_amount Decimal @db.Decimal(14, 2) used_amount Decimal @default(0) @db.Decimal(14, 2) warning_threshold_percent Int @default(80) archived_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid department Department? @relation(fields: [department_id], references: [id], onDelete: SetNull) legal_entity LegalEntity? @relation("BudgetPoolLegalEntity", fields: [legal_entity_id], references: [id], onDelete: SetNull) cost_center CostCenter? @relation("BudgetPoolCostCenter", fields: [cost_center_id], references: [id], onDelete: SetNull) ledger_entries BudgetLedgerEntry[] purchase_requests PurchaseRequest[] travel_requests TravelRequest[] @@index([department_id, status], map: "idx_budget_pools_department_status") @@index([legal_entity_id, cost_center_id, status], map: "idx_budget_pools_scope_status") @@index([status, period_start, period_end], map: "idx_budget_pools_period") @@map("budget_pools") } model BudgetLedgerEntry { id String @id @default(uuid()) @db.Uuid budget_pool_id String @db.Uuid reimbursement_id String? @db.Uuid entity_type String? @db.VarChar(32) entity_id String? @db.Uuid kind String @db.VarChar(32) amount Decimal @db.Decimal(14, 2) note String? created_at DateTime @default(now()) created_by String? @db.Uuid budget_pool BudgetPool @relation(fields: [budget_pool_id], references: [id], onDelete: Cascade) reimbursement_form ReimbursementForm? @relation(fields: [reimbursement_id], references: [id], onDelete: SetNull) @@index([budget_pool_id, created_at], map: "idx_budget_ledger_pool_created_at") @@index([reimbursement_id]) @@index([entity_type, entity_id], map: "idx_budget_ledger_entity") @@map("budget_ledger_entries") } model FeatureFlag { id String @id @default(uuid()) @db.Uuid flag_key String @unique @db.VarChar(64) override_mode String @default("force_off") @db.VarChar(16) rollout_type String @default("global") @db.VarChar(32) department_ids Json? note String? version Int @default(1) created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid @@index([flag_key], map: "idx_feature_flags_key") @@map("feature_flags") } model IntegrationOutbox { id String @id @default(uuid()) @db.Uuid event_type String @db.VarChar(64) status String @default("pending") @db.VarChar(16) payload Json dedupe_key String? @db.VarChar(128) attempts Int @default(0) max_attempts Int @default(5) last_error String? next_retry_at DateTime? processed_at DateTime? dead_letter_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid @@unique([event_type, dedupe_key], map: "integration_outbox_event_dedupe_ux") @@index([status, next_retry_at, created_at], map: "idx_integration_outbox_queue") @@map("integration_outbox") } model IntegrationBinding { id String @id @default(uuid()) @db.Uuid provider String @db.VarChar(32) entity_type String @db.VarChar(64) local_id String @db.Uuid external_id String? @db.VarChar(128) external_code String? @db.VarChar(128) last_synced_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid @@unique([provider, entity_type, local_id], map: "integration_bindings_provider_entity_local_ux") @@unique([provider, entity_type, external_id], map: "integration_bindings_provider_entity_external_id_ux") @@index([provider, entity_type, external_code], map: "idx_integration_bindings_provider_entity_external_code") @@map("integration_bindings") } model IntegrationSyncState { id String @id @default(uuid()) @db.Uuid provider String @db.VarChar(32) resource_type String @db.VarChar(32) status String @default("idle") @db.VarChar(16) last_result Json? last_error String? last_started_at DateTime? last_finished_at DateTime? last_success_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid @@unique([provider, resource_type], map: "integration_sync_states_provider_resource_ux") @@index([provider, status], map: "idx_integration_sync_states_provider_status") @@map("integration_sync_states") } model TravelRequest { id String @id @default(uuid()) @db.Uuid request_no String? @unique applicant_employee_id String @db.Uuid submitter_id String? @db.Uuid department_id String @db.Uuid legal_entity_id String @db.Uuid cost_center_id String @db.Uuid budget_pool_id String? @db.Uuid purpose String trip_type TravelTripType start_date DateTime @db.Date end_date DateTime @db.Date estimated_amount Decimal @db.Decimal(14, 2) status TravelRequestStatus @default(draft) created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid applicant Employee @relation("TravelRequestApplicant", fields: [applicant_employee_id], references: [id], onDelete: Restrict) submitter Employee? @relation("TravelRequestSubmitter", fields: [submitter_id], references: [id], onDelete: SetNull) department Department @relation(fields: [department_id], references: [id], onDelete: Restrict) legal_entity LegalEntity @relation(fields: [legal_entity_id], references: [id], onDelete: Restrict) cost_center CostCenter @relation(fields: [cost_center_id], references: [id], onDelete: Restrict) budget_pool BudgetPool? @relation(fields: [budget_pool_id], references: [id], onDelete: SetNull) segments TravelRequestSegment[] travel_orders TravelOrder[] workflow_instances WorkflowInstance[] @@index([applicant_employee_id, start_date], map: "idx_travel_requests_applicant_start") @@index([submitter_id], map: "idx_travel_requests_submitter") @@index([department_id, status], map: "idx_travel_requests_department_status") @@index([legal_entity_id, cost_center_id], map: "idx_travel_requests_legal_cost_center") @@index([budget_pool_id], map: "idx_travel_requests_budget_pool") @@index([status], map: "idx_travel_requests_status") @@map("travel_requests") } model TravelRequestSegment { id String @id @default(uuid()) @db.Uuid travel_request_id String @db.Uuid sort_order Int segment_role TravelSegmentRole transport_type TravelTransportType from_city String to_city String depart_at DateTime arrive_at DateTime created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid travel_request TravelRequest @relation(fields: [travel_request_id], references: [id], onDelete: Cascade) @@index([travel_request_id, sort_order], map: "idx_travel_request_segments_request_sort") @@map("travel_request_segments") } model TravelOrder { id String @id @default(uuid()) @db.Uuid source String @default("manual") @db.VarChar(32) external_order_no String employee_id String @db.Uuid department_id String? @db.Uuid linked_reimbursement_id String? @db.Uuid travel_request_id String? @db.Uuid occurred_at DateTime? @db.Date amount Decimal? @db.Decimal(14, 2) currency String @default("CNY") @db.VarChar(8) status String @default("imported") @db.VarChar(20) raw_payload Json? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid employee Employee @relation(fields: [employee_id], references: [id], onDelete: Restrict) department Department? @relation(fields: [department_id], references: [id], onDelete: SetNull) linked_reimbursement ReimbursementForm? @relation(fields: [linked_reimbursement_id], references: [id], onDelete: SetNull) travel_request TravelRequest? @relation(fields: [travel_request_id], references: [id], onDelete: SetNull) @@unique([source, external_order_no], map: "travel_orders_source_order_ux") @@index([employee_id, occurred_at], map: "idx_travel_orders_employee_occurred") @@index([linked_reimbursement_id]) @@index([travel_request_id]) @@map("travel_orders") } model NotificationChannel { id String @id @default(uuid()) @db.Uuid channel_type String @db.VarChar(32) name String enabled Boolean @default(true) config_json Json template_json Json? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid @@unique([channel_type, name], map: "notification_channels_type_name_ux") @@index([enabled, channel_type], map: "idx_notification_channels_enabled_type") @@map("notification_channels") } model WorkflowDefinition { id String @id @default(uuid()) @db.Uuid name String entity_type String definition_json Json version Int is_active Boolean @default(true) created_at DateTime @default(now()) created_by String? @db.Uuid workflow_instances WorkflowInstance[] @@unique([entity_type, version]) @@index([entity_type, is_active]) @@map("workflow_definitions") } model WorkflowInstance { id String @id @default(uuid()) @db.Uuid workflow_id String @db.Uuid entity_type String entity_id String @db.Uuid reimbursement_id String? @db.Uuid leave_request_id String? @db.Uuid purchase_request_id String? @db.Uuid travel_request_id String? @db.Uuid submitter_id String @db.Uuid current_status WorkflowInstanceStatus @default(pending) current_step_id String context_data Json started_at DateTime @default(now()) completed_at DateTime? created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid workflow_definition WorkflowDefinition @relation(fields: [workflow_id], references: [id], onDelete: Restrict) reimbursement_form ReimbursementForm? @relation(fields: [reimbursement_id], references: [id], onDelete: Cascade) leave_request LeaveRequest? @relation(fields: [leave_request_id], references: [id], onDelete: Cascade) purchase_request PurchaseRequest? @relation(fields: [purchase_request_id], references: [id], onDelete: Cascade) travel_request TravelRequest? @relation(fields: [travel_request_id], references: [id], onDelete: Cascade) submitter Employee @relation("WorkflowInstanceSubmitter", fields: [submitter_id], references: [id], onDelete: Restrict) department Department? @relation(fields: [context_department_id], references: [id], onDelete: SetNull) workflow_tasks WorkflowTask[] workflow_history WorkflowHistory[] /// Denormalized for fast filtering (must match context_data.department_id) context_department_id String? @db.Uuid @@index([workflow_id]) @@index([entity_type, entity_id]) @@index([reimbursement_id]) @@index([leave_request_id]) @@index([purchase_request_id]) @@index([travel_request_id]) @@index([current_status]) @@index([submitter_id]) @@index([context_department_id]) @@map("workflow_instances") } model WorkflowTask { id String @id @default(uuid()) @db.Uuid instance_id String @db.Uuid step_id String step_name String assignee_id String? @db.Uuid assignee_role RoleCode? status WorkflowTaskStatus @default(pending) outcome WorkflowTaskOutcome? comment String? due_date DateTime? created_at DateTime @default(now()) completed_at DateTime? completed_by String? @db.Uuid workflow_instance WorkflowInstance @relation(fields: [instance_id], references: [id], onDelete: Cascade) assignee_employee Employee? @relation("WorkflowTaskAssignee", fields: [assignee_id], references: [id], onDelete: SetNull) completer_employee Employee? @relation("WorkflowTaskCompleter", fields: [completed_by], references: [id], onDelete: SetNull) workflow_history WorkflowHistory[] @@index([instance_id]) @@index([status]) @@index([assignee_id]) @@index([assignee_role]) @@index([status, assignee_id, assignee_role], map: "idx_workflow_tasks_status_assignee") @@index([status, assignee_id, created_at], map: "idx_workflow_tasks_status_assignee_id_created_at") @@index([status, assignee_role, created_at], map: "idx_workflow_tasks_status_assignee_role_created_at") @@map("workflow_tasks") } model WorkflowHistory { id String @id @default(uuid()) @db.Uuid instance_id String @db.Uuid task_id String? @db.Uuid actor_id String @db.Uuid action String comment String? payload Json? created_at DateTime @default(now()) workflow_instance WorkflowInstance @relation(fields: [instance_id], references: [id], onDelete: Cascade) workflow_task WorkflowTask? @relation(fields: [task_id], references: [id], onDelete: SetNull) actor Employee @relation("WorkflowHistoryActor", fields: [actor_id], references: [id], onDelete: Restrict) @@index([instance_id, created_at]) @@index([task_id]) @@index([actor_id]) @@map("workflow_history") } model ApprovalRule { id String @id @default(uuid()) @db.Uuid name String min_amount Decimal @db.Decimal(14, 2) max_amount Decimal? @db.Decimal(14, 2) level1_role RoleCode level2_role RoleCode? level3_role RoleCode? enabled Boolean @default(true) priority Int created_at DateTime @default(now()) created_by String? @db.Uuid updated_at DateTime @updatedAt updated_by String? @db.Uuid @@index([enabled, priority]) @@map("approval_rules") } model ApprovalTransfer { id String @id @default(uuid()) @db.Uuid reimbursement_id String @db.Uuid from_approver_id String @db.Uuid to_approver_id String @db.Uuid reason String status ApprovalTransferStatus @default(active) created_at DateTime @default(now()) created_by String? @db.Uuid reimbursement_form ReimbursementForm @relation(fields: [reimbursement_id], references: [id], onDelete: Cascade) from_approver Employee @relation("TransferFrom", fields: [from_approver_id], references: [id], onDelete: Restrict) to_approver Employee @relation("TransferTo", fields: [to_approver_id], references: [id], onDelete: Restrict) @@index([reimbursement_id]) @@index([from_approver_id]) @@index([to_approver_id]) @@map("approval_transfers") }