feat(P15B-1,P15B-6): 完成请假V1闭环

这个提交包含在:
2026-03-07 08:11:24 +08:00
父节点 03c5a78485
当前提交 4ba311199d
共修改 23 个文件,包含 3202 行新增和 308 行删除
@@ -0,0 +1,121 @@
/*
Warnings:
- Added the required column `leave_type_id` to the `leave_requests` table without a default value. This is not possible if the table is not empty.
- Added the required column `requested_days` to the `leave_requests` table without a default value. This is not possible if the table is not empty.
*/
-- CreateEnum
CREATE TYPE "LeaveDaySession" AS ENUM ('am', 'pm');
-- CreateEnum
CREATE TYPE "LeaveLedgerEntryType" AS ENUM ('grant', 'reserve', 'consume', 'release', 'adjust');
-- AlterTable
ALTER TABLE "leave_requests" ADD COLUMN "attachment_mime_type" TEXT,
ADD COLUMN "attachment_original_name" TEXT,
ADD COLUMN "attachment_storage_path" TEXT,
ADD COLUMN "end_session" "LeaveDaySession" NOT NULL DEFAULT 'pm',
ADD COLUMN "leave_type_id" UUID NOT NULL,
ADD COLUMN "requested_days" DECIMAL(8,1) NOT NULL,
ADD COLUMN "start_session" "LeaveDaySession" NOT NULL DEFAULT 'am';
-- CreateTable
CREATE TABLE "leave_types" (
"id" UUID NOT NULL,
"code" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"requires_attachment" BOOLEAN NOT NULL DEFAULT false,
"attachment_mime_types" JSONB,
"sort_order" INTEGER NOT NULL DEFAULT 100,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"created_by" UUID,
"updated_at" TIMESTAMP(3) NOT NULL,
"updated_by" UUID,
CONSTRAINT "leave_types_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "leave_policies" (
"id" UUID NOT NULL,
"leave_type_id" UUID NOT NULL,
"annual_quota_days" DECIMAL(8,1) NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"description" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"created_by" UUID,
"updated_at" TIMESTAMP(3) NOT NULL,
"updated_by" UUID,
CONSTRAINT "leave_policies_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "leave_ledgers" (
"id" UUID NOT NULL,
"employee_id" UUID NOT NULL,
"leave_type_id" UUID NOT NULL,
"leave_policy_id" UUID,
"leave_request_id" UUID,
"entry_type" "LeaveLedgerEntryType" NOT NULL,
"days" DECIMAL(8,1) NOT NULL,
"period_start" DATE,
"period_end" DATE,
"note" TEXT,
"payload" JSONB,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"created_by" UUID,
CONSTRAINT "leave_ledgers_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "leave_types_code_key" ON "leave_types"("code");
-- CreateIndex
CREATE INDEX "idx_leave_types_enabled_sort_order" ON "leave_types"("enabled", "sort_order");
-- CreateIndex
CREATE UNIQUE INDEX "leave_policies_leave_type_id_key" ON "leave_policies"("leave_type_id");
-- CreateIndex
CREATE INDEX "idx_leave_policies_enabled" ON "leave_policies"("enabled");
-- CreateIndex
CREATE INDEX "idx_leave_ledger_employee_type_created_at" ON "leave_ledgers"("employee_id", "leave_type_id", "created_at");
-- CreateIndex
CREATE INDEX "leave_ledgers_leave_request_id_idx" ON "leave_ledgers"("leave_request_id");
-- CreateIndex
CREATE INDEX "idx_leave_ledger_period" ON "leave_ledgers"("period_start", "period_end");
-- CreateIndex
CREATE INDEX "idx_leave_ledger_entry_employee" ON "leave_ledgers"("entry_type", "employee_id");
-- CreateIndex
CREATE INDEX "leave_requests_leave_type_id_idx" ON "leave_requests"("leave_type_id");
-- CreateIndex
CREATE INDEX "idx_leave_owner_date_range" ON "leave_requests"("owner_id", "start_date", "end_date");
-- AddForeignKey
ALTER TABLE "leave_policies" ADD CONSTRAINT "leave_policies_leave_type_id_fkey" FOREIGN KEY ("leave_type_id") REFERENCES "leave_types"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "leave_requests" ADD CONSTRAINT "leave_requests_leave_type_id_fkey" FOREIGN KEY ("leave_type_id") REFERENCES "leave_types"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "leave_ledgers" ADD CONSTRAINT "leave_ledgers_employee_id_fkey" FOREIGN KEY ("employee_id") REFERENCES "employees"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "leave_ledgers" ADD CONSTRAINT "leave_ledgers_leave_type_id_fkey" FOREIGN KEY ("leave_type_id") REFERENCES "leave_types"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "leave_ledgers" ADD CONSTRAINT "leave_ledgers_leave_policy_id_fkey" FOREIGN KEY ("leave_policy_id") REFERENCES "leave_policies"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "leave_ledgers" ADD CONSTRAINT "leave_ledgers_leave_request_id_fkey" FOREIGN KEY ("leave_request_id") REFERENCES "leave_requests"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+205 -110
查看文件
@@ -44,6 +44,19 @@ enum LeaveRequestStatus {
cancelled
}
enum LeaveDaySession {
am
pm
}
enum LeaveLedgerEntryType {
grant
reserve
consume
release
adjust
}
enum InvoicePoolStatus {
pending_ocr
ocr_failed
@@ -159,13 +172,13 @@ model Employee {
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)
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?
archived_at DateTime?
created_at DateTime @default(now())
created_by String? @db.Uuid
@@ -182,19 +195,20 @@ model Employee {
delegation_grants_as_delegate DelegationGrant[] @relation("DelegationDelegate")
auth_sessions AuthSession[]
audit_logs AuditLog[] @relation("AuditActor")
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_invoices InvoicePool[] @relation("InvoiceOwner")
uploaded_invoices InvoicePool[] @relation("InvoiceUploader")
overridden_invoices InvoicePool[] @relation("InvoiceOverrideBy")
owned_invoices InvoicePool[] @relation("InvoiceOwner")
uploaded_invoices InvoicePool[] @relation("InvoiceUploader")
overridden_invoices InvoicePool[] @relation("InvoiceOverrideBy")
requested_invoice_verifications InvoiceVerificationRecord[] @relation("InvoiceVerificationRequester")
travel_orders TravelOrder[]
travel_orders TravelOrder[]
workflow_instances_started WorkflowInstance[] @relation("WorkflowInstanceSubmitter")
workflow_history_actions WorkflowHistory[] @relation("WorkflowHistoryActor")
@@ -302,15 +316,15 @@ model AuditLog {
}
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)
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
target_type String? @db.VarChar(64)
target_id String? @db.Uuid
read_at DateTime?
created_at DateTime @default(now())
created_at DateTime @default(now())
recipient Employee @relation(fields: [recipient_employee_id], references: [id], onDelete: Cascade)
@@ -383,10 +397,10 @@ model ReimbursementForm {
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[]
items ReimbursementItem[]
invoice_pool_items InvoicePool[]
workflow_instances WorkflowInstance[]
approval_transfers ApprovalTransfer[]
budget_ledger_entries BudgetLedgerEntry[]
travel_orders TravelOrder[]
@@ -424,32 +438,113 @@ model ReimbursementItem {
@@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)
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
owner Employee @relation(fields: [owner_id], references: [id], onDelete: Restrict)
department Department @relation(fields: [department_id], references: [id], onDelete: Restrict)
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
@@ -484,15 +579,15 @@ model InvoicePool {
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_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)
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?
@@ -517,9 +612,9 @@ model InvoicePool {
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)
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])
@@ -534,27 +629,27 @@ model InvoicePool {
}
model InvoiceVerificationRecord {
id String @id @default(uuid()) @db.Uuid
invoice_id String @db.Uuid
provider String @db.VarChar(32)
status String @db.VarChar(16)
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
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)
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")
@@ -565,16 +660,16 @@ model InvoiceVerificationRecord {
}
model BudgetPool {
id String @id @default(uuid()) @db.Uuid
code String @unique
id String @id @default(uuid()) @db.Uuid
code String @unique
name String
department_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)
department_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())
@@ -609,13 +704,13 @@ model BudgetLedgerEntry {
}
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)
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)
note String?
version Int @default(1)
created_at DateTime @default(now())
created_by String? @db.Uuid
@@ -627,16 +722,16 @@ model FeatureFlag {
}
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?
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())
@@ -650,16 +745,16 @@ model IntegrationOutbox {
}
model TravelOrder {
id String @id @default(uuid()) @db.Uuid
source String @default("manual") @db.VarChar(32)
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
employee_id String @db.Uuid
department_id String? @db.Uuid
linked_reimbursement_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)
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())
@@ -678,11 +773,11 @@ model TravelOrder {
}
model NotificationChannel {
id String @id @default(uuid()) @db.Uuid
channel_type String @db.VarChar(32)
name String
enabled Boolean @default(true)
config_json Json
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())
@@ -714,18 +809,18 @@ model WorkflowDefinition {
}
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?
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