feat(P20T3-1,P20T3-2,P20T3-3): 对账批次引擎与ERP摘要出站后端
这个提交包含在:
+116
@@ -0,0 +1,116 @@
|
||||
ALTER TYPE "DocumentCategoryEntityType" ADD VALUE IF NOT EXISTS 'travel_reconciliation_batch';
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "TravelReconciliationBatchStatus" AS ENUM ('draft', 'reconciled');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "TravelReconciliationItemKind" AS ENUM ('order', 'orphan_transaction');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "TravelReconciliationMatchStatus" AS ENUM (
|
||||
'matched',
|
||||
'amount_mismatch',
|
||||
'currency_mismatch',
|
||||
'missing_transaction',
|
||||
'duplicate_charge',
|
||||
'orphan_transaction',
|
||||
'reversed_after_payment'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "TravelReconciliationResolutionStatus" AS ENUM ('pending', 'confirmed');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "travel_reconciliation_batches" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"batch_no" TEXT NOT NULL,
|
||||
"legal_entity_id" UUID NOT NULL,
|
||||
"cost_center_id" UUID,
|
||||
"period_start" DATE NOT NULL,
|
||||
"period_end" DATE NOT NULL,
|
||||
"status" "TravelReconciliationBatchStatus" NOT NULL DEFAULT 'draft',
|
||||
"summary_json" JSONB NOT NULL,
|
||||
"confirmed_at" TIMESTAMP(3),
|
||||
"confirmed_by" UUID,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"created_by" UUID,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_by" UUID,
|
||||
CONSTRAINT "travel_reconciliation_batches_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "travel_reconciliation_batches_batch_no_key" ON "travel_reconciliation_batches"("batch_no");
|
||||
CREATE INDEX IF NOT EXISTS "idx_travel_reconciliation_batches_status_period" ON "travel_reconciliation_batches"("status", "period_start", "period_end");
|
||||
CREATE INDEX IF NOT EXISTS "idx_travel_reconciliation_batches_legal_cost_center" ON "travel_reconciliation_batches"("legal_entity_id", "cost_center_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_travel_reconciliation_batches_confirmed_by" ON "travel_reconciliation_batches"("confirmed_by");
|
||||
|
||||
ALTER TABLE "travel_reconciliation_batches"
|
||||
ADD CONSTRAINT "travel_reconciliation_batches_legal_entity_id_fkey"
|
||||
FOREIGN KEY ("legal_entity_id") REFERENCES "legal_entities"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "travel_reconciliation_batches"
|
||||
ADD CONSTRAINT "travel_reconciliation_batches_cost_center_id_fkey"
|
||||
FOREIGN KEY ("cost_center_id") REFERENCES "cost_centers"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "travel_reconciliation_batches"
|
||||
ADD CONSTRAINT "travel_reconciliation_batches_confirmed_by_fkey"
|
||||
FOREIGN KEY ("confirmed_by") REFERENCES "employees"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "travel_reconciliation_items" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"travel_reconciliation_batch_id" UUID NOT NULL,
|
||||
"item_kind" "TravelReconciliationItemKind" NOT NULL,
|
||||
"travel_request_id" UUID,
|
||||
"travel_order_id" UUID,
|
||||
"card_transaction_id" UUID,
|
||||
"match_status" "TravelReconciliationMatchStatus" NOT NULL,
|
||||
"resolution_status" "TravelReconciliationResolutionStatus" NOT NULL DEFAULT 'pending',
|
||||
"order_amount" DECIMAL(14,2),
|
||||
"transaction_amount" DECIMAL(14,2),
|
||||
"currency" VARCHAR(8) NOT NULL DEFAULT 'CNY',
|
||||
"anomaly_code" VARCHAR(32),
|
||||
"snapshot_json" JSONB NOT NULL,
|
||||
"confirmed_at" TIMESTAMP(3),
|
||||
"confirmed_by" UUID,
|
||||
"confirm_note" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "travel_reconciliation_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "idx_travel_reconciliation_items_batch_resolution" ON "travel_reconciliation_items"("travel_reconciliation_batch_id", "resolution_status");
|
||||
CREATE INDEX IF NOT EXISTS "idx_travel_reconciliation_items_request" ON "travel_reconciliation_items"("travel_request_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_travel_reconciliation_items_order" ON "travel_reconciliation_items"("travel_order_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_travel_reconciliation_items_transaction" ON "travel_reconciliation_items"("card_transaction_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_travel_reconciliation_items_confirmed_by" ON "travel_reconciliation_items"("confirmed_by");
|
||||
|
||||
ALTER TABLE "travel_reconciliation_items"
|
||||
ADD CONSTRAINT "travel_reconciliation_items_batch_id_fkey"
|
||||
FOREIGN KEY ("travel_reconciliation_batch_id") REFERENCES "travel_reconciliation_batches"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "travel_reconciliation_items"
|
||||
ADD CONSTRAINT "travel_reconciliation_items_travel_request_id_fkey"
|
||||
FOREIGN KEY ("travel_request_id") REFERENCES "travel_requests"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "travel_reconciliation_items"
|
||||
ADD CONSTRAINT "travel_reconciliation_items_travel_order_id_fkey"
|
||||
FOREIGN KEY ("travel_order_id") REFERENCES "travel_orders"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "travel_reconciliation_items"
|
||||
ADD CONSTRAINT "travel_reconciliation_items_card_transaction_id_fkey"
|
||||
FOREIGN KEY ("card_transaction_id") REFERENCES "card_transactions"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "travel_reconciliation_items"
|
||||
ADD CONSTRAINT "travel_reconciliation_items_confirmed_by_fkey"
|
||||
FOREIGN KEY ("confirmed_by") REFERENCES "employees"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
+120
-24
@@ -78,6 +78,31 @@ enum TravelTransportType {
|
||||
other
|
||||
}
|
||||
|
||||
enum TravelReconciliationBatchStatus {
|
||||
draft
|
||||
reconciled
|
||||
}
|
||||
|
||||
enum TravelReconciliationItemKind {
|
||||
order
|
||||
orphan_transaction
|
||||
}
|
||||
|
||||
enum TravelReconciliationMatchStatus {
|
||||
matched
|
||||
amount_mismatch
|
||||
currency_mismatch
|
||||
missing_transaction
|
||||
duplicate_charge
|
||||
orphan_transaction
|
||||
reversed_after_payment
|
||||
}
|
||||
|
||||
enum TravelReconciliationResolutionStatus {
|
||||
pending
|
||||
confirmed
|
||||
}
|
||||
|
||||
enum AssetCardStatus {
|
||||
in_stock
|
||||
in_use
|
||||
@@ -157,6 +182,7 @@ enum DocumentCategoryEntityType {
|
||||
leave
|
||||
purchase_request
|
||||
travel_request
|
||||
travel_reconciliation_batch
|
||||
asset_card
|
||||
general
|
||||
}
|
||||
@@ -302,11 +328,13 @@ model Employee {
|
||||
uploaded_invoices InvoicePool[] @relation("InvoiceUploader")
|
||||
overridden_invoices InvoicePool[] @relation("InvoiceOverrideBy")
|
||||
requested_invoice_verifications InvoiceVerificationRecord[] @relation("InvoiceVerificationRequester")
|
||||
travel_orders TravelOrder[] @relation("TravelOrderEmployee")
|
||||
paid_travel_orders TravelOrder[] @relation("TravelOrderPaidBy")
|
||||
card_authorizations CardAuthorization[]
|
||||
card_transactions CardTransaction[] @relation("CardTransactionEmployee")
|
||||
freeze_suggested_card_transactions CardTransaction[] @relation("CardTransactionFreezeSuggestedBy")
|
||||
travel_orders TravelOrder[] @relation("TravelOrderEmployee")
|
||||
paid_travel_orders TravelOrder[] @relation("TravelOrderPaidBy")
|
||||
confirmed_travel_reconciliation_batches TravelReconciliationBatch[] @relation("TravelReconciliationBatchConfirmedBy")
|
||||
card_authorizations CardAuthorization[]
|
||||
card_transactions CardTransaction[] @relation("CardTransactionEmployee")
|
||||
freeze_suggested_card_transactions CardTransaction[] @relation("CardTransactionFreezeSuggestedBy")
|
||||
confirmed_travel_reconciliation_items TravelReconciliationItem[] @relation("TravelReconciliationItemConfirmedBy")
|
||||
|
||||
workflow_instances_started WorkflowInstance[] @relation("WorkflowInstanceSubmitter")
|
||||
workflow_history_actions WorkflowHistory[] @relation("WorkflowHistoryActor")
|
||||
@@ -491,11 +519,12 @@ model LegalEntity {
|
||||
employees Employee[]
|
||||
holiday_calendars HolidayCalendar[] @relation("HolidayCalendarLegalEntity")
|
||||
suppliers Supplier[]
|
||||
purchase_requests PurchaseRequest[]
|
||||
travel_requests TravelRequest[]
|
||||
card_authorizations CardAuthorization[]
|
||||
asset_cards AssetCard[]
|
||||
budget_pools BudgetPool[] @relation("BudgetPoolLegalEntity")
|
||||
purchase_requests PurchaseRequest[]
|
||||
travel_requests TravelRequest[]
|
||||
travel_reconciliation_batches TravelReconciliationBatch[]
|
||||
card_authorizations CardAuthorization[]
|
||||
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")
|
||||
@@ -521,11 +550,12 @@ model CostCenter {
|
||||
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[]
|
||||
card_authorizations CardAuthorization[]
|
||||
asset_cards AssetCard[]
|
||||
budget_pools BudgetPool[] @relation("BudgetPoolCostCenter")
|
||||
purchase_requests PurchaseRequest[]
|
||||
travel_requests TravelRequest[]
|
||||
travel_reconciliation_batches TravelReconciliationBatch[]
|
||||
card_authorizations CardAuthorization[]
|
||||
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")
|
||||
@@ -1319,11 +1349,12 @@ model TravelRequest {
|
||||
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[]
|
||||
card_authorizations CardAuthorization[]
|
||||
card_transactions CardTransaction[]
|
||||
workflow_instances WorkflowInstance[]
|
||||
segments TravelRequestSegment[]
|
||||
travel_orders TravelOrder[]
|
||||
reconciliation_items TravelReconciliationItem[]
|
||||
card_authorizations CardAuthorization[]
|
||||
card_transactions CardTransaction[]
|
||||
workflow_instances WorkflowInstance[]
|
||||
|
||||
@@index([applicant_employee_id, start_date], map: "idx_travel_requests_applicant_start")
|
||||
@@index([submitter_id], map: "idx_travel_requests_submitter")
|
||||
@@ -1384,9 +1415,10 @@ model TravelOrder {
|
||||
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)
|
||||
paid_by_employee Employee? @relation("TravelOrderPaidBy", fields: [paid_by], references: [id], onDelete: SetNull)
|
||||
paid_by_employee Employee? @relation("TravelOrderPaidBy", fields: [paid_by], references: [id], onDelete: SetNull)
|
||||
invoice_pool_items InvoicePool[]
|
||||
card_transactions CardTransaction[]
|
||||
reconciliation_items TravelReconciliationItem[]
|
||||
|
||||
@@unique([source, external_order_no], map: "travel_orders_source_order_ux")
|
||||
@@index([employee_id, occurred_at], map: "idx_travel_orders_employee_occurred")
|
||||
@@ -1465,9 +1497,10 @@ model CardTransaction {
|
||||
card_authorization CardAuthorization? @relation(fields: [card_authorization_id], references: [id], onDelete: SetNull)
|
||||
employee Employee @relation("CardTransactionEmployee", fields: [employee_id], references: [id], onDelete: Restrict)
|
||||
department Department? @relation(fields: [department_id], references: [id], onDelete: SetNull)
|
||||
travel_request TravelRequest @relation(fields: [travel_request_id], references: [id], onDelete: Cascade)
|
||||
travel_order TravelOrder? @relation(fields: [travel_order_id], references: [id], onDelete: SetNull)
|
||||
freeze_suggested_by_employee Employee? @relation("CardTransactionFreezeSuggestedBy", fields: [freeze_suggested_by], references: [id], onDelete: SetNull)
|
||||
travel_request TravelRequest @relation(fields: [travel_request_id], references: [id], onDelete: Cascade)
|
||||
travel_order TravelOrder? @relation(fields: [travel_order_id], references: [id], onDelete: SetNull)
|
||||
freeze_suggested_by_employee Employee? @relation("CardTransactionFreezeSuggestedBy", fields: [freeze_suggested_by], references: [id], onDelete: SetNull)
|
||||
reconciliation_items TravelReconciliationItem[]
|
||||
|
||||
@@unique([source, external_txn_id], map: "card_transactions_source_txn_ux")
|
||||
@@index([card_authorization_id], map: "idx_card_transactions_authorization")
|
||||
@@ -1479,6 +1512,69 @@ model CardTransaction {
|
||||
@@map("card_transactions")
|
||||
}
|
||||
|
||||
model TravelReconciliationBatch {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
batch_no String @unique
|
||||
legal_entity_id String @db.Uuid
|
||||
cost_center_id String? @db.Uuid
|
||||
period_start DateTime @db.Date
|
||||
period_end DateTime @db.Date
|
||||
status TravelReconciliationBatchStatus @default(draft)
|
||||
summary_json Json
|
||||
confirmed_at DateTime?
|
||||
confirmed_by String? @db.Uuid
|
||||
|
||||
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)
|
||||
cost_center CostCenter? @relation(fields: [cost_center_id], references: [id], onDelete: SetNull)
|
||||
confirmed_by_employee Employee? @relation("TravelReconciliationBatchConfirmedBy", fields: [confirmed_by], references: [id], onDelete: SetNull)
|
||||
items TravelReconciliationItem[]
|
||||
|
||||
@@index([status, period_start, period_end], map: "idx_travel_reconciliation_batches_status_period")
|
||||
@@index([legal_entity_id, cost_center_id], map: "idx_travel_reconciliation_batches_legal_cost_center")
|
||||
@@index([confirmed_by], map: "idx_travel_reconciliation_batches_confirmed_by")
|
||||
@@map("travel_reconciliation_batches")
|
||||
}
|
||||
|
||||
model TravelReconciliationItem {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
travel_reconciliation_batch_id String @db.Uuid
|
||||
item_kind TravelReconciliationItemKind
|
||||
travel_request_id String? @db.Uuid
|
||||
travel_order_id String? @db.Uuid
|
||||
card_transaction_id String? @db.Uuid
|
||||
match_status TravelReconciliationMatchStatus
|
||||
resolution_status TravelReconciliationResolutionStatus @default(pending)
|
||||
order_amount Decimal? @db.Decimal(14, 2)
|
||||
transaction_amount Decimal? @db.Decimal(14, 2)
|
||||
currency String @default("CNY") @db.VarChar(8)
|
||||
anomaly_code String? @db.VarChar(32)
|
||||
snapshot_json Json
|
||||
confirmed_at DateTime?
|
||||
confirmed_by String? @db.Uuid
|
||||
confirm_note String?
|
||||
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
batch TravelReconciliationBatch @relation(fields: [travel_reconciliation_batch_id], references: [id], onDelete: Cascade)
|
||||
travel_request TravelRequest? @relation(fields: [travel_request_id], references: [id], onDelete: SetNull)
|
||||
travel_order TravelOrder? @relation(fields: [travel_order_id], references: [id], onDelete: SetNull)
|
||||
card_transaction CardTransaction? @relation(fields: [card_transaction_id], references: [id], onDelete: SetNull)
|
||||
confirmed_by_employee Employee? @relation("TravelReconciliationItemConfirmedBy", fields: [confirmed_by], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([travel_reconciliation_batch_id, resolution_status], map: "idx_travel_reconciliation_items_batch_resolution")
|
||||
@@index([travel_request_id], map: "idx_travel_reconciliation_items_request")
|
||||
@@index([travel_order_id], map: "idx_travel_reconciliation_items_order")
|
||||
@@index([card_transaction_id], map: "idx_travel_reconciliation_items_transaction")
|
||||
@@index([confirmed_by], map: "idx_travel_reconciliation_items_confirmed_by")
|
||||
@@map("travel_reconciliation_items")
|
||||
}
|
||||
|
||||
model NotificationChannel {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
channel_type String @db.VarChar(32)
|
||||
|
||||
在新工单中引用
屏蔽一个用户