diff --git a/client/src/auth/permissions.ts b/client/src/auth/permissions.ts index 87e8f760..fdbb3f51 100644 --- a/client/src/auth/permissions.ts +++ b/client/src/auth/permissions.ts @@ -22,6 +22,8 @@ const PROCUREMENT_SELF_SERVICE_PERMISSIONS = [ "procurement.submit", ] as const; +const ASSET_SELF_SERVICE_PERMISSIONS = ["asset.view_own"] as const; + const MASTERDATA_VIEW_PERMISSIONS = ["masterdata.view"] as const; const MASTERDATA_MANAGE_PERMISSIONS = ["masterdata.manage"] as const; @@ -34,6 +36,7 @@ const ROLE_PERMISSIONS: Record = { employee: [ ...EMPLOYEE_BASE_PERMISSIONS, ...PROCUREMENT_SELF_SERVICE_PERMISSIONS, + ...ASSET_SELF_SERVICE_PERMISSIONS, ], manager: [ ...EMPLOYEE_BASE_PERMISSIONS, @@ -44,6 +47,7 @@ const ROLE_PERMISSIONS: Record = { "leave.approve", "procurement.approve", "procurement.view_all", + "asset.view_all", "report.dashboard.view", "budget.view", "budget.manage", @@ -59,6 +63,7 @@ const ROLE_PERMISSIONS: Record = { "leave.view_all", "procurement.approve", "procurement.view_all", + "asset.view_all", "report.dashboard.view", "budget.view", ], @@ -73,6 +78,7 @@ const ROLE_PERMISSIONS: Record = { "expense.finance.pay", "expense.view_all", "procurement.view_all", + "asset.view_all", ], finance_mgr: [ ...EMPLOYEE_BASE_PERMISSIONS, @@ -90,6 +96,9 @@ const ROLE_PERMISSIONS: Record = { "expense.finance.override_duplicate", "procurement.view_all", "procurement.manage", + "asset.view_all", + "asset.manage", + "asset.inventory", "system.settings", "system.audit.view", ], @@ -98,6 +107,9 @@ const ROLE_PERMISSIONS: Record = { ...EMPLOYEE_BASE_PERMISSIONS, "procurement.view_all", "procurement.manage", + "asset.view_all", + "asset.manage", + "asset.transfer", ], hr: [ ...LEAVE_SELF_SERVICE_PERMISSIONS, diff --git a/server/prisma/migrations/20260307142228_p18a_asset_registry_core/migration.sql b/server/prisma/migrations/20260307142228_p18a_asset_registry_core/migration.sql new file mode 100644 index 00000000..514cd658 --- /dev/null +++ b/server/prisma/migrations/20260307142228_p18a_asset_registry_core/migration.sql @@ -0,0 +1,89 @@ +-- CreateEnum +CREATE TYPE "AssetCardStatus" AS ENUM ('in_stock', 'in_use', 'under_repair', 'retired', 'archived'); + +-- CreateTable +CREATE TABLE "asset_categories" ( + "id" UUID NOT NULL, + "code" TEXT NOT NULL, + "name" TEXT NOT NULL, + "document_category_id" UUID, + "enabled" BOOLEAN NOT NULL DEFAULT true, + "archived_at" TIMESTAMP(3), + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by" UUID, + "updated_at" TIMESTAMP(3) NOT NULL, + "updated_by" UUID, + + CONSTRAINT "asset_categories_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "asset_cards" ( + "id" UUID NOT NULL, + "asset_no" TEXT NOT NULL, + "name" TEXT NOT NULL, + "category_id" UUID NOT NULL, + "legal_entity_id" UUID NOT NULL, + "cost_center_id" UUID NOT NULL, + "purchase_request_id" UUID, + "serial_no" TEXT, + "location" TEXT, + "status" "AssetCardStatus" NOT NULL DEFAULT 'in_stock', + "assignee_employee_id" UUID, + "activated_at" DATE, + "retired_at" DATE, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created_by" UUID, + "updated_at" TIMESTAMP(3) NOT NULL, + "updated_by" UUID, + + CONSTRAINT "asset_cards_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "asset_categories_code_key" ON "asset_categories"("code"); + +-- CreateIndex +CREATE INDEX "idx_asset_categories_status" ON "asset_categories"("enabled", "archived_at"); + +-- CreateIndex +CREATE INDEX "idx_asset_categories_document_category" ON "asset_categories"("document_category_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "asset_cards_asset_no_key" ON "asset_cards"("asset_no"); + +-- CreateIndex +CREATE INDEX "idx_asset_cards_category" ON "asset_cards"("category_id"); + +-- CreateIndex +CREATE INDEX "idx_asset_cards_legal_entity" ON "asset_cards"("legal_entity_id"); + +-- CreateIndex +CREATE INDEX "idx_asset_cards_cost_center" ON "asset_cards"("cost_center_id"); + +-- CreateIndex +CREATE INDEX "idx_asset_cards_purchase_request" ON "asset_cards"("purchase_request_id"); + +-- CreateIndex +CREATE INDEX "idx_asset_cards_assignee" ON "asset_cards"("assignee_employee_id"); + +-- CreateIndex +CREATE INDEX "idx_asset_cards_status" ON "asset_cards"("status"); + +-- AddForeignKey +ALTER TABLE "asset_categories" ADD CONSTRAINT "asset_categories_document_category_id_fkey" FOREIGN KEY ("document_category_id") REFERENCES "document_categories"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "asset_cards" ADD CONSTRAINT "asset_cards_category_id_fkey" FOREIGN KEY ("category_id") REFERENCES "asset_categories"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "asset_cards" ADD CONSTRAINT "asset_cards_legal_entity_id_fkey" FOREIGN KEY ("legal_entity_id") REFERENCES "legal_entities"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "asset_cards" ADD CONSTRAINT "asset_cards_cost_center_id_fkey" FOREIGN KEY ("cost_center_id") REFERENCES "cost_centers"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "asset_cards" ADD CONSTRAINT "asset_cards_purchase_request_id_fkey" FOREIGN KEY ("purchase_request_id") REFERENCES "purchase_requests"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "asset_cards" ADD CONSTRAINT "asset_cards_assignee_employee_id_fkey" FOREIGN KEY ("assignee_employee_id") REFERENCES "employees"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 517c2382..ba4b855e 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -52,6 +52,14 @@ enum PurchaseRequestStatus { cancelled } +enum AssetCardStatus { + in_stock + in_use + under_repair + retired + archived +} + enum LeaveDaySession { am pm @@ -229,10 +237,11 @@ model Employee { leave_requests LeaveRequest[] leave_ledgers LeaveLedger[] owned_cost_centers CostCenter[] @relation("CostCenterOwner") - purchase_requests_applicant PurchaseRequest[] @relation("PurchaseRequestApplicant") - purchase_requests_submitted PurchaseRequest[] @relation("PurchaseRequestSubmitter") - purchase_attachments_uploaded PurchaseAttachment[] @relation("PurchaseAttachmentUploader") + purchase_requests_applicant PurchaseRequest[] @relation("PurchaseRequestApplicant") + purchase_requests_submitted PurchaseRequest[] @relation("PurchaseRequestSubmitter") + purchase_attachments_uploaded PurchaseAttachment[] @relation("PurchaseAttachmentUploader") purchase_order_archives PurchaseOrderArchive[] @relation("PurchaseOrderArchiveArchivedBy") + assigned_asset_cards AssetCard[] @relation("AssetCardAssignee") owned_invoices InvoicePool[] @relation("InvoiceOwner") uploaded_invoices InvoicePool[] @relation("InvoiceUploader") @@ -424,6 +433,7 @@ model LegalEntity { holiday_calendars HolidayCalendar[] @relation("HolidayCalendarLegalEntity") suppliers Supplier[] purchase_requests PurchaseRequest[] + asset_cards AssetCard[] budget_pools BudgetPool[] @relation("BudgetPoolLegalEntity") @@index([enabled, archived_at], map: "idx_legal_entities_status") @@ -449,9 +459,10 @@ model CostCenter { 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") + owner_employee Employee? @relation("CostCenterOwner", fields: [owner_employee_id], references: [id], onDelete: SetNull) + budget_pools BudgetPool[] @relation("BudgetPoolCostCenter") purchase_requests PurchaseRequest[] + 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") @@ -478,6 +489,7 @@ model DocumentCategory { 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") @@ -586,6 +598,7 @@ model PurchaseRequest { 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") @@ -656,6 +669,62 @@ model PurchaseOrderArchive { @@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) + + @@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 ReimbursementForm { id String @id @default(uuid()) @db.Uuid form_number String? @unique diff --git a/server/src/constants/permissions.js b/server/src/constants/permissions.js index b8e2ca23..da220c6d 100644 --- a/server/src/constants/permissions.js +++ b/server/src/constants/permissions.js @@ -22,6 +22,8 @@ const PROCUREMENT_SELF_SERVICE_PERMISSIONS = [ "procurement.submit", ]; +const ASSET_SELF_SERVICE_PERMISSIONS = ["asset.view_own"]; + const MASTERDATA_VIEW_PERMISSIONS = ["masterdata.view"]; const MASTERDATA_MANAGE_PERMISSIONS = ["masterdata.manage"]; @@ -83,6 +85,7 @@ const ROLE_PERMISSIONS = { employee: [ ...EMPLOYEE_BASE_PERMISSIONS, ...PROCUREMENT_SELF_SERVICE_PERMISSIONS, + ...ASSET_SELF_SERVICE_PERMISSIONS, ], manager: [ ...EMPLOYEE_BASE_PERMISSIONS, @@ -93,6 +96,7 @@ const ROLE_PERMISSIONS = { "leave.approve", "procurement.approve", "procurement.view_all", + "asset.view_all", "report.dashboard.view", "budget.view", "budget.manage", @@ -108,6 +112,7 @@ const ROLE_PERMISSIONS = { "leave.view_all", "procurement.approve", "procurement.view_all", + "asset.view_all", "report.dashboard.view", "budget.view", ], @@ -122,6 +127,7 @@ const ROLE_PERMISSIONS = { "expense.finance.pay", "expense.view_all", "procurement.view_all", + "asset.view_all", ], finance_mgr: [ ...EMPLOYEE_BASE_PERMISSIONS, @@ -139,6 +145,9 @@ const ROLE_PERMISSIONS = { "expense.finance.override_duplicate", "procurement.view_all", "procurement.manage", + "asset.view_all", + "asset.manage", + "asset.inventory", "system.settings", "system.audit.view", ], @@ -147,6 +156,9 @@ const ROLE_PERMISSIONS = { ...EMPLOYEE_BASE_PERMISSIONS, "procurement.view_all", "procurement.manage", + "asset.view_all", + "asset.manage", + "asset.transfer", ], hr: [ ...LEAVE_SELF_SERVICE_PERMISSIONS, diff --git a/server/src/scripts/seed-config.js b/server/src/scripts/seed-config.js index 6ce3cb36..170623ff 100644 --- a/server/src/scripts/seed-config.js +++ b/server/src/scripts/seed-config.js @@ -150,8 +150,8 @@ const DEFAULT_DOCUMENT_CATEGORIES = [ enabled: true, }, { - code: "asset_it", - name: "IT 固资台账", + code: "ASSET_CARD_DEFAULT", + name: "固定资产卡片", entity_type: "asset_card", sequence_prefix: "ZC", number_length: 4, diff --git a/server/src/services/documentSequence.js b/server/src/services/documentSequence.js index 95f701a4..747b7174 100644 --- a/server/src/services/documentSequence.js +++ b/server/src/services/documentSequence.js @@ -40,7 +40,7 @@ const DOCUMENT_TYPE_CONFIG = { }, }, asset_card: { - defaultCategoryCode: "asset_it", + defaultCategoryCode: "ASSET_CARD_DEFAULT", preferLegacySequence: false, fallback: { prefix: "ZC",