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 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 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[] workflow_instances WorkflowInstance[] @@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 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) 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[] owned_invoices InvoicePool[] @relation("InvoiceOwner") uploaded_invoices InvoicePool[] @relation("InvoiceUploader") overridden_invoices InvoicePool[] @relation("InvoiceOverrideBy") 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([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 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[] @@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 LeaveRequest { id String @id @default(uuid()) @db.Uuid owner_id String @db.Uuid department_id String @db.Uuid start_date DateTime @db.Date end_date DateTime @db.Date reason 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) workflow_instances WorkflowInstance[] @@index([owner_id]) @@index([department_id]) @@index([status]) @@index([owner_id, status, updated_at], map: "idx_leave_owner_status_updated_at") @@map("leave_requests") } 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) @@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 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 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) 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([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") }