const { issueDocumentNumber, resolveDocumentSequenceConfig, } = require("../../src/services/documentSequence"); function buildPrisma({ sequenceMap = {}, categoryMap = {} } = {}) { return { businessNumberPolicy: { findUnique: vi.fn(async () => null) }, $executeRaw: vi.fn(async () => 0), documentSequence: { findUnique: async ({ where }) => sequenceMap[where.document_type] ?? null, upsert: async ({ where, update, create }) => { const current = sequenceMap[where.document_type]; if (current) { return { ...current, prefix: update.prefix, date_format: update.date_format, separator: update.separator, number_length: update.number_length, reset_period: update.reset_period, current_number: current.current_number + 1, updated_by: update.updated_by, updated_at: new Date("2026-03-07T00:00:00.000Z"), }; } return { document_type: create.document_type, prefix: create.prefix, date_format: create.date_format, separator: create.separator, number_length: create.number_length, reset_period: create.reset_period, current_number: create.current_number, last_reset_date: null, updated_at: new Date("2026-03-07T00:00:00.000Z"), updated_by: create.updated_by, }; }, }, documentCategory: { findUnique: async ({ where }) => categoryMap[where.code] ?? null, findFirst: async ({ where }) => Object.values(categoryMap).find( (item) => item.entity_type === where.entity_type && item.enabled && item.archived_at == null, ) ?? null, }, }; } describe("documentSequence service", () => { it("keeps reimbursement on legacy sequence when sequence row exists", async () => { const prisma = buildPrisma({ sequenceMap: { reimbursement: { document_type: "reimbursement", prefix: "BX", date_format: "YYYYMMDD", separator: "-", number_length: 4, reset_period: "none", current_number: 12, last_reset_date: null, updated_at: new Date("2026-03-07T00:00:00.000Z"), updated_by: "tester", }, }, categoryMap: { reimbursement_general: { code: "reimbursement_general", entity_type: "reimbursement", sequence_prefix: "RB", number_length: 6, reset_period: "yearly", enabled: true, archived_at: null, }, }, }); const config = await resolveDocumentSequenceConfig(prisma, { documentType: "reimbursement" }); expect(config.source).toBe("document_sequence"); expect(config.prefix).toBe("BX"); expect(config.resetPeriod).toBe("none"); }); it("falls back to default purchase category when sequence row does not exist", async () => { const prisma = buildPrisma({ categoryMap: { PR_STANDARD: { code: "PR_STANDARD", entity_type: "purchase_request", sequence_prefix: "CG", number_length: 4, reset_period: "yearly", enabled: true, archived_at: null, }, }, }); const config = await resolveDocumentSequenceConfig(prisma, { documentType: "purchase_request" }); expect(config.source).toBe("document_category"); expect(config.documentCategoryCode).toBe("PR_STANDARD"); expect(config.prefix).toBe("CG"); }); it("issues a document number using category-driven config", async () => { const prisma = buildPrisma({ categoryMap: { PR_STANDARD: { code: "PR_STANDARD", entity_type: "purchase_request", sequence_prefix: "CG", number_length: 4, reset_period: "yearly", enabled: true, archived_at: null, }, }, }); const issued = await issueDocumentNumber(prisma, { documentType: "purchase_request", actorId: "tester", timeZone: "Asia/Shanghai", }); expect(issued.config.source).toBe("document_category"); expect(issued.documentNumber).toMatch(/^CG-\d{8}-0001$/); }); it("falls back to inventory check legacy-free config", async () => { const prisma = buildPrisma(); const config = await resolveDocumentSequenceConfig(prisma, { documentType: "inventory_check" }); expect(config.source).toBe("fallback"); expect(config.prefix).toBe("PD"); expect(config.documentCategoryCode).toBeNull(); }); it("falls back to default travel category code for travel requests", async () => { const prisma = buildPrisma({ categoryMap: { TR_STANDARD: { code: "TR_STANDARD", entity_type: "travel_request", sequence_prefix: "CL", number_length: 4, reset_period: "yearly", enabled: true, archived_at: null, }, }, }); const config = await resolveDocumentSequenceConfig(prisma, { documentType: "travel_request" }); expect(config.source).toBe("document_category"); expect(config.documentCategoryCode).toBe("TR_STANDARD"); expect(config.prefix).toBe("CL"); }); it("falls back to default travel reconciliation batch category code", async () => { const prisma = buildPrisma({ categoryMap: { TR_RECON: { code: "TR_RECON", entity_type: "travel_reconciliation_batch", sequence_prefix: "TRB", number_length: 4, reset_period: "yearly", enabled: true, archived_at: null, }, }, }); const config = await resolveDocumentSequenceConfig(prisma, { documentType: "travel_reconciliation_batch" }); expect(config.source).toBe("document_category"); expect(config.documentCategoryCode).toBe("TR_RECON"); expect(config.prefix).toBe("TRB"); }); it("falls back to default asset category code for asset cards", async () => { const prisma = buildPrisma({ categoryMap: { ASSET_CARD_DEFAULT: { code: "ASSET_CARD_DEFAULT", entity_type: "asset_card", sequence_prefix: "ZC", number_length: 4, reset_period: "yearly", enabled: true, archived_at: null, }, }, }); const config = await resolveDocumentSequenceConfig(prisma, { documentType: "asset_card" }); expect(config.source).toBe("document_category"); expect(config.documentCategoryCode).toBe("ASSET_CARD_DEFAULT"); expect(config.prefix).toBe("ZC"); }); it("can issue asset numbers with independent sequence keys per asset number family", async () => { const prisma = buildPrisma({ sequenceMap: { "asset_card:ASSET_EA_DOC": { document_type: "asset_card:ASSET_EA_DOC", prefix: "EA-DOC", date_format: "YYYY", separator: "-", number_length: 6, reset_period: "yearly", current_number: 8, last_reset_date: null, updated_at: new Date("2026-03-07T00:00:00.000Z"), updated_by: "tester", }, }, categoryMap: { ASSET_EA_DOC: { code: "ASSET_EA_DOC", entity_type: "asset_card", sequence_prefix: "EA-DOC", number_length: 6, reset_period: "yearly", enabled: true, archived_at: null, }, }, }); const issued = await issueDocumentNumber(prisma, { documentType: "asset_card", documentCategoryCode: "ASSET_EA_DOC", sequenceKey: "asset_card:ASSET_EA_DOC", actorId: "tester", timeZone: "Asia/Shanghai", }); expect(issued.sequence.documentType).toBe("asset_card:ASSET_EA_DOC"); expect(issued.documentNumber).toMatch(/^EA-DOC-\d{4}-000009$/); }); });