feat(P17A-1,P17A-2): 新增采购模型权限与系统种子

这个提交包含在:
2026-03-07 20:59:28 +08:00
父节点 9de1169d78
当前提交 1cd4290b4d
共修改 8 个文件,包含 354 行新增和 18 行删除
+21 -1
查看文件
@@ -16,6 +16,12 @@ const LEAVE_SELF_SERVICE_PERMISSIONS = [
"leave.submit", "leave.submit",
] as const; ] as const;
const PROCUREMENT_SELF_SERVICE_PERMISSIONS = [
"procurement.create",
"procurement.view_own",
"procurement.submit",
] as const;
const MASTERDATA_VIEW_PERMISSIONS = ["masterdata.view"] as const; const MASTERDATA_VIEW_PERMISSIONS = ["masterdata.view"] as const;
const MASTERDATA_MANAGE_PERMISSIONS = ["masterdata.manage"] as const; const MASTERDATA_MANAGE_PERMISSIONS = ["masterdata.manage"] as const;
@@ -25,25 +31,34 @@ const EMPLOYEE_BASE_PERMISSIONS = [
] as const; ] as const;
const ROLE_PERMISSIONS: Record<string, readonly string[]> = { const ROLE_PERMISSIONS: Record<string, readonly string[]> = {
employee: EMPLOYEE_BASE_PERMISSIONS, employee: [
...EMPLOYEE_BASE_PERMISSIONS,
...PROCUREMENT_SELF_SERVICE_PERMISSIONS,
],
manager: [ manager: [
...EMPLOYEE_BASE_PERMISSIONS, ...EMPLOYEE_BASE_PERMISSIONS,
...PROCUREMENT_SELF_SERVICE_PERMISSIONS,
"expense.approve", "expense.approve",
"expense.transfer", "expense.transfer",
"expense.view_all", "expense.view_all",
"leave.approve", "leave.approve",
"procurement.approve",
"procurement.view_all",
"report.dashboard.view", "report.dashboard.view",
"budget.view", "budget.view",
"budget.manage", "budget.manage",
], ],
ceo: [ ceo: [
...EMPLOYEE_BASE_PERMISSIONS, ...EMPLOYEE_BASE_PERMISSIONS,
...PROCUREMENT_SELF_SERVICE_PERMISSIONS,
...MASTERDATA_VIEW_PERMISSIONS, ...MASTERDATA_VIEW_PERMISSIONS,
"expense.approve", "expense.approve",
"expense.transfer", "expense.transfer",
"expense.view_all", "expense.view_all",
"leave.approve", "leave.approve",
"leave.view_all", "leave.view_all",
"procurement.approve",
"procurement.view_all",
"report.dashboard.view", "report.dashboard.view",
"budget.view", "budget.view",
], ],
@@ -57,6 +72,7 @@ const ROLE_PERMISSIONS: Record<string, readonly string[]> = {
"expense.approve", "expense.approve",
"expense.finance.pay", "expense.finance.pay",
"expense.view_all", "expense.view_all",
"procurement.view_all",
], ],
finance_mgr: [ finance_mgr: [
...EMPLOYEE_BASE_PERMISSIONS, ...EMPLOYEE_BASE_PERMISSIONS,
@@ -72,12 +88,16 @@ const ROLE_PERMISSIONS: Record<string, readonly string[]> = {
"expense.finance.pay", "expense.finance.pay",
"expense.view_all", "expense.view_all",
"expense.finance.override_duplicate", "expense.finance.override_duplicate",
"procurement.view_all",
"procurement.manage",
"system.settings", "system.settings",
"system.audit.view", "system.audit.view",
], ],
administration: [ administration: [
"expense.proxy_submit", "expense.proxy_submit",
...EMPLOYEE_BASE_PERMISSIONS, ...EMPLOYEE_BASE_PERMISSIONS,
"procurement.view_all",
"procurement.manage",
], ],
hr: [ hr: [
...LEAVE_SELF_SERVICE_PERMISSIONS, ...LEAVE_SELF_SERVICE_PERMISSIONS,
@@ -0,0 +1,135 @@
-- CreateEnum
CREATE TYPE "PurchaseRequestStatus" AS ENUM ('draft', 'reviewing', 'approved', 'rejected', 'cancelled');
-- CreateTable
CREATE TABLE "purchase_requests" (
"id" UUID NOT NULL,
"request_no" TEXT,
"applicant_id" UUID NOT NULL,
"submitter_id" UUID,
"department_id" UUID NOT NULL,
"legal_entity_id" UUID,
"cost_center_id" UUID,
"supplier_id" UUID,
"document_category_id" UUID,
"budget_pool_id" UUID,
"title" TEXT NOT NULL DEFAULT '',
"reason" TEXT,
"currency_code" VARCHAR(8) NOT NULL DEFAULT 'CNY',
"total_amount" DECIMAL(14,2) NOT NULL DEFAULT 0,
"expected_delivery_date" DATE,
"status" "PurchaseRequestStatus" NOT NULL DEFAULT 'draft',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"created_by" UUID,
"updated_at" TIMESTAMP(3) NOT NULL,
"updated_by" UUID,
CONSTRAINT "purchase_requests_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "purchase_request_items" (
"id" UUID NOT NULL,
"purchase_request_id" UUID NOT NULL,
"description" TEXT NOT NULL,
"specification" TEXT,
"quantity" DECIMAL(14,2) NOT NULL,
"unit_price" DECIMAL(14,2) NOT NULL,
"line_amount" DECIMAL(14,2) NOT NULL,
"is_asset_candidate" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"created_by" UUID,
"updated_at" TIMESTAMP(3) NOT NULL,
"updated_by" UUID,
CONSTRAINT "purchase_request_items_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "purchase_attachments" (
"id" UUID NOT NULL,
"purchase_request_id" UUID NOT NULL,
"storage_path" TEXT NOT NULL,
"original_name" TEXT NOT NULL,
"mime_type" TEXT NOT NULL,
"file_size" INTEGER NOT NULL,
"uploaded_by" UUID NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "purchase_attachments_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "purchase_requests_request_no_key" ON "purchase_requests"("request_no");
-- CreateIndex
CREATE INDEX "idx_purchase_requests_applicant_status_updated" ON "purchase_requests"("applicant_id", "status", "updated_at");
-- CreateIndex
CREATE INDEX "idx_purchase_requests_submitter" ON "purchase_requests"("submitter_id");
-- CreateIndex
CREATE INDEX "idx_purchase_requests_department" ON "purchase_requests"("department_id");
-- CreateIndex
CREATE INDEX "idx_purchase_requests_legal_entity" ON "purchase_requests"("legal_entity_id");
-- CreateIndex
CREATE INDEX "idx_purchase_requests_cost_center" ON "purchase_requests"("cost_center_id");
-- CreateIndex
CREATE INDEX "idx_purchase_requests_supplier" ON "purchase_requests"("supplier_id");
-- CreateIndex
CREATE INDEX "idx_purchase_requests_document_category" ON "purchase_requests"("document_category_id");
-- CreateIndex
CREATE INDEX "idx_purchase_requests_budget_pool" ON "purchase_requests"("budget_pool_id");
-- CreateIndex
CREATE INDEX "idx_purchase_requests_status" ON "purchase_requests"("status");
-- CreateIndex
CREATE INDEX "idx_purchase_request_items_request" ON "purchase_request_items"("purchase_request_id");
-- CreateIndex
CREATE INDEX "idx_purchase_attachments_request_created" ON "purchase_attachments"("purchase_request_id", "created_at");
-- CreateIndex
CREATE INDEX "idx_purchase_attachments_uploader" ON "purchase_attachments"("uploaded_by");
-- AddForeignKey
ALTER TABLE "purchase_requests" ADD CONSTRAINT "purchase_requests_applicant_id_fkey" FOREIGN KEY ("applicant_id") REFERENCES "employees"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "purchase_requests" ADD CONSTRAINT "purchase_requests_submitter_id_fkey" FOREIGN KEY ("submitter_id") REFERENCES "employees"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "purchase_requests" ADD CONSTRAINT "purchase_requests_department_id_fkey" FOREIGN KEY ("department_id") REFERENCES "departments"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "purchase_requests" ADD CONSTRAINT "purchase_requests_legal_entity_id_fkey" FOREIGN KEY ("legal_entity_id") REFERENCES "legal_entities"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "purchase_requests" ADD CONSTRAINT "purchase_requests_cost_center_id_fkey" FOREIGN KEY ("cost_center_id") REFERENCES "cost_centers"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "purchase_requests" ADD CONSTRAINT "purchase_requests_supplier_id_fkey" FOREIGN KEY ("supplier_id") REFERENCES "suppliers"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "purchase_requests" ADD CONSTRAINT "purchase_requests_document_category_id_fkey" FOREIGN KEY ("document_category_id") REFERENCES "document_categories"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "purchase_requests" ADD CONSTRAINT "purchase_requests_budget_pool_id_fkey" FOREIGN KEY ("budget_pool_id") REFERENCES "budget_pools"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "purchase_request_items" ADD CONSTRAINT "purchase_request_items_purchase_request_id_fkey" FOREIGN KEY ("purchase_request_id") REFERENCES "purchase_requests"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "purchase_attachments" ADD CONSTRAINT "purchase_attachments_purchase_request_id_fkey" FOREIGN KEY ("purchase_request_id") REFERENCES "purchase_requests"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "purchase_attachments" ADD CONSTRAINT "purchase_attachments_uploaded_by_fkey" FOREIGN KEY ("uploaded_by") REFERENCES "employees"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "workflow_instances" ADD CONSTRAINT "workflow_instances_purchase_request_id_fkey" FOREIGN KEY ("purchase_request_id") REFERENCES "purchase_requests"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+114 -8
查看文件
@@ -44,6 +44,14 @@ enum LeaveRequestStatus {
cancelled cancelled
} }
enum PurchaseRequestStatus {
draft
reviewing
approved
rejected
cancelled
}
enum LeaveDaySession { enum LeaveDaySession {
am am
pm pm
@@ -162,6 +170,7 @@ model Department {
employees Employee[] employees Employee[]
reimbursement_forms ReimbursementForm[] reimbursement_forms ReimbursementForm[]
leave_requests LeaveRequest[] leave_requests LeaveRequest[]
purchase_requests PurchaseRequest[]
workflow_instances WorkflowInstance[] workflow_instances WorkflowInstance[]
budget_pools BudgetPool[] budget_pools BudgetPool[]
travel_orders TravelOrder[] travel_orders TravelOrder[]
@@ -217,9 +226,12 @@ model Employee {
submitted_reimbursements ReimbursementForm[] @relation("ReimbursementSubmitter") submitted_reimbursements ReimbursementForm[] @relation("ReimbursementSubmitter")
owned_reimbursements ReimbursementForm[] @relation("ReimbursementOwner") owned_reimbursements ReimbursementForm[] @relation("ReimbursementOwner")
paid_reimbursements ReimbursementForm[] @relation("ReimbursementPaidBy") paid_reimbursements ReimbursementForm[] @relation("ReimbursementPaidBy")
leave_requests LeaveRequest[] leave_requests LeaveRequest[]
leave_ledgers LeaveLedger[] leave_ledgers LeaveLedger[]
owned_cost_centers CostCenter[] @relation("CostCenterOwner") owned_cost_centers CostCenter[] @relation("CostCenterOwner")
purchase_requests_applicant PurchaseRequest[] @relation("PurchaseRequestApplicant")
purchase_requests_submitted PurchaseRequest[] @relation("PurchaseRequestSubmitter")
purchase_attachments_uploaded PurchaseAttachment[] @relation("PurchaseAttachmentUploader")
owned_invoices InvoicePool[] @relation("InvoiceOwner") owned_invoices InvoicePool[] @relation("InvoiceOwner")
uploaded_invoices InvoicePool[] @relation("InvoiceUploader") uploaded_invoices InvoicePool[] @relation("InvoiceUploader")
@@ -410,6 +422,7 @@ model LegalEntity {
employees Employee[] employees Employee[]
holiday_calendars HolidayCalendar[] @relation("HolidayCalendarLegalEntity") holiday_calendars HolidayCalendar[] @relation("HolidayCalendarLegalEntity")
suppliers Supplier[] suppliers Supplier[]
purchase_requests PurchaseRequest[]
budget_pools BudgetPool[] @relation("BudgetPoolLegalEntity") budget_pools BudgetPool[] @relation("BudgetPoolLegalEntity")
@@index([enabled, archived_at], map: "idx_legal_entities_status") @@index([enabled, archived_at], map: "idx_legal_entities_status")
@@ -437,6 +450,7 @@ model CostCenter {
children CostCenter[] @relation("CostCenterToParent") children CostCenter[] @relation("CostCenterToParent")
owner_employee Employee? @relation("CostCenterOwner", fields: [owner_employee_id], references: [id], onDelete: SetNull) owner_employee Employee? @relation("CostCenterOwner", fields: [owner_employee_id], references: [id], onDelete: SetNull)
budget_pools BudgetPool[] @relation("BudgetPoolCostCenter") budget_pools BudgetPool[] @relation("BudgetPoolCostCenter")
purchase_requests PurchaseRequest[]
@@unique([legal_entity_id, code], map: "legal_entity_cost_center_code_ux") @@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([legal_entity_id, enabled, archived_at], map: "idx_cost_centers_legal_entity_status")
@@ -462,6 +476,8 @@ model DocumentCategory {
updated_at DateTime @updatedAt updated_at DateTime @updatedAt
updated_by String? @db.Uuid updated_by String? @db.Uuid
purchase_requests PurchaseRequest[]
@@index([entity_type, enabled, archived_at], map: "idx_document_categories_entity_status") @@index([entity_type, enabled, archived_at], map: "idx_document_categories_entity_status")
@@map("document_categories") @@map("document_categories")
} }
@@ -526,13 +542,101 @@ model Supplier {
updated_at DateTime @updatedAt updated_at DateTime @updatedAt
updated_by String? @db.Uuid updated_by String? @db.Uuid
legal_entity LegalEntity? @relation(fields: [legal_entity_id], references: [id], onDelete: SetNull) 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([legal_entity_id, enabled, archived_at], map: "idx_suppliers_legal_entity_status")
@@index([name], map: "idx_suppliers_name") @@index([name], map: "idx_suppliers_name")
@@map("suppliers") @@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[]
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 ReimbursementForm { model ReimbursementForm {
id String @id @default(uuid()) @db.Uuid id String @id @default(uuid()) @db.Uuid
form_number String? @unique form_number String? @unique
@@ -841,10 +945,11 @@ model BudgetPool {
updated_at DateTime @updatedAt updated_at DateTime @updatedAt
updated_by String? @db.Uuid updated_by String? @db.Uuid
department Department? @relation(fields: [department_id], references: [id], onDelete: SetNull) department Department? @relation(fields: [department_id], references: [id], onDelete: SetNull)
legal_entity LegalEntity? @relation("BudgetPoolLegalEntity", fields: [legal_entity_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) cost_center CostCenter? @relation("BudgetPoolCostCenter", fields: [cost_center_id], references: [id], onDelete: SetNull)
ledger_entries BudgetLedgerEntry[] ledger_entries BudgetLedgerEntry[]
purchase_requests PurchaseRequest[]
@@index([department_id, status], map: "idx_budget_pools_department_status") @@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([legal_entity_id, cost_center_id, status], map: "idx_budget_pools_scope_status")
@@ -1001,6 +1106,7 @@ model WorkflowInstance {
workflow_definition WorkflowDefinition @relation(fields: [workflow_id], references: [id], onDelete: Restrict) workflow_definition WorkflowDefinition @relation(fields: [workflow_id], references: [id], onDelete: Restrict)
reimbursement_form ReimbursementForm? @relation(fields: [reimbursement_id], references: [id], onDelete: Cascade) reimbursement_form ReimbursementForm? @relation(fields: [reimbursement_id], references: [id], onDelete: Cascade)
leave_request LeaveRequest? @relation(fields: [leave_request_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)
submitter Employee @relation("WorkflowInstanceSubmitter", fields: [submitter_id], references: [id], onDelete: Restrict) submitter Employee @relation("WorkflowInstanceSubmitter", fields: [submitter_id], references: [id], onDelete: Restrict)
department Department? @relation(fields: [context_department_id], references: [id], onDelete: SetNull) department Department? @relation(fields: [context_department_id], references: [id], onDelete: SetNull)
+21 -1
查看文件
@@ -16,6 +16,12 @@ const LEAVE_SELF_SERVICE_PERMISSIONS = [
"leave.submit", "leave.submit",
]; ];
const PROCUREMENT_SELF_SERVICE_PERMISSIONS = [
"procurement.create",
"procurement.view_own",
"procurement.submit",
];
const MASTERDATA_VIEW_PERMISSIONS = ["masterdata.view"]; const MASTERDATA_VIEW_PERMISSIONS = ["masterdata.view"];
const MASTERDATA_MANAGE_PERMISSIONS = ["masterdata.manage"]; const MASTERDATA_MANAGE_PERMISSIONS = ["masterdata.manage"];
@@ -74,25 +80,34 @@ const ENTITY_PERMISSION_MAP = {
}; };
const ROLE_PERMISSIONS = { const ROLE_PERMISSIONS = {
employee: EMPLOYEE_BASE_PERMISSIONS, employee: [
...EMPLOYEE_BASE_PERMISSIONS,
...PROCUREMENT_SELF_SERVICE_PERMISSIONS,
],
manager: [ manager: [
...EMPLOYEE_BASE_PERMISSIONS, ...EMPLOYEE_BASE_PERMISSIONS,
...PROCUREMENT_SELF_SERVICE_PERMISSIONS,
"expense.approve", "expense.approve",
"expense.transfer", "expense.transfer",
"expense.view_all", "expense.view_all",
"leave.approve", "leave.approve",
"procurement.approve",
"procurement.view_all",
"report.dashboard.view", "report.dashboard.view",
"budget.view", "budget.view",
"budget.manage", "budget.manage",
], ],
ceo: [ ceo: [
...EMPLOYEE_BASE_PERMISSIONS, ...EMPLOYEE_BASE_PERMISSIONS,
...PROCUREMENT_SELF_SERVICE_PERMISSIONS,
...MASTERDATA_VIEW_PERMISSIONS, ...MASTERDATA_VIEW_PERMISSIONS,
"expense.approve", "expense.approve",
"expense.transfer", "expense.transfer",
"expense.view_all", "expense.view_all",
"leave.approve", "leave.approve",
"leave.view_all", "leave.view_all",
"procurement.approve",
"procurement.view_all",
"report.dashboard.view", "report.dashboard.view",
"budget.view", "budget.view",
], ],
@@ -106,6 +121,7 @@ const ROLE_PERMISSIONS = {
"expense.approve", "expense.approve",
"expense.finance.pay", "expense.finance.pay",
"expense.view_all", "expense.view_all",
"procurement.view_all",
], ],
finance_mgr: [ finance_mgr: [
...EMPLOYEE_BASE_PERMISSIONS, ...EMPLOYEE_BASE_PERMISSIONS,
@@ -121,12 +137,16 @@ const ROLE_PERMISSIONS = {
"expense.finance.pay", "expense.finance.pay",
"expense.view_all", "expense.view_all",
"expense.finance.override_duplicate", "expense.finance.override_duplicate",
"procurement.view_all",
"procurement.manage",
"system.settings", "system.settings",
"system.audit.view", "system.audit.view",
], ],
administration: [ administration: [
"expense.proxy_submit", "expense.proxy_submit",
...EMPLOYEE_BASE_PERMISSIONS, ...EMPLOYEE_BASE_PERMISSIONS,
"procurement.view_all",
"procurement.manage",
], ],
hr: [ hr: [
...LEAVE_SELF_SERVICE_PERMISSIONS, ...LEAVE_SELF_SERVICE_PERMISSIONS,
+48 -2
查看文件
@@ -105,6 +105,18 @@ const DEFAULT_LEAVE_WORKFLOW_DEFINITION = {
], ],
}; };
const DEFAULT_PURCHASE_WORKFLOW_DEFINITION = {
steps: [
{
id: "approval_1_manager",
name: "直属经理审批",
type: "approval",
assignee_rule: {
type: "department_manager",
},
},
],
};
const DEFAULT_DOCUMENT_CATEGORIES = [ const DEFAULT_DOCUMENT_CATEGORIES = [
{ {
@@ -128,8 +140,8 @@ const DEFAULT_DOCUMENT_CATEGORIES = [
enabled: true, enabled: true,
}, },
{ {
code: "purchase_general", code: "PR_STANDARD",
name: "日常采购申请", name: "标准采购申请",
entity_type: "purchase_request", entity_type: "purchase_request",
sequence_prefix: "CG", sequence_prefix: "CG",
number_length: 4, number_length: 4,
@@ -478,6 +490,38 @@ async function seedWorkflowDefinition(prisma) {
created_by: null, created_by: null,
}, },
}); });
await prisma.workflowDefinition.updateMany({
where: {
entity_type: "purchase_request",
},
data: {
is_active: false,
},
});
await prisma.workflowDefinition.upsert({
where: {
entity_type_version: {
entity_type: "purchase_request",
version: 1,
},
},
update: {
name: "采购申请审批流程(V1)",
definition_json: DEFAULT_PURCHASE_WORKFLOW_DEFINITION,
is_active: true,
created_by: null,
},
create: {
name: "采购申请审批流程(V1)",
entity_type: "purchase_request",
definition_json: DEFAULT_PURCHASE_WORKFLOW_DEFINITION,
version: 1,
is_active: true,
created_by: null,
},
});
} }
async function main() { async function main() {
@@ -508,6 +552,8 @@ async function main() {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log("- workflow definition: leave v1 (1-step department_manager)"); console.log("- workflow definition: leave v1 (1-step department_manager)");
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log("- workflow definition: purchase_request v1 (1-step department_manager)");
// eslint-disable-next-line no-console
console.log(`- leave types: ${DEFAULT_LEAVE_TYPES.length} seeded with default policies`); console.log(`- leave types: ${DEFAULT_LEAVE_TYPES.length} seeded with default policies`);
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(`- feature flags: ${DEFAULT_FEATURE_FLAGS.length} seeded`); console.log(`- feature flags: ${DEFAULT_FEATURE_FLAGS.length} seeded`);
+1 -1
查看文件
@@ -29,7 +29,7 @@ const DOCUMENT_TYPE_CONFIG = {
}, },
}, },
purchase_request: { purchase_request: {
defaultCategoryCode: "purchase_general", defaultCategoryCode: "PR_STANDARD",
preferLegacySequence: false, preferLegacySequence: false,
fallback: { fallback: {
prefix: "CG", prefix: "CG",
@@ -35,6 +35,15 @@ describe("Permission Constants", () => {
expect(hrPermissions).toEqual(expect.arrayContaining(["leave.view_all", "leave.manage"])); expect(hrPermissions).toEqual(expect.arrayContaining(["leave.view_all", "leave.manage"]));
}); });
it("grants procurement permissions to configured roles", () => {
expect(hasPermission(resolvePermissionsForRoles(["employee"]), "procurement.create")).toBe(true);
expect(hasPermission(resolvePermissionsForRoles(["employee"]), "procurement.view_own")).toBe(true);
expect(hasPermission(resolvePermissionsForRoles(["manager"]), "procurement.approve")).toBe(true);
expect(hasPermission(resolvePermissionsForRoles(["finance"]), "procurement.approve")).toBe(false);
expect(hasPermission(resolvePermissionsForRoles(["finance_mgr"]), "procurement.manage")).toBe(true);
expect(hasPermission(resolvePermissionsForRoles(["administration"]), "procurement.manage")).toBe(true);
});
it("maps workflow entities to correct permission codes", () => { it("maps workflow entities to correct permission codes", () => {
expect(resolveEntityPermission("reimbursement", "approve")).toBe("expense.approve"); expect(resolveEntityPermission("reimbursement", "approve")).toBe("expense.approve");
expect(resolveEntityPermission("reimbursement", "transfer")).toBe("expense.transfer"); expect(resolveEntityPermission("reimbursement", "transfer")).toBe("expense.transfer");
@@ -87,8 +87,8 @@ describe("documentSequence service", () => {
it("falls back to default purchase category when sequence row does not exist", async () => { it("falls back to default purchase category when sequence row does not exist", async () => {
const prisma = buildPrisma({ const prisma = buildPrisma({
categoryMap: { categoryMap: {
purchase_general: { PR_STANDARD: {
code: "purchase_general", code: "PR_STANDARD",
entity_type: "purchase_request", entity_type: "purchase_request",
sequence_prefix: "CG", sequence_prefix: "CG",
number_length: 4, number_length: 4,
@@ -102,15 +102,15 @@ describe("documentSequence service", () => {
const config = await resolveDocumentSequenceConfig(prisma, { documentType: "purchase_request" }); const config = await resolveDocumentSequenceConfig(prisma, { documentType: "purchase_request" });
expect(config.source).toBe("document_category"); expect(config.source).toBe("document_category");
expect(config.documentCategoryCode).toBe("purchase_general"); expect(config.documentCategoryCode).toBe("PR_STANDARD");
expect(config.prefix).toBe("CG"); expect(config.prefix).toBe("CG");
}); });
it("issues a document number using category-driven config", async () => { it("issues a document number using category-driven config", async () => {
const prisma = buildPrisma({ const prisma = buildPrisma({
categoryMap: { categoryMap: {
purchase_general: { PR_STANDARD: {
code: "purchase_general", code: "PR_STANDARD",
entity_type: "purchase_request", entity_type: "purchase_request",
sequence_prefix: "CG", sequence_prefix: "CG",
number_length: 4, number_length: 4,