medusajs-documents-plugin 1.1.14 → 1.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractBaseReference = extractBaseReference;
4
+ exports.getDocTypePriority = getDocTypePriority;
5
+ exports.getDocLifecycleOrder = getDocLifecycleOrder;
6
+ exports.getDocTypeLabel = getDocTypeLabel;
7
+ exports.groupDocuments = groupDocuments;
8
+ /**
9
+ * Extracts normalized base reference by stripping known document prefixes.
10
+ * Examples:
11
+ * - "JOB-1792156525673-SNL9QH" -> "1792156525673-SNL9QH"
12
+ * - "RT-1792156525673-SNL9QH" -> "1792156525673-SNL9QH"
13
+ * - "EST-1792156525673-SNL9QH" -> "1792156525673-SNL9QH"
14
+ * - "RCPT-1792156525673-SNL9QH" -> "1792156525673-SNL9QH"
15
+ * - "INV-1001" -> "1001"
16
+ * - "RCPT-1001" -> "1001"
17
+ * - "PKG-1001" -> "1001"
18
+ */
19
+ function extractBaseReference(displayId) {
20
+ if (!displayId)
21
+ return "";
22
+ const cleaned = String(displayId).trim();
23
+ // Strip repeated prefixes like JOB-, RT-, EST-, RCPT-, INV-, PKG-, DOC-
24
+ const withoutPrefix = cleaned.replace(/^(JOB-|RT-|EST-|RCPT-|INV-|PKG-|DOC-)+/i, "");
25
+ return withoutPrefix.trim();
26
+ }
27
+ /**
28
+ * Priority scoring for electing the primary document representing a pack.
29
+ * An Invoice is always top priority if available in the pack.
30
+ */
31
+ function getDocTypePriority(type) {
32
+ switch (type?.toLowerCase()) {
33
+ case "invoice":
34
+ return 100; // Invoices always take top precedence
35
+ case "estimate":
36
+ return 80;
37
+ case "job_card":
38
+ return 70;
39
+ case "receipt":
40
+ return 60;
41
+ case "packing_slip":
42
+ return 50;
43
+ case "custom":
44
+ return 40;
45
+ default:
46
+ return 10;
47
+ }
48
+ }
49
+ /**
50
+ * Workflow lifecycle ordering for sorting related documents inside an expanded pack.
51
+ * 1. Job Card -> 2. Quote/Estimate -> 3. Invoice -> 4. Receipt -> 5. Packing Slip -> 6. Custom
52
+ */
53
+ function getDocLifecycleOrder(type) {
54
+ switch (type?.toLowerCase()) {
55
+ case "job_card":
56
+ return 1;
57
+ case "estimate":
58
+ return 2;
59
+ case "invoice":
60
+ return 3;
61
+ case "receipt":
62
+ return 4;
63
+ case "packing_slip":
64
+ return 5;
65
+ case "custom":
66
+ return 6;
67
+ default:
68
+ return 10;
69
+ }
70
+ }
71
+ /**
72
+ * Human-friendly title or label for a document type.
73
+ */
74
+ function getDocTypeLabel(type) {
75
+ switch (type?.toLowerCase()) {
76
+ case "invoice":
77
+ return "Invoice";
78
+ case "estimate":
79
+ return "Quote";
80
+ case "job_card":
81
+ return "Job Card";
82
+ case "receipt":
83
+ return "Receipt";
84
+ case "packing_slip":
85
+ return "Packing Slip";
86
+ case "custom":
87
+ return "Certificate";
88
+ default:
89
+ return type || "Document";
90
+ }
91
+ }
92
+ /**
93
+ * Groups related documents by job or order using a multi-tier correlation algorithm.
94
+ * 1. Ties documents by repair_ticket_id
95
+ * 2. Ties documents by order_id
96
+ * 3. Ties documents by normalized reference base suffix (e.g. 1792156525673-SNL9QH or 1001)
97
+ */
98
+ function groupDocuments(documents) {
99
+ if (!documents || documents.length === 0)
100
+ return [];
101
+ // Disjoint Set Union (DSU) to cluster documents sharing any correlation key
102
+ const parent = new Map();
103
+ function find(x) {
104
+ if (!parent.has(x))
105
+ parent.set(x, x);
106
+ if (parent.get(x) !== x) {
107
+ parent.set(x, find(parent.get(x)));
108
+ }
109
+ return parent.get(x);
110
+ }
111
+ function union(x, y) {
112
+ const rootX = find(x);
113
+ const rootY = find(y);
114
+ if (rootX !== rootY) {
115
+ parent.set(rootY, rootX);
116
+ }
117
+ }
118
+ // Assign keys to each document
119
+ const docKeys = new Map();
120
+ for (const doc of documents) {
121
+ const keys = [`doc:${doc.id}`];
122
+ if (doc.repair_ticket_id) {
123
+ keys.push(`rep:${doc.repair_ticket_id}`);
124
+ }
125
+ if (doc.order_id) {
126
+ keys.push(`ord:${doc.order_id}`);
127
+ }
128
+ const baseRef = extractBaseReference(doc.display_id);
129
+ if (baseRef && baseRef.length >= 3) {
130
+ // Long references like 1792156525673-SNL9QH are globally unique
131
+ if (baseRef.length >= 6 || /[A-Za-z]/.test(baseRef)) {
132
+ keys.push(`ref:${baseRef.toLowerCase()}`);
133
+ }
134
+ else {
135
+ // Short references like 1001 correlate within the same customer or order
136
+ const custKey = (doc.customer_email || doc.customer_phone || doc.customer_name || "").toLowerCase().trim();
137
+ keys.push(`ref:${baseRef.toLowerCase()}${custKey ? `:${custKey}` : ""}`);
138
+ }
139
+ }
140
+ docKeys.set(doc.id, keys);
141
+ // Union all keys of this document together
142
+ const primaryKey = keys[0];
143
+ for (let i = 1; i < keys.length; i++) {
144
+ union(primaryKey, keys[i]);
145
+ }
146
+ }
147
+ // Connect multiple documents that share any key
148
+ const keyToDocs = new Map();
149
+ for (const [docId, keys] of docKeys.entries()) {
150
+ for (const k of keys) {
151
+ if (!keyToDocs.has(k))
152
+ keyToDocs.set(k, []);
153
+ keyToDocs.get(k).push(docId);
154
+ }
155
+ }
156
+ for (const [, docIds] of keyToDocs.entries()) {
157
+ if (docIds.length > 1) {
158
+ const firstRoot = `doc:${docIds[0]}`;
159
+ for (let i = 1; i < docIds.length; i++) {
160
+ union(firstRoot, `doc:${docIds[i]}`);
161
+ }
162
+ }
163
+ }
164
+ // Aggregate documents by root cluster
165
+ const clusters = new Map();
166
+ for (const doc of documents) {
167
+ const root = find(`doc:${doc.id}`);
168
+ if (!clusters.has(root))
169
+ clusters.set(root, []);
170
+ clusters.get(root).push(doc);
171
+ }
172
+ // Build DocumentGroup objects
173
+ const groups = [];
174
+ for (const [clusterId, clusterDocs] of clusters.entries()) {
175
+ // Sort documents in lifecycle order: job_card -> estimate -> invoice -> receipt -> packing_slip -> custom
176
+ clusterDocs.sort((a, b) => {
177
+ const orderA = getDocLifecycleOrder(a.type);
178
+ const orderB = getDocLifecycleOrder(b.type);
179
+ if (orderA !== orderB)
180
+ return orderA - orderB;
181
+ return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
182
+ });
183
+ // Elect primary document: Highest priority (Invoice if available, else Estimate/Job Card)
184
+ let primaryDoc = clusterDocs[0];
185
+ let maxPriority = -1;
186
+ for (const d of clusterDocs) {
187
+ const p = getDocTypePriority(d.type);
188
+ if (p > maxPriority) {
189
+ maxPriority = p;
190
+ primaryDoc = d;
191
+ }
192
+ }
193
+ const hasInvoice = clusterDocs.some((d) => d.type?.toLowerCase() === "invoice");
194
+ // Group total amount: prefer Invoice total, then Estimate total, then Receipt total, then primaryDoc total
195
+ const invoiceDoc = clusterDocs.find((d) => d.type?.toLowerCase() === "invoice");
196
+ const estimateDoc = clusterDocs.find((d) => d.type?.toLowerCase() === "estimate");
197
+ const receiptDoc = clusterDocs.find((d) => d.type?.toLowerCase() === "receipt");
198
+ const totalAmount = Number((invoiceDoc && invoiceDoc.total_amount != null)
199
+ ? invoiceDoc.total_amount
200
+ : (estimateDoc && estimateDoc.total_amount != null)
201
+ ? estimateDoc.total_amount
202
+ : (receiptDoc && receiptDoc.total_amount != null)
203
+ ? receiptDoc.total_amount
204
+ : primaryDoc.total_amount || 0);
205
+ // Group currency
206
+ const currencyCode = primaryDoc.currency_code || invoiceDoc?.currency_code || "kes";
207
+ // Summary of included types
208
+ const uniqueTypes = Array.from(new Set(clusterDocs.map((d) => getDocTypeLabel(d.type))));
209
+ const includedTypesSummary = uniqueTypes.join(" • ");
210
+ // Latest created_at timestamp
211
+ const latestCreatedAt = clusterDocs.reduce((latest, d) => {
212
+ return new Date(d.created_at) > new Date(latest) ? d.created_at : latest;
213
+ }, clusterDocs[0].created_at);
214
+ // Build human-friendly groupKey
215
+ let groupKey = primaryDoc.display_id;
216
+ if (primaryDoc.repair_ticket_id) {
217
+ groupKey = `Repair ${primaryDoc.display_id}`;
218
+ }
219
+ else if (primaryDoc.order_id) {
220
+ groupKey = `Order #${primaryDoc.display_id}`;
221
+ }
222
+ groups.push({
223
+ groupId: clusterId,
224
+ groupKey,
225
+ primaryDoc,
226
+ documents: clusterDocs,
227
+ hasInvoice,
228
+ totalAmount,
229
+ currencyCode,
230
+ customerName: primaryDoc.customer_name || "Valued Customer",
231
+ customerEmail: primaryDoc.customer_email || clusterDocs.find((d) => d.customer_email)?.customer_email,
232
+ customerPhone: primaryDoc.customer_phone || clusterDocs.find((d) => d.customer_phone)?.customer_phone,
233
+ latestCreatedAt,
234
+ includedTypesSummary,
235
+ });
236
+ }
237
+ // Sort groups by latest activity (most recent first)
238
+ groups.sort((a, b) => new Date(b.latestCreatedAt).getTime() - new Date(a.latestCreatedAt).getTime());
239
+ return groups;
240
+ }
241
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZG9jdW1lbnQtZ3JvdXBlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy91dGlscy9kb2N1bWVudC1ncm91cGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBaURBLG9EQU1DO0FBTUQsZ0RBaUJDO0FBTUQsb0RBaUJDO0FBS0QsMENBaUJDO0FBUUQsd0NBbUtDO0FBaFFEOzs7Ozs7Ozs7O0dBVUc7QUFDSCxTQUFnQixvQkFBb0IsQ0FBQyxTQUFrQjtJQUNyRCxJQUFJLENBQUMsU0FBUztRQUFFLE9BQU8sRUFBRSxDQUFDO0lBQzFCLE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUN6Qyx3RUFBd0U7SUFDeEUsTUFBTSxhQUFhLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyx5Q0FBeUMsRUFBRSxFQUFFLENBQUMsQ0FBQztJQUNyRixPQUFPLGFBQWEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUM5QixDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsU0FBZ0Isa0JBQWtCLENBQUMsSUFBYTtJQUM5QyxRQUFRLElBQUksRUFBRSxXQUFXLEVBQUUsRUFBRSxDQUFDO1FBQzVCLEtBQUssU0FBUztZQUNaLE9BQU8sR0FBRyxDQUFDLENBQUMsc0NBQXNDO1FBQ3BELEtBQUssVUFBVTtZQUNiLE9BQU8sRUFBRSxDQUFDO1FBQ1osS0FBSyxVQUFVO1lBQ2IsT0FBTyxFQUFFLENBQUM7UUFDWixLQUFLLFNBQVM7WUFDWixPQUFPLEVBQUUsQ0FBQztRQUNaLEtBQUssY0FBYztZQUNqQixPQUFPLEVBQUUsQ0FBQztRQUNaLEtBQUssUUFBUTtZQUNYLE9BQU8sRUFBRSxDQUFDO1FBQ1o7WUFDRSxPQUFPLEVBQUUsQ0FBQztJQUNkLENBQUM7QUFDSCxDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsU0FBZ0Isb0JBQW9CLENBQUMsSUFBYTtJQUNoRCxRQUFRLElBQUksRUFBRSxXQUFXLEVBQUUs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
package/CHANGELOG.md CHANGED
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.1.16] - 2026-10-16
11
+
12
+ ### Group & Pack Related Documents by Job / Order
13
+
14
+ - **Packed Document Rows by Job / Order**:
15
+ - Automatically clusters related documents belonging to the same repair or order into a single collapsed master row to dramatically save vertical space.
16
+ - Multi-tier correlation algorithm (`src/utils/document-grouper.ts`):
17
+ - Correlates by `repair_ticket_id`
18
+ - Correlates by `order_id`
19
+ - Correlates by normalized base reference number suffix (e.g. `1792156525673-SNL9QH` shared between `JOB-`, `RT-`, `EST-`, `RCPT-`, and `1001` shared between `INV-`, `RCPT-`, `PKG-`).
20
+ - **Primary Invoice Priority in Collapsed View**:
21
+ - If an **Invoice** is present in the pack, the collapsed master row prominently displays the **Invoice** display ID, amount, and payment status.
22
+ - If an invoice is not yet generated, primary representation gracefully falls back to Quote/Estimate or Job Card.
23
+ - Reference column features a count badge (e.g. `4 docs`) and an expandable chevron (`▼` / `▶`).
24
+ - Type column shows a clear summary of included documents: `Includes: Job Card • Quote • Invoice • Receipt`.
25
+ - **Inline Accordion Sub-Table & Quick Dropdown Menu**:
26
+ - Clicking the chevron or row expands an indented nested sub-table directly below the parent row with distinct color-coded badges for every document in the workflow:
27
+ - `Job Card` (orange)
28
+ - `Quote / Estimate` (blue)
29
+ - `Tax Invoice` (green / purple)
30
+ - `Payment Receipt` (emerald / purple)
31
+ - `Packing Slip` (amber / grey)
32
+ - Every child document includes clickable PDF view/download, status badge, amount, source, created date, and individual actions (View, Send, Push Zoho, Copy shortlink).
33
+ - Added a "Related Docs" quick-action dropdown menu (`Docs (4) ▼`) directly on the master row for rapid one-click access.
34
+ - **Pack by Job / Order Toolbar Switch**:
35
+ - Added a toggle switch in the Admin Hub toolbar to easily switch between the packed view (enabled by default) and the flat document list anytime.
36
+ - **Comprehensive Test Suite**:
37
+ - Added `tests/document-grouping.test.ts` validating base reference extraction, priority scoring, multi-tier clustering, and lifecycle ordering (48/48 unit tests passing).
38
+
39
+ ---
40
+
10
41
  ## [1.1.14] - 2026-10-16
11
42
 
12
43
  ### Company Name Support, Shortlink 500 Fix, Duplicate Prefix Cleanup & Currency Improvements
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "medusajs-documents-plugin",
3
- "version": "1.1.14",
3
+ "version": "1.1.16",
4
4
  "description": "Comprehensive document generation and management module for Medusa v2 with Zoho Books integration, PDFKit fallback, QR codes, shortlinks, multi-channel notifications and unified admin hub.",
5
5
  "author": "Urban Device Care <info@urbandevicecare.co.uk>",
6
6
  "license": "MIT",