mirror of
https://github.com/Sea-Haven-Industries/seahaven-ap.git
synced 2026-10-02 22:43:14 +00:00
708 lines
18 KiB
TypeScript
708 lines
18 KiB
TypeScript
import { syncMockApprovalsForInvoice } from "@/mocks/approvals";
|
|
import { areMocksEnabled } from "@/mocks/env";
|
|
|
|
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;
|
|
};
|
|
|
|
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";
|
|
documentUrl: string;
|
|
memo: string;
|
|
lines: MockInvoiceLine[];
|
|
comments: MockInvoiceComment[];
|
|
activity: MockInvoiceActivity[];
|
|
hasBolt: boolean;
|
|
hasBell: boolean;
|
|
hasEye: boolean;
|
|
actionLabel: string;
|
|
lastUpdate: string;
|
|
lastUpdateMessage: string;
|
|
lastUpdateActorName: string;
|
|
lastUpdateAt: string;
|
|
invoiceDate: string;
|
|
processingBegan: string;
|
|
tagging: string;
|
|
fs: string;
|
|
description: string;
|
|
invoiceType: string;
|
|
location: string;
|
|
paymentAccount: string;
|
|
relatedInvoice: string | null;
|
|
rowCount: number;
|
|
};
|
|
|
|
export type MockInvoiceHeaderPatch = Partial<
|
|
Pick<
|
|
MockInvoice,
|
|
| "vendorName"
|
|
| "amountDue"
|
|
| "amount"
|
|
| "dueDate"
|
|
| "payDate"
|
|
| "sendPaymentOn"
|
|
| "paymentMethod"
|
|
| "memo"
|
|
>
|
|
>;
|
|
|
|
const SAMPLE_DOCUMENT_URL = "/fixtures/sample-invoice.pdf";
|
|
|
|
type MockInvoiceCore = Omit<
|
|
MockInvoice,
|
|
| "hasBolt"
|
|
| "hasBell"
|
|
| "hasEye"
|
|
| "actionLabel"
|
|
| "lastUpdate"
|
|
| "lastUpdateMessage"
|
|
| "lastUpdateActorName"
|
|
| "lastUpdateAt"
|
|
| "invoiceDate"
|
|
| "processingBegan"
|
|
| "tagging"
|
|
| "fs"
|
|
| "description"
|
|
| "invoiceType"
|
|
| "location"
|
|
| "paymentAccount"
|
|
| "relatedInvoice"
|
|
| "rowCount"
|
|
>;
|
|
|
|
const ACTION_LABEL_BY_STATUS: Record<MockInvoiceStatus, string> = {
|
|
pending_approval: "Approve invoice",
|
|
approved: "Approved",
|
|
scheduled: "Scheduled",
|
|
paid: "Paid",
|
|
rejected: "Rejected",
|
|
};
|
|
|
|
function actionLabelForStatus(status: MockInvoiceStatus): string {
|
|
return ACTION_LABEL_BY_STATUS[status];
|
|
}
|
|
|
|
function derivedActivityFields(invoice: Pick<MockInvoice, "activity" | "comments" | "dueDate">) {
|
|
const events = [
|
|
...invoice.activity.map((entry) => ({
|
|
at: entry.createdAt,
|
|
message: entry.message,
|
|
actor: entry.actorName,
|
|
})),
|
|
...invoice.comments.map((comment) => ({
|
|
at: comment.createdAt,
|
|
message: comment.body,
|
|
actor: comment.authorName,
|
|
})),
|
|
];
|
|
const latest = events.reduce<(typeof events)[number] | undefined>((current, event) => {
|
|
if (!current || event.at > current.at) {
|
|
return event;
|
|
}
|
|
return current;
|
|
}, undefined);
|
|
const lastUpdateAt = latest?.at ?? `${invoice.dueDate}T12:00:00.000Z`;
|
|
return {
|
|
lastUpdate: lastUpdateAt.slice(0, 10),
|
|
lastUpdateMessage: latest?.message ?? "Invoice updated",
|
|
lastUpdateActorName: latest?.actor ?? "System",
|
|
lastUpdateAt,
|
|
};
|
|
}
|
|
|
|
function paymentAccountFor(paymentMethod: MockInvoice["paymentMethod"]): string {
|
|
return paymentMethod === "ach" ? "Operating ACH" : "Operating Check";
|
|
}
|
|
|
|
/** Invoice date is independent of the due date. Offset varies per seed. */
|
|
function invoiceDateBeforeDue(dueDate: string, index: number): string {
|
|
const [year, month, day] = dueDate.split("-").map(Number);
|
|
const date = new Date(Date.UTC(year, month - 1, day));
|
|
date.setUTCDate(date.getUTCDate() - (14 + index));
|
|
return date.toISOString().slice(0, 10);
|
|
}
|
|
|
|
/** Keep catalog-derived fields in sync after mock mutations. */
|
|
function withRefreshedCatalogFields(invoice: MockInvoice): MockInvoice {
|
|
return {
|
|
...invoice,
|
|
actionLabel: actionLabelForStatus(invoice.status),
|
|
...derivedActivityFields(invoice),
|
|
description: invoice.memo,
|
|
paymentAccount: paymentAccountFor(invoice.paymentMethod),
|
|
rowCount: invoice.lines.length,
|
|
};
|
|
}
|
|
|
|
function enrichInvoice(core: MockInvoiceCore, index: number): MockInvoice {
|
|
return withRefreshedCatalogFields(
|
|
{
|
|
...core,
|
|
hasBolt: index % 3 === 0,
|
|
hasBell: index % 2 === 0,
|
|
hasEye: true,
|
|
actionLabel: actionLabelForStatus(core.status),
|
|
lastUpdate: "",
|
|
lastUpdateMessage: "",
|
|
lastUpdateActorName: "",
|
|
lastUpdateAt: "",
|
|
invoiceDate: invoiceDateBeforeDue(core.dueDate, index),
|
|
processingBegan: core.activity[0]?.createdAt?.slice(0, 10) ?? core.dueDate,
|
|
tagging: index % 2 === 0 ? "Ops" : "",
|
|
fs: index % 2 === 0 ? "synced" : "",
|
|
description: core.memo,
|
|
invoiceType: "Standard",
|
|
location: "Ronkonkoma",
|
|
paymentAccount: paymentAccountFor(core.paymentMethod),
|
|
relatedInvoice: null,
|
|
rowCount: core.lines.length,
|
|
},
|
|
);
|
|
}
|
|
|
|
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 })),
|
|
};
|
|
}
|
|
|
|
const seedInvoiceCores: MockInvoiceCore[] = [
|
|
{
|
|
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",
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
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",
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
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",
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
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",
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
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",
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
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",
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
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",
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
const seedInvoices: MockInvoice[] = seedInvoiceCores.map((core, index) =>
|
|
enrichInvoice(core, index),
|
|
);
|
|
|
|
/** Mutable in-memory invoice store for mock POC interactions. */
|
|
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);
|
|
}
|
|
|
|
export function getMockInvoices(): MockInvoice[] {
|
|
if (!areMocksEnabled()) {
|
|
return [];
|
|
}
|
|
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;
|
|
}
|
|
|
|
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}`);
|
|
}
|
|
|
|
const current = invoiceStore[index];
|
|
return replaceInvoice(
|
|
index,
|
|
withRefreshedCatalogFields(
|
|
{
|
|
...current,
|
|
payDate,
|
|
activity: [
|
|
...current.activity,
|
|
{
|
|
id: `activity-pay-date-${Date.now()}`,
|
|
actorUserId: "user-1",
|
|
actorName: "System",
|
|
message: "Pay date updated",
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
],
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
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,
|
|
withRefreshedCatalogFields({ ...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,
|
|
withRefreshedCatalogFields(
|
|
{
|
|
...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,
|
|
withRefreshedCatalogFields(
|
|
{
|
|
...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,
|
|
withRefreshedCatalogFields(
|
|
{
|
|
...current,
|
|
status,
|
|
activity: [...current.activity, nextActivity],
|
|
},
|
|
),
|
|
);
|
|
|
|
if (status === "approved" || status === "rejected") {
|
|
syncMockApprovalsForInvoice(id, status);
|
|
} else if (status === "pending_approval") {
|
|
syncMockApprovalsForInvoice(id, "pending");
|
|
}
|
|
|
|
return updated;
|
|
}
|
|
|
|
/** Test helper to restore seed data between unit tests. */
|
|
export function resetMockInvoices(): void {
|
|
invoiceStore = seedInvoices.map((invoice) => cloneInvoice(invoice));
|
|
}
|