2026-08-10 18:22:22 -04:00
|
|
|
import { syncMockApprovalsForInvoice } from "@/mocks/approvals";
|
2026-08-10 18:00:51 -04:00
|
|
|
import { areMocksEnabled } from "@/mocks/env";
|
|
|
|
|
|
2026-08-10 18:22:22 -04:00
|
|
|
export type MockInvoiceStatus = "pending_approval" | "approved" | "scheduled" | "paid" | "rejected";
|
|
|
|
|
|
|
|
|
|
export type MockInvoiceLine = {
|
|
|
|
|
id: string;
|
|
|
|
|
description: string;
|
|
|
|
|
amount: number;
|
|
|
|
|
glAccountId: string;
|
|
|
|
|
departmentId: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type MockInvoiceComment = {
|
|
|
|
|
id: string;
|
|
|
|
|
authorUserId: string;
|
|
|
|
|
authorName: string;
|
|
|
|
|
body: string;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type MockInvoiceActivity = {
|
|
|
|
|
id: string;
|
|
|
|
|
actorUserId: string;
|
|
|
|
|
actorName: string;
|
|
|
|
|
message: string;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
};
|
2026-08-10 18:00:51 -04:00
|
|
|
|
|
|
|
|
export type MockInvoice = {
|
|
|
|
|
id: string;
|
|
|
|
|
invoiceNumber: string;
|
|
|
|
|
invoiceNumbers: string;
|
|
|
|
|
vendorId: string;
|
|
|
|
|
vendorName: string;
|
|
|
|
|
amountDue: number;
|
|
|
|
|
amount: number;
|
|
|
|
|
dueDate: string;
|
|
|
|
|
payDate: string | null;
|
|
|
|
|
sendPaymentOn: string | null;
|
|
|
|
|
status: MockInvoiceStatus;
|
|
|
|
|
paymentMethod: "check" | "ach";
|
2026-08-10 18:22:22 -04:00
|
|
|
documentUrl: string;
|
|
|
|
|
memo: string;
|
|
|
|
|
lines: MockInvoiceLine[];
|
|
|
|
|
comments: MockInvoiceComment[];
|
|
|
|
|
activity: MockInvoiceActivity[];
|
2026-08-10 18:00:51 -04:00
|
|
|
};
|
|
|
|
|
|
2026-08-10 18:22:22 -04:00
|
|
|
export type MockInvoiceHeaderPatch = Partial<
|
|
|
|
|
Pick<
|
|
|
|
|
MockInvoice,
|
|
|
|
|
| "vendorName"
|
|
|
|
|
| "amountDue"
|
|
|
|
|
| "amount"
|
|
|
|
|
| "dueDate"
|
|
|
|
|
| "payDate"
|
|
|
|
|
| "sendPaymentOn"
|
|
|
|
|
| "paymentMethod"
|
|
|
|
|
| "memo"
|
|
|
|
|
>
|
|
|
|
|
>;
|
|
|
|
|
|
|
|
|
|
const SAMPLE_DOCUMENT_URL = "/fixtures/sample-invoice.pdf";
|
|
|
|
|
|
|
|
|
|
function cloneInvoice(invoice: MockInvoice): MockInvoice {
|
|
|
|
|
return {
|
|
|
|
|
...invoice,
|
|
|
|
|
lines: invoice.lines.map((line) => ({ ...line })),
|
|
|
|
|
comments: invoice.comments.map((comment) => ({ ...comment })),
|
|
|
|
|
activity: invoice.activity.map((entry) => ({ ...entry })),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 18:00:51 -04:00
|
|
|
const seedInvoices: MockInvoice[] = [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-1",
|
|
|
|
|
invoiceNumber: "INV-1001",
|
|
|
|
|
invoiceNumbers: "INV-1001",
|
|
|
|
|
vendorId: "vendor-1",
|
|
|
|
|
vendorName: "Acme Supplies Co",
|
|
|
|
|
amountDue: 1250.5,
|
|
|
|
|
amount: 1250.5,
|
|
|
|
|
dueDate: "2026-08-20",
|
|
|
|
|
payDate: "2026-08-15",
|
|
|
|
|
sendPaymentOn: "2026-08-15",
|
|
|
|
|
status: "approved",
|
|
|
|
|
paymentMethod: "check",
|
2026-08-10 18:22:22 -04:00
|
|
|
documentUrl: SAMPLE_DOCUMENT_URL,
|
|
|
|
|
memo: "Office supplies restock",
|
|
|
|
|
lines: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-1-line-1",
|
|
|
|
|
description: "Printer paper (case)",
|
|
|
|
|
amount: 450.5,
|
|
|
|
|
glAccountId: "gl-1",
|
|
|
|
|
departmentId: "dept-4",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "inv-1-line-2",
|
|
|
|
|
description: "Toner cartridges",
|
|
|
|
|
amount: 800,
|
|
|
|
|
glAccountId: "gl-1",
|
|
|
|
|
departmentId: "dept-4",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
comments: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-1-c1",
|
|
|
|
|
authorUserId: "user-1",
|
|
|
|
|
authorName: "Alex Rivera",
|
|
|
|
|
body: "Coded to Administration office supplies.",
|
|
|
|
|
createdAt: "2026-08-05T14:20:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
activity: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-1-a1",
|
|
|
|
|
actorUserId: "user-1",
|
|
|
|
|
actorName: "Alex Rivera",
|
|
|
|
|
message: "Submitted for approval",
|
|
|
|
|
createdAt: "2026-08-05T15:00:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "inv-1-a2",
|
|
|
|
|
actorUserId: "user-2",
|
|
|
|
|
actorName: "Jordan Lee",
|
|
|
|
|
message: "Approved",
|
|
|
|
|
createdAt: "2026-08-06T10:12:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-08-10 18:00:51 -04:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "inv-2",
|
|
|
|
|
invoiceNumber: "INV-1002",
|
|
|
|
|
invoiceNumbers: "INV-1002",
|
|
|
|
|
vendorId: "vendor-2",
|
|
|
|
|
vendorName: "Harbor Maintenance LLC",
|
|
|
|
|
amountDue: 4800,
|
|
|
|
|
amount: 4800,
|
|
|
|
|
dueDate: "2026-08-25",
|
|
|
|
|
payDate: null,
|
|
|
|
|
sendPaymentOn: "2026-08-20",
|
|
|
|
|
status: "pending_approval",
|
|
|
|
|
paymentMethod: "ach",
|
2026-08-10 18:22:22 -04:00
|
|
|
documentUrl: SAMPLE_DOCUMENT_URL,
|
|
|
|
|
memo: "Dock repair materials",
|
|
|
|
|
lines: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-2-line-1",
|
|
|
|
|
description: "Dock board replacement",
|
|
|
|
|
amount: 3200,
|
|
|
|
|
glAccountId: "gl-2",
|
|
|
|
|
departmentId: "dept-1",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "inv-2-line-2",
|
|
|
|
|
description: "Labor",
|
|
|
|
|
amount: 1600,
|
|
|
|
|
glAccountId: "gl-4",
|
|
|
|
|
departmentId: "dept-1",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
comments: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-2-c1",
|
|
|
|
|
authorUserId: "user-1",
|
|
|
|
|
authorName: "Alex Rivera",
|
|
|
|
|
body: "Please confirm GL split before approve.",
|
|
|
|
|
createdAt: "2026-08-08T11:05:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
activity: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-2-a1",
|
|
|
|
|
actorUserId: "user-1",
|
|
|
|
|
actorName: "Alex Rivera",
|
|
|
|
|
message: "Submitted for approval",
|
|
|
|
|
createdAt: "2026-08-08T11:10:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-08-10 18:00:51 -04:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "inv-3",
|
|
|
|
|
invoiceNumber: "INV-1003",
|
|
|
|
|
invoiceNumbers: "INV-1003",
|
|
|
|
|
vendorId: "vendor-3",
|
|
|
|
|
vendorName: "Northwind Utilities",
|
|
|
|
|
amountDue: 312.75,
|
|
|
|
|
amount: 312.75,
|
|
|
|
|
dueDate: "2026-08-12",
|
|
|
|
|
payDate: "2026-08-10",
|
|
|
|
|
sendPaymentOn: "2026-08-10",
|
|
|
|
|
status: "approved",
|
|
|
|
|
paymentMethod: "check",
|
2026-08-10 18:22:22 -04:00
|
|
|
documentUrl: SAMPLE_DOCUMENT_URL,
|
|
|
|
|
memo: "July utilities",
|
|
|
|
|
lines: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-3-line-1",
|
|
|
|
|
description: "Electric service",
|
|
|
|
|
amount: 312.75,
|
|
|
|
|
glAccountId: "gl-3",
|
|
|
|
|
departmentId: "dept-1",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
comments: [],
|
|
|
|
|
activity: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-3-a1",
|
|
|
|
|
actorUserId: "user-1",
|
|
|
|
|
actorName: "Alex Rivera",
|
|
|
|
|
message: "Submitted for approval",
|
|
|
|
|
createdAt: "2026-08-01T09:00:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "inv-3-a2",
|
|
|
|
|
actorUserId: "user-3",
|
|
|
|
|
actorName: "Sam Patel",
|
|
|
|
|
message: "Approved",
|
|
|
|
|
createdAt: "2026-08-02T16:40:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-08-10 18:00:51 -04:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "inv-4",
|
|
|
|
|
invoiceNumber: "INV-1004",
|
|
|
|
|
invoiceNumbers: "INV-1004",
|
|
|
|
|
vendorId: "vendor-4",
|
|
|
|
|
vendorName: "Coastal Office Supply",
|
|
|
|
|
amountDue: 890.25,
|
|
|
|
|
amount: 900,
|
|
|
|
|
dueDate: "2026-09-01",
|
|
|
|
|
payDate: null,
|
|
|
|
|
sendPaymentOn: "2026-08-27",
|
|
|
|
|
status: "pending_approval",
|
|
|
|
|
paymentMethod: "ach",
|
2026-08-10 18:22:22 -04:00
|
|
|
documentUrl: SAMPLE_DOCUMENT_URL,
|
|
|
|
|
memo: "Desk chairs",
|
|
|
|
|
lines: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-4-line-1",
|
|
|
|
|
description: "Ergonomic chairs (3)",
|
|
|
|
|
amount: 900,
|
|
|
|
|
glAccountId: "gl-1",
|
|
|
|
|
departmentId: "dept-3",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
comments: [],
|
|
|
|
|
activity: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-4-a1",
|
|
|
|
|
actorUserId: "user-1",
|
|
|
|
|
actorName: "Alex Rivera",
|
|
|
|
|
message: "Submitted for approval",
|
|
|
|
|
createdAt: "2026-08-09T13:22:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-08-10 18:00:51 -04:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "inv-5",
|
|
|
|
|
invoiceNumber: "INV-1005",
|
|
|
|
|
invoiceNumbers: "INV-1005",
|
|
|
|
|
vendorId: "vendor-1",
|
|
|
|
|
vendorName: "Acme Supplies Co",
|
|
|
|
|
amountDue: 2100,
|
|
|
|
|
amount: 2100,
|
|
|
|
|
dueDate: "2026-07-30",
|
|
|
|
|
payDate: "2026-07-25",
|
|
|
|
|
sendPaymentOn: "2026-07-25",
|
|
|
|
|
status: "scheduled",
|
|
|
|
|
paymentMethod: "check",
|
2026-08-10 18:22:22 -04:00
|
|
|
documentUrl: SAMPLE_DOCUMENT_URL,
|
|
|
|
|
memo: "Quarterly stock",
|
|
|
|
|
lines: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-5-line-1",
|
|
|
|
|
description: "Bulk supplies",
|
|
|
|
|
amount: 2100,
|
|
|
|
|
glAccountId: "gl-1",
|
|
|
|
|
departmentId: "dept-2",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
comments: [],
|
|
|
|
|
activity: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-5-a1",
|
|
|
|
|
actorUserId: "user-2",
|
|
|
|
|
actorName: "Jordan Lee",
|
|
|
|
|
message: "Approved",
|
|
|
|
|
createdAt: "2026-07-20T12:00:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "inv-5-a2",
|
|
|
|
|
actorUserId: "user-1",
|
|
|
|
|
actorName: "Alex Rivera",
|
|
|
|
|
message: "Scheduled for payment",
|
|
|
|
|
createdAt: "2026-07-22T09:30:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-08-10 18:00:51 -04:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "inv-6",
|
|
|
|
|
invoiceNumber: "INV-1006",
|
|
|
|
|
invoiceNumbers: "INV-1006",
|
|
|
|
|
vendorId: "vendor-2",
|
|
|
|
|
vendorName: "Harbor Maintenance LLC",
|
|
|
|
|
amountDue: 0,
|
|
|
|
|
amount: 1550,
|
|
|
|
|
dueDate: "2026-07-15",
|
|
|
|
|
payDate: "2026-07-10",
|
|
|
|
|
sendPaymentOn: "2026-07-10",
|
|
|
|
|
status: "paid",
|
|
|
|
|
paymentMethod: "ach",
|
2026-08-10 18:22:22 -04:00
|
|
|
documentUrl: SAMPLE_DOCUMENT_URL,
|
|
|
|
|
memo: "Emergency pump repair",
|
|
|
|
|
lines: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-6-line-1",
|
|
|
|
|
description: "Pump repair",
|
|
|
|
|
amount: 1550,
|
|
|
|
|
glAccountId: "gl-2",
|
|
|
|
|
departmentId: "dept-1",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
comments: [],
|
|
|
|
|
activity: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-6-a1",
|
|
|
|
|
actorUserId: "user-1",
|
|
|
|
|
actorName: "Alex Rivera",
|
|
|
|
|
message: "Marked paid",
|
|
|
|
|
createdAt: "2026-07-11T08:00:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-08-10 18:00:51 -04:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "inv-7",
|
|
|
|
|
invoiceNumber: "INV-1007",
|
|
|
|
|
invoiceNumbers: "INV-1007",
|
|
|
|
|
vendorId: "vendor-3",
|
|
|
|
|
vendorName: "Northwind Utilities",
|
|
|
|
|
amountDue: 645.1,
|
|
|
|
|
amount: 645.1,
|
|
|
|
|
dueDate: "2026-08-28",
|
|
|
|
|
payDate: "2026-08-22",
|
|
|
|
|
sendPaymentOn: "2026-08-22",
|
|
|
|
|
status: "approved",
|
|
|
|
|
paymentMethod: "ach",
|
2026-08-10 18:22:22 -04:00
|
|
|
documentUrl: SAMPLE_DOCUMENT_URL,
|
|
|
|
|
memo: "August utilities",
|
|
|
|
|
lines: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-7-line-1",
|
|
|
|
|
description: "Water and sewer",
|
|
|
|
|
amount: 645.1,
|
|
|
|
|
glAccountId: "gl-3",
|
|
|
|
|
departmentId: "dept-1",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
comments: [],
|
|
|
|
|
activity: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-7-a1",
|
|
|
|
|
actorUserId: "user-3",
|
|
|
|
|
actorName: "Sam Patel",
|
|
|
|
|
message: "Approved",
|
|
|
|
|
createdAt: "2026-08-18T17:05:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "inv-8",
|
|
|
|
|
invoiceNumber: "INV-1008",
|
|
|
|
|
invoiceNumbers: "INV-1008",
|
|
|
|
|
vendorId: "vendor-1",
|
|
|
|
|
vendorName: "Acme Supplies Co",
|
|
|
|
|
amountDue: 275.5,
|
|
|
|
|
amount: 275.5,
|
|
|
|
|
dueDate: "2026-08-30",
|
|
|
|
|
payDate: null,
|
|
|
|
|
sendPaymentOn: "2026-08-28",
|
|
|
|
|
status: "pending_approval",
|
|
|
|
|
paymentMethod: "check",
|
|
|
|
|
documentUrl: SAMPLE_DOCUMENT_URL,
|
|
|
|
|
memo: "Misc small order",
|
|
|
|
|
lines: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-8-line-1",
|
|
|
|
|
description: "Labels and binders",
|
|
|
|
|
amount: 275.5,
|
|
|
|
|
glAccountId: "gl-1",
|
|
|
|
|
departmentId: "dept-2",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
comments: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-8-c1",
|
|
|
|
|
authorUserId: "user-1",
|
|
|
|
|
authorName: "Alex Rivera",
|
|
|
|
|
body: "Under threshold policy.",
|
|
|
|
|
createdAt: "2026-08-07T10:00:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
activity: [
|
|
|
|
|
{
|
|
|
|
|
id: "inv-8-a1",
|
|
|
|
|
actorUserId: "user-1",
|
|
|
|
|
actorName: "Alex Rivera",
|
|
|
|
|
message: "Submitted for approval",
|
|
|
|
|
createdAt: "2026-08-07T10:05:00.000Z",
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-08-10 18:00:51 -04:00
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/** Mutable in-memory invoice store for mock POC interactions. */
|
2026-08-10 18:22:22 -04:00
|
|
|
let invoiceStore: MockInvoice[] = seedInvoices.map((invoice) => cloneInvoice(invoice));
|
|
|
|
|
|
|
|
|
|
function replaceInvoice(index: number, next: MockInvoice): MockInvoice {
|
|
|
|
|
invoiceStore = [...invoiceStore.slice(0, index), next, ...invoiceStore.slice(index + 1)];
|
|
|
|
|
return cloneInvoice(next);
|
|
|
|
|
}
|
2026-08-10 18:00:51 -04:00
|
|
|
|
|
|
|
|
export function getMockInvoices(): MockInvoice[] {
|
|
|
|
|
if (!areMocksEnabled()) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2026-08-10 18:22:22 -04:00
|
|
|
return invoiceStore.map((invoice) => cloneInvoice(invoice));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getMockInvoice(id: string): MockInvoice | undefined {
|
|
|
|
|
if (!areMocksEnabled()) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
const found = invoiceStore.find((invoice) => invoice.id === id);
|
|
|
|
|
return found ? cloneInvoice(found) : undefined;
|
2026-08-10 18:00:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateMockInvoicePayDate(id: string, payDate: string | null): MockInvoice {
|
|
|
|
|
const index = invoiceStore.findIndex((invoice) => invoice.id === id);
|
|
|
|
|
if (index === -1) {
|
|
|
|
|
throw new Error(`Mock invoice not found: ${id}`);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 18:22:22 -04:00
|
|
|
return replaceInvoice(index, { ...invoiceStore[index], payDate });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateMockInvoiceHeader(id: string, patch: MockInvoiceHeaderPatch): MockInvoice {
|
|
|
|
|
const index = invoiceStore.findIndex((invoice) => invoice.id === id);
|
|
|
|
|
if (index === -1) {
|
|
|
|
|
throw new Error(`Mock invoice not found: ${id}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return replaceInvoice(index, { ...invoiceStore[index], ...patch });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateMockInvoiceLines(id: string, lines: MockInvoiceLine[]): MockInvoice {
|
|
|
|
|
const index = invoiceStore.findIndex((invoice) => invoice.id === id);
|
|
|
|
|
if (index === -1) {
|
|
|
|
|
throw new Error(`Mock invoice not found: ${id}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return replaceInvoice(index, {
|
|
|
|
|
...invoiceStore[index],
|
|
|
|
|
lines: lines.map((line) => ({ ...line })),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function addMockInvoiceComment(
|
|
|
|
|
id: string,
|
|
|
|
|
comment: Omit<MockInvoiceComment, "id" | "createdAt"> & {
|
|
|
|
|
id?: string;
|
|
|
|
|
createdAt?: string;
|
|
|
|
|
},
|
|
|
|
|
): MockInvoice {
|
|
|
|
|
const index = invoiceStore.findIndex((invoice) => invoice.id === id);
|
|
|
|
|
if (index === -1) {
|
|
|
|
|
throw new Error(`Mock invoice not found: ${id}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const current = invoiceStore[index];
|
|
|
|
|
const nextComment: MockInvoiceComment = {
|
|
|
|
|
id: comment.id ?? `comment-${Date.now()}`,
|
|
|
|
|
authorUserId: comment.authorUserId,
|
|
|
|
|
authorName: comment.authorName,
|
|
|
|
|
body: comment.body,
|
|
|
|
|
createdAt: comment.createdAt ?? new Date().toISOString(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return replaceInvoice(index, {
|
|
|
|
|
...current,
|
|
|
|
|
comments: [...current.comments, nextComment],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function transitionMockInvoiceStatus(
|
|
|
|
|
id: string,
|
|
|
|
|
status: MockInvoiceStatus,
|
|
|
|
|
activity: Omit<MockInvoiceActivity, "id" | "createdAt"> & {
|
|
|
|
|
id?: string;
|
|
|
|
|
createdAt?: string;
|
|
|
|
|
},
|
|
|
|
|
): MockInvoice {
|
|
|
|
|
const index = invoiceStore.findIndex((invoice) => invoice.id === id);
|
|
|
|
|
if (index === -1) {
|
|
|
|
|
throw new Error(`Mock invoice not found: ${id}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const current = invoiceStore[index];
|
|
|
|
|
const nextActivity: MockInvoiceActivity = {
|
|
|
|
|
id: activity.id ?? `activity-${Date.now()}`,
|
|
|
|
|
actorUserId: activity.actorUserId,
|
|
|
|
|
actorName: activity.actorName,
|
|
|
|
|
message: activity.message,
|
|
|
|
|
createdAt: activity.createdAt ?? new Date().toISOString(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updated = replaceInvoice(index, {
|
|
|
|
|
...current,
|
|
|
|
|
status,
|
|
|
|
|
activity: [...current.activity, nextActivity],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (status === "approved" || status === "rejected") {
|
|
|
|
|
syncMockApprovalsForInvoice(id, status);
|
|
|
|
|
} else if (status === "pending_approval") {
|
|
|
|
|
syncMockApprovalsForInvoice(id, "pending");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return updated;
|
2026-08-10 18:00:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Test helper to restore seed data between unit tests. */
|
|
|
|
|
export function resetMockInvoices(): void {
|
2026-08-10 18:22:22 -04:00
|
|
|
invoiceStore = seedInvoices.map((invoice) => cloneInvoice(invoice));
|
2026-08-10 18:00:51 -04:00
|
|
|
}
|