All PO attachments are stored as PODocument rows whose lifecycle stage (submission vs delivery) is encoded in the storageKey prefix. The PO details screen previously listed them in a single flat "Attachments" block, giving no indication of which were submission documents (invoice, quotation) versus delivery receipts. Add lib/attachments.ts to derive a user-facing group from the storageKey prefix (submission / payment / delivery / other) and render each non-empty group as a labelled subsection on the PO details screen, in lifecycle order. Unknown prefixes fall back to an "Other" group so nothing is ever hidden. Fixes #10
96 lines
2.7 KiB
TypeScript
96 lines
2.7 KiB
TypeScript
/**
|
|
* Attachment grouping.
|
|
*
|
|
* All PO attachments are stored as `PODocument` rows. The lifecycle stage an
|
|
* attachment belongs to is encoded in the leading segment of its `storageKey`
|
|
* (see `buildStorageKey` in `lib/storage.ts`), e.g. `po-document/<poId>/...`
|
|
* or `receipt/<poId>/...`. This module derives a user-facing grouping from
|
|
* that prefix so the PO details screen can show every attachment grouped by
|
|
* type (submission, payment, delivery).
|
|
*/
|
|
|
|
export type AttachmentGroupKey = "submission" | "payment" | "delivery" | "other";
|
|
|
|
export interface AttachmentGroupMeta {
|
|
key: AttachmentGroupKey;
|
|
label: string;
|
|
description: string;
|
|
}
|
|
|
|
/** Display order for attachment groups (lifecycle order). */
|
|
export const ATTACHMENT_GROUP_ORDER: AttachmentGroupKey[] = [
|
|
"submission",
|
|
"payment",
|
|
"delivery",
|
|
"other",
|
|
];
|
|
|
|
export const ATTACHMENT_GROUP_META: Record<AttachmentGroupKey, AttachmentGroupMeta> = {
|
|
submission: {
|
|
key: "submission",
|
|
label: "Submission documents",
|
|
description: "Uploaded with the purchase order (e.g. invoice, quotation).",
|
|
},
|
|
payment: {
|
|
key: "payment",
|
|
label: "Payment documents",
|
|
description: "Uploaded at payment (e.g. payment proof).",
|
|
},
|
|
delivery: {
|
|
key: "delivery",
|
|
label: "Delivery receipts",
|
|
description: "Uploaded at delivery confirmation (e.g. delivery receipt).",
|
|
},
|
|
other: {
|
|
key: "other",
|
|
label: "Other attachments",
|
|
description: "",
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Derive the lifecycle group of an attachment from its storage key prefix.
|
|
* Unknown prefixes fall back to "other" so nothing is ever hidden.
|
|
*/
|
|
export function categorizeAttachment(storageKey: string): AttachmentGroupKey {
|
|
const prefix = storageKey.split("/")[0];
|
|
switch (prefix) {
|
|
case "po-document":
|
|
return "submission";
|
|
case "payment-document":
|
|
case "payment":
|
|
return "payment";
|
|
case "receipt":
|
|
return "delivery";
|
|
default:
|
|
return "other";
|
|
}
|
|
}
|
|
|
|
export interface AttachmentGroup<T> {
|
|
meta: AttachmentGroupMeta;
|
|
items: T[];
|
|
}
|
|
|
|
/**
|
|
* Group attachments by lifecycle stage, returning only non-empty groups in
|
|
* canonical lifecycle order. Item order within each group is preserved.
|
|
*/
|
|
export function groupAttachments<T extends { storageKey: string }>(
|
|
documents: T[]
|
|
): AttachmentGroup<T>[] {
|
|
const buckets = new Map<AttachmentGroupKey, T[]>();
|
|
for (const doc of documents) {
|
|
const key = categorizeAttachment(doc.storageKey);
|
|
const bucket = buckets.get(key);
|
|
if (bucket) bucket.push(doc);
|
|
else buckets.set(key, [doc]);
|
|
}
|
|
|
|
return ATTACHMENT_GROUP_ORDER.flatMap((key) => {
|
|
const items = buckets.get(key);
|
|
return items && items.length > 0
|
|
? [{ meta: ATTACHMENT_GROUP_META[key], items }]
|
|
: [];
|
|
});
|
|
}
|