@aglyn/plugins-commerce 1.0.0-beta.289 → 1.0.0-beta.291

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.
Files changed (140) hide show
  1. package/README.md +4 -2
  2. package/package.json +13 -13
  3. package/src/lib/components/commerce-console-page.js +2 -1
  4. package/src/lib/components/commerce-console-page.js.map +1 -1
  5. package/src/lib/components/console/commerce-pdf-download.d.ts +30 -0
  6. package/src/lib/components/console/commerce-pdf-download.js +53 -0
  7. package/src/lib/components/console/commerce-pdf-download.js.map +1 -0
  8. package/src/lib/components/console/host-orders-card.component.d.ts +6 -0
  9. package/src/lib/components/console/host-orders-card.component.js +3 -2
  10. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  11. package/src/lib/components/console/host-quotes-card.component.js +20 -4
  12. package/src/lib/components/console/host-quotes-card.component.js.map +1 -1
  13. package/src/lib/components/console/invoice-send.component.d.ts +44 -0
  14. package/src/lib/components/console/invoice-send.component.js +237 -0
  15. package/src/lib/components/console/invoice-send.component.js.map +1 -0
  16. package/src/lib/components/console/order-detail-dialog.component.d.ts +5 -0
  17. package/src/lib/components/console/order-detail-dialog.component.js +9 -2
  18. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  19. package/src/lib/components/console/order-invoice.component.d.ts +15 -4
  20. package/src/lib/components/console/order-invoice.component.js +93 -12
  21. package/src/lib/components/console/order-invoice.component.js.map +1 -1
  22. package/src/lib/components/console/quote-editor-dialog.component.d.ts +29 -0
  23. package/src/lib/components/console/quote-editor-dialog.component.js +154 -5
  24. package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -1
  25. package/src/lib/components/console/quote-proofs.component.d.ts +48 -0
  26. package/src/lib/components/console/quote-proofs.component.js +694 -0
  27. package/src/lib/components/console/quote-proofs.component.js.map +1 -0
  28. package/src/lib/components/console/quote-send.component.d.ts +42 -0
  29. package/src/lib/components/console/quote-send.component.js +307 -0
  30. package/src/lib/components/console/quote-send.component.js.map +1 -0
  31. package/src/lib/components/console/quote-start.component.d.ts +45 -0
  32. package/src/lib/components/console/quote-start.component.js +182 -0
  33. package/src/lib/components/console/quote-start.component.js.map +1 -0
  34. package/src/lib/components/product-options-field-body.d.ts +44 -0
  35. package/src/lib/components/product-options-field-body.js +260 -0
  36. package/src/lib/components/product-options-field-body.js.map +1 -0
  37. package/src/lib/components/product-options-field.d.ts +32 -0
  38. package/src/lib/components/product-options-field.js +128 -0
  39. package/src/lib/components/product-options-field.js.map +1 -0
  40. package/src/lib/components/quote-page-body.js +67 -20
  41. package/src/lib/components/quote-page-body.js.map +1 -1
  42. package/src/lib/components/quote-proof-panel.d.ts +42 -0
  43. package/src/lib/components/quote-proof-panel.js +436 -0
  44. package/src/lib/components/quote-proof-panel.js.map +1 -0
  45. package/src/lib/model/commerce-invoices.d.ts +132 -0
  46. package/src/lib/model/commerce-invoices.js +332 -0
  47. package/src/lib/model/commerce-invoices.js.map +1 -0
  48. package/src/lib/model/commerce-orders.d.ts +29 -2
  49. package/src/lib/model/commerce-orders.js.map +1 -1
  50. package/src/lib/model/commerce-product-field.d.ts +23 -0
  51. package/src/lib/model/commerce-product-field.js +26 -0
  52. package/src/lib/model/commerce-product-field.js.map +1 -0
  53. package/src/lib/model/commerce-quote-email.d.ts +67 -0
  54. package/src/lib/model/commerce-quote-email.js +154 -0
  55. package/src/lib/model/commerce-quote-email.js.map +1 -0
  56. package/src/lib/model/commerce-quote-pay.d.ts +76 -6
  57. package/src/lib/model/commerce-quote-pay.js +105 -39
  58. package/src/lib/model/commerce-quote-pay.js.map +1 -1
  59. package/src/lib/model/commerce-quote-proofs.d.ts +159 -0
  60. package/src/lib/model/commerce-quote-proofs.js +348 -0
  61. package/src/lib/model/commerce-quote-proofs.js.map +1 -0
  62. package/src/lib/model/commerce-quote-request.d.ts +93 -0
  63. package/src/lib/model/commerce-quote-request.js +207 -0
  64. package/src/lib/model/commerce-quote-request.js.map +1 -0
  65. package/src/lib/model/commerce-quotes.d.ts +140 -3
  66. package/src/lib/model/commerce-quotes.js.map +1 -1
  67. package/src/lib/model/index.d.ts +4 -0
  68. package/src/lib/model/index.js +4 -0
  69. package/src/lib/model/index.js.map +1 -1
  70. package/src/lib/plugin.js +19 -0
  71. package/src/lib/plugin.js.map +1 -1
  72. package/src/lib/server/billing-webhook.js +115 -70
  73. package/src/lib/server/billing-webhook.js.map +1 -1
  74. package/src/lib/server/commerce-pdf.d.ts +104 -0
  75. package/src/lib/server/commerce-pdf.js +310 -0
  76. package/src/lib/server/commerce-pdf.js.map +1 -0
  77. package/src/lib/server/draft-order.js +23 -3
  78. package/src/lib/server/draft-order.js.map +1 -1
  79. package/src/lib/server/invoice-email.d.ts +76 -0
  80. package/src/lib/server/invoice-email.js +183 -0
  81. package/src/lib/server/invoice-email.js.map +1 -0
  82. package/src/lib/server/order-invoice-send.d.ts +43 -0
  83. package/src/lib/server/order-invoice-send.js +218 -0
  84. package/src/lib/server/order-invoice-send.js.map +1 -0
  85. package/src/lib/server/order-print.d.ts +6 -0
  86. package/src/lib/server/order-print.js +22 -5
  87. package/src/lib/server/order-print.js.map +1 -1
  88. package/src/lib/server/order-status.d.ts +11 -0
  89. package/src/lib/server/order-status.js +44 -8
  90. package/src/lib/server/order-status.js.map +1 -1
  91. package/src/lib/server/pdf-response.d.ts +31 -0
  92. package/src/lib/server/pdf-response.js +30 -0
  93. package/src/lib/server/pdf-response.js.map +1 -0
  94. package/src/lib/server/person-eraser.d.ts +3 -2
  95. package/src/lib/server/person-eraser.js +25 -6
  96. package/src/lib/server/person-eraser.js.map +1 -1
  97. package/src/lib/server/pos-reader-splash.d.ts +2 -18
  98. package/src/lib/server/pos-reader-splash.js +8 -93
  99. package/src/lib/server/pos-reader-splash.js.map +1 -1
  100. package/src/lib/server/quote-email.d.ts +85 -0
  101. package/src/lib/server/quote-email.js +211 -0
  102. package/src/lib/server/quote-email.js.map +1 -0
  103. package/src/lib/server/quote-link-token.d.ts +11 -0
  104. package/src/lib/server/quote-link-token.js +21 -0
  105. package/src/lib/server/quote-link-token.js.map +1 -1
  106. package/src/lib/server/quote-pay.d.ts +38 -6
  107. package/src/lib/server/quote-pay.js +115 -53
  108. package/src/lib/server/quote-pay.js.map +1 -1
  109. package/src/lib/server/quote-proofs.d.ts +51 -0
  110. package/src/lib/server/quote-proofs.js +500 -0
  111. package/src/lib/server/quote-proofs.js.map +1 -0
  112. package/src/lib/server/quote-uploads.d.ts +33 -0
  113. package/src/lib/server/quote-uploads.js +29 -0
  114. package/src/lib/server/quote-uploads.js.map +1 -0
  115. package/src/lib/server/quote.d.ts +25 -0
  116. package/src/lib/server/quote.js +638 -8
  117. package/src/lib/server/quote.js.map +1 -1
  118. package/src/lib/server/site-image-source.d.ts +38 -0
  119. package/src/lib/server/site-image-source.js +130 -0
  120. package/src/lib/server/site-image-source.js.map +1 -0
  121. package/src/lib/server/site-page-resolver.js +2 -1
  122. package/src/lib/server/site-page-resolver.js.map +1 -1
  123. package/src/lib/server.js +24 -1
  124. package/src/lib/server.js.map +1 -1
  125. package/src/lib/site.js +8 -0
  126. package/src/lib/site.js.map +1 -1
  127. package/src/lib/tenant-emails.js +243 -0
  128. package/src/lib/tenant-emails.js.map +1 -1
  129. package/src/lib/utils/commerce-document-pdf.d.ts +82 -0
  130. package/src/lib/utils/commerce-document-pdf.js +613 -0
  131. package/src/lib/utils/commerce-document-pdf.js.map +1 -0
  132. package/src/lib/utils/invoice-email-content.d.ts +45 -0
  133. package/src/lib/utils/invoice-email-content.js +126 -0
  134. package/src/lib/utils/invoice-email-content.js.map +1 -0
  135. package/src/lib/utils/order-invoice.d.ts +52 -3
  136. package/src/lib/utils/order-invoice.js +124 -64
  137. package/src/lib/utils/order-invoice.js.map +1 -1
  138. package/src/lib/utils/quote-email-content.d.ts +62 -0
  139. package/src/lib/utils/quote-email-content.js +154 -0
  140. package/src/lib/utils/quote-email-content.js.map +1 -0
@@ -0,0 +1,207 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
+ /*==========================================
18
+ * A QUOTE STARTED FROM A REQUEST (AGL-4098).
19
+ *
20
+ * A "Request a quote" form sends a submission the forms plugin keeps; the
21
+ * Inbox shows it, and this plugin answers it with a quote. Neither plugin
22
+ * imports the other: the request is read through the core's record-facts
23
+ * seam ({@link QUOTE_REQUEST_FACTS_RESOURCE}, the forms plugin's reader), and
24
+ * the product a customer picked rides the submission as plain strings the
25
+ * Product and options field posted ({@link parseProductFieldValue}).
26
+ *
27
+ * What a quote takes from a request, all of it server-read:
28
+ *
29
+ * - the CUSTOMER: name, email, phone and company, as the form gave them;
30
+ * - a LINE for the product and its options, when the form had the field;
31
+ * - the NOTES: the request's own words;
32
+ * - its FILES, kept where the request keeps them and listed on the quote as
33
+ * the customer's files (`quote.requestFiles`), downloaded through the
34
+ * quote's own gated route;
35
+ * - the REQUEST itself (`quote.request`): which submission, which form, when;
36
+ * - the CONVERSATION (`quote.emailThread`): the thread the request's
37
+ * confirmation email started, so the quote's first email replies into it.
38
+ *=========================================*/ /** The resource the forms plugin reads a submission's facts under, on the core's seam. */ export const QUOTE_REQUEST_FACTS_RESOURCE = 'forms.submission';
39
+ const ID = /^(?!__.*__$)[^/]{1,200}$/;
40
+ const MAX_OPTIONS = 20;
41
+ const MAX_MODIFIERS = 50;
42
+ const text = (value, max = 200)=>typeof value === 'string' ? value.trim().slice(0, max) : '';
43
+ /** The compact value the field posts. */ export function encodeProductFieldValue(value) {
44
+ var _value_modifiers;
45
+ return JSON.stringify(_extends({
46
+ productId: value.productId
47
+ }, value.variantId ? {
48
+ variantId: value.variantId
49
+ } : {}, value.options && Object.keys(value.options).length ? {
50
+ options: value.options
51
+ } : {}, ((_value_modifiers = value.modifiers) == null ? void 0 : _value_modifiers.length) ? {
52
+ modifiers: value.modifiers
53
+ } : {}));
54
+ }
55
+ /**
56
+ * The field's posted value, read back, or null for anything that is not one:
57
+ * malformed JSON, no product id, ids that cannot be document ids. The options
58
+ * keep at most twenty names and values of up to 200 characters.
59
+ */ export function parseProductFieldValue(raw) {
60
+ if (typeof raw !== 'string' || !raw.trim() || raw.length > 4000) return null;
61
+ let parsed;
62
+ try {
63
+ parsed = JSON.parse(raw);
64
+ } catch (unused) {
65
+ return null;
66
+ }
67
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null;
68
+ const source = parsed;
69
+ const productId = text(source['productId']);
70
+ if (!ID.test(productId)) return null;
71
+ const variantId = text(source['variantId']);
72
+ const options = {};
73
+ const rawOptions = source['options'];
74
+ if (rawOptions && typeof rawOptions === 'object' && !Array.isArray(rawOptions)) {
75
+ for (const [name, value] of Object.entries(rawOptions).slice(0, MAX_OPTIONS)){
76
+ const key = text(name);
77
+ const picked = text(value);
78
+ if (key && picked) options[key] = picked;
79
+ }
80
+ }
81
+ const modifiers = Array.isArray(source['modifiers']) ? source['modifiers'].slice(0, MAX_MODIFIERS).map((entry)=>{
82
+ const pick = entry != null ? entry : {};
83
+ return {
84
+ groupId: text(pick['groupId']),
85
+ optionId: text(pick['optionId'])
86
+ };
87
+ }).filter((pick)=>pick.groupId && pick.optionId) : [];
88
+ return _extends({
89
+ productId
90
+ }, variantId && ID.test(variantId) ? {
91
+ variantId
92
+ } : {}, Object.keys(options).length ? {
93
+ options
94
+ } : {}, modifiers.length ? {
95
+ modifiers
96
+ } : {});
97
+ }
98
+ /** The readable line the field posts under its name: the product, then its options. */ export function productFieldLabel(productName, options) {
99
+ const picked = Object.values(options != null ? options : {}).filter(Boolean);
100
+ return picked.length ? `${productName} — ${picked.join(' / ')}` : productName;
101
+ }
102
+ /** A request's facts as the seam answered them, read defensively. */ export function readQuoteRequestFacts(raw) {
103
+ var _raw_person, _raw_request, _raw_emailThread, _raw_capturedRecord, _raw_fieldData;
104
+ if (!raw || raw['record'] !== 'formSubmission') return null;
105
+ const person = (_raw_person = raw['person']) != null ? _raw_person : {};
106
+ const request = (_raw_request = raw['request']) != null ? _raw_request : null;
107
+ const files = Array.isArray(raw['files']) ? raw['files'] : [];
108
+ const thread = (_raw_emailThread = raw['emailThread']) != null ? _raw_emailThread : null;
109
+ const captured = (_raw_capturedRecord = raw['capturedRecord']) != null ? _raw_capturedRecord : null;
110
+ const references = Array.isArray(thread == null ? void 0 : thread['references']) ? (thread == null ? void 0 : thread['references']).map(String) : [];
111
+ return {
112
+ hostId: text(raw['hostId']),
113
+ formName: text(raw['formName']) || 'Form',
114
+ createdAtMs: typeof raw['createdAtMs'] === 'number' ? raw['createdAtMs'] : null,
115
+ requestKind: typeof (request == null ? void 0 : request.kind) === 'string' ? request.kind : null,
116
+ person: {
117
+ name: text(person['name']),
118
+ email: text(person['email']),
119
+ phone: text(person['phone'], 40),
120
+ company: text(person['company'])
121
+ },
122
+ capturedRecord: ((captured == null ? void 0 : captured.kind) === 'lead' || (captured == null ? void 0 : captured.kind) === 'contact') && ID.test(text(captured.id)) ? {
123
+ kind: captured.kind,
124
+ id: text(captured.id)
125
+ } : null,
126
+ message: text(raw['message'], 5000),
127
+ fieldData: Object.fromEntries(Object.entries((_raw_fieldData = raw['fieldData']) != null ? _raw_fieldData : {}).filter((entry)=>typeof entry[1] === 'string').map(([key, value])=>[
128
+ key,
129
+ value.slice(0, 4000)
130
+ ])),
131
+ files: files.slice(0, 10).flatMap((entry)=>{
132
+ const file = entry != null ? entry : {};
133
+ const id = text(file['id'], 128);
134
+ const path = text(file['path'], 500);
135
+ if (!id || !path.startsWith('hosts/') || !text(file['name'])) return [];
136
+ return [
137
+ {
138
+ id,
139
+ name: text(file['name']),
140
+ contentType: text(file['contentType'], 100),
141
+ size: Number(file['size']) || 0,
142
+ path
143
+ }
144
+ ];
145
+ }),
146
+ emailThread: thread && typeof thread['messageId'] === 'string' && typeof thread['subject'] === 'string' ? {
147
+ messageId: thread['messageId'],
148
+ references,
149
+ subject: thread['subject']
150
+ } : null
151
+ };
152
+ }
153
+ /** The product lines a request's Product and options fields ask for, one per field. */ export function quoteLinesFromFieldData(fieldData) {
154
+ const lines = [];
155
+ for (const raw of Object.values(fieldData)){
156
+ var _value_modifiers;
157
+ const value = parseProductFieldValue(raw);
158
+ if (!value) continue;
159
+ lines.push(_extends({
160
+ kind: 'catalog',
161
+ productId: value.productId
162
+ }, value.variantId ? {
163
+ variantId: value.variantId
164
+ } : {}, {
165
+ quantity: 1
166
+ }, ((_value_modifiers = value.modifiers) == null ? void 0 : _value_modifiers.length) ? {
167
+ modifiers: value.modifiers
168
+ } : {}));
169
+ }
170
+ return lines;
171
+ }
172
+ /** A quote's start from a request: the editor's prefill, and what the server stamps on the quote. */ export function quoteStartFromRequest(submissionId, facts) {
173
+ return {
174
+ prefill: {
175
+ customer: _extends({}, facts.capturedRecord ? {
176
+ record: facts.capturedRecord
177
+ } : {}, facts.person.name ? {
178
+ name: facts.person.name
179
+ } : {}, facts.person.email ? {
180
+ email: facts.person.email
181
+ } : {}, facts.person.phone ? {
182
+ phone: facts.person.phone
183
+ } : {}, facts.person.company ? {
184
+ company: facts.person.company
185
+ } : {}),
186
+ lines: quoteLinesFromFieldData(facts.fieldData),
187
+ notes: facts.message
188
+ },
189
+ request: {
190
+ submissionId,
191
+ formName: facts.formName,
192
+ receivedAtMs: facts.createdAtMs
193
+ },
194
+ files: facts.files,
195
+ emailThread: facts.emailThread
196
+ };
197
+ }
198
+ /** A request's files as the console may see them: no storage path. */ export function quoteRequestFileViews(files) {
199
+ return (files != null ? files : []).map(({ id, name, contentType, size })=>({
200
+ id,
201
+ name,
202
+ contentType,
203
+ size
204
+ }));
205
+ }
206
+
207
+ //# sourceMappingURL=commerce-quote-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-quote-request.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { QuoteCatalogLineRequest, QuoteCustomer, QuoteEmailThread, QuoteResponseFile } from './commerce-quotes'\n\n/*==========================================\n * A QUOTE STARTED FROM A REQUEST (AGL-4098).\n *\n * A \"Request a quote\" form sends a submission the forms plugin keeps; the\n * Inbox shows it, and this plugin answers it with a quote. Neither plugin\n * imports the other: the request is read through the core's record-facts\n * seam ({@link QUOTE_REQUEST_FACTS_RESOURCE}, the forms plugin's reader), and\n * the product a customer picked rides the submission as plain strings the\n * Product and options field posted ({@link parseProductFieldValue}).\n *\n * What a quote takes from a request, all of it server-read:\n *\n * - the CUSTOMER: name, email, phone and company, as the form gave them;\n * - a LINE for the product and its options, when the form had the field;\n * - the NOTES: the request's own words;\n * - its FILES, kept where the request keeps them and listed on the quote as\n * the customer's files (`quote.requestFiles`), downloaded through the\n * quote's own gated route;\n * - the REQUEST itself (`quote.request`): which submission, which form, when;\n * - the CONVERSATION (`quote.emailThread`): the thread the request's\n * confirmation email started, so the quote's first email replies into it.\n *=========================================*/\n\n/** The resource the forms plugin reads a submission's facts under, on the core's seam. */\nexport const QUOTE_REQUEST_FACTS_RESOURCE = 'forms.submission'\n\n/** The request a quote was started from. */\nexport interface QuoteRequestRef {\n submissionId: string\n formName: string\n /** When the request arrived, epoch ms; null when the submission did not say. */\n receivedAtMs: number | null\n}\n\n/**\n * What the Product and options field posts beside its readable line, under\n * `{name}__data`: the product, the variant the options make, and the options\n * as picked. Add-ons are not taken from the storefront yet; when they are,\n * they ride here as `modifiers` and the quote line prices them as the editor\n * does.\n */\nexport interface ProductFieldValue {\n productId: string\n variantId?: string\n options?: Record<string, string>\n modifiers?: Array<{ groupId: string; optionId: string }>\n}\n\nconst ID = /^(?!__.*__$)[^/]{1,200}$/\nconst MAX_OPTIONS = 20\nconst MAX_MODIFIERS = 50\n\nconst text = (value: unknown, max = 200): string => (typeof value === 'string' ? value.trim().slice(0, max) : '')\n\n/** The compact value the field posts. */\nexport function encodeProductFieldValue(value: ProductFieldValue): string {\n return JSON.stringify({\n productId: value.productId,\n ...(value.variantId ? { variantId: value.variantId } : {}),\n ...(value.options && Object.keys(value.options).length ? { options: value.options } : {}),\n ...(value.modifiers?.length ? { modifiers: value.modifiers } : {}),\n })\n}\n\n/**\n * The field's posted value, read back, or null for anything that is not one:\n * malformed JSON, no product id, ids that cannot be document ids. The options\n * keep at most twenty names and values of up to 200 characters.\n */\nexport function parseProductFieldValue(raw: unknown): ProductFieldValue | null {\n if (typeof raw !== 'string' || !raw.trim() || raw.length > 4000) return null\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return null\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null\n const source = parsed as Record<string, unknown>\n const productId = text(source['productId'])\n if (!ID.test(productId)) return null\n const variantId = text(source['variantId'])\n const options: Record<string, string> = {}\n const rawOptions = source['options']\n if (rawOptions && typeof rawOptions === 'object' && !Array.isArray(rawOptions)) {\n for (const [name, value] of Object.entries(rawOptions as Record<string, unknown>).slice(0, MAX_OPTIONS)) {\n const key = text(name)\n const picked = text(value)\n if (key && picked) options[key] = picked\n }\n }\n const modifiers = Array.isArray(source['modifiers'])\n ? (source['modifiers'] as unknown[])\n .slice(0, MAX_MODIFIERS)\n .map((entry) => {\n const pick = (entry ?? {}) as Record<string, unknown>\n return { groupId: text(pick['groupId']), optionId: text(pick['optionId']) }\n })\n .filter((pick) => pick.groupId && pick.optionId)\n : []\n return {\n productId,\n ...(variantId && ID.test(variantId) ? { variantId } : {}),\n ...(Object.keys(options).length ? { options } : {}),\n ...(modifiers.length ? { modifiers } : {}),\n }\n}\n\n/** The readable line the field posts under its name: the product, then its options. */\nexport function productFieldLabel(productName: string, options: Record<string, string> | undefined): string {\n const picked = Object.values(options ?? {}).filter(Boolean)\n return picked.length ? `${productName} — ${picked.join(' / ')}` : productName\n}\n\n/** What this plugin reads of a request's facts (the forms plugin's reader documents them). */\nexport interface QuoteRequestFacts {\n hostId: string\n formName: string\n createdAtMs: number | null\n requestKind: string | null\n person: { name: string; email: string; phone: string; company: string }\n /** The lead or contact the request was filed as, which the quote's customer links to. */\n capturedRecord: { kind: string; id: string } | null\n message: string\n fieldData: Record<string, string>\n files: Array<QuoteResponseFile>\n emailThread: QuoteEmailThread | null\n}\n\n/** A request's facts as the seam answered them, read defensively. */\nexport function readQuoteRequestFacts(raw: Readonly<Record<string, unknown>> | null | undefined): QuoteRequestFacts | null {\n if (!raw || raw['record'] !== 'formSubmission') return null\n const person = (raw['person'] ?? {}) as Record<string, unknown>\n const request = (raw['request'] ?? null) as { kind?: unknown } | null\n const files = Array.isArray(raw['files']) ? (raw['files'] as unknown[]) : []\n const thread = (raw['emailThread'] ?? null) as Record<string, unknown> | null\n const captured = (raw['capturedRecord'] ?? null) as { kind?: unknown; id?: unknown } | null\n const references = Array.isArray(thread?.['references']) ? (thread?.['references'] as unknown[]).map(String) : []\n return {\n hostId: text(raw['hostId']),\n formName: text(raw['formName']) || 'Form',\n createdAtMs: typeof raw['createdAtMs'] === 'number' ? raw['createdAtMs'] : null,\n requestKind: typeof request?.kind === 'string' ? request.kind : null,\n person: {\n name: text(person['name']),\n email: text(person['email']),\n phone: text(person['phone'], 40),\n company: text(person['company']),\n },\n capturedRecord:\n (captured?.kind === 'lead' || captured?.kind === 'contact') && ID.test(text(captured.id))\n ? { kind: captured.kind, id: text(captured.id) }\n : null,\n message: text(raw['message'], 5000),\n fieldData: Object.fromEntries(\n Object.entries((raw['fieldData'] ?? {}) as Record<string, unknown>)\n .filter((entry): entry is [string, string] => typeof entry[1] === 'string')\n .map(([key, value]) => [key, value.slice(0, 4000)]),\n ),\n files: files.slice(0, 10).flatMap((entry) => {\n const file = (entry ?? {}) as Record<string, unknown>\n const id = text(file['id'], 128)\n const path = text(file['path'], 500)\n if (!id || !path.startsWith('hosts/') || !text(file['name'])) return []\n return [\n {\n id,\n name: text(file['name']),\n contentType: text(file['contentType'], 100),\n size: Number(file['size']) || 0,\n path,\n },\n ]\n }),\n emailThread:\n thread && typeof thread['messageId'] === 'string' && typeof thread['subject'] === 'string'\n ? { messageId: thread['messageId'], references, subject: thread['subject'] }\n : null,\n }\n}\n\n/** What the quote editor opens with when a quote is started from a request. */\nexport interface QuoteStartPrefill {\n customer: Pick<QuoteCustomer, 'record' | 'name' | 'email' | 'phone' | 'company'>\n lines: QuoteCatalogLineRequest[]\n notes: string\n}\n\n/** The product lines a request's Product and options fields ask for, one per field. */\nexport function quoteLinesFromFieldData(fieldData: Readonly<Record<string, string>>): QuoteCatalogLineRequest[] {\n const lines: QuoteCatalogLineRequest[] = []\n for (const raw of Object.values(fieldData)) {\n const value = parseProductFieldValue(raw)\n if (!value) continue\n lines.push({\n kind: 'catalog',\n productId: value.productId,\n ...(value.variantId ? { variantId: value.variantId } : {}),\n quantity: 1,\n ...(value.modifiers?.length ? { modifiers: value.modifiers } : {}),\n })\n }\n return lines\n}\n\n/** A quote's start from a request: the editor's prefill, and what the server stamps on the quote. */\nexport function quoteStartFromRequest(\n submissionId: string,\n facts: QuoteRequestFacts,\n): {\n prefill: QuoteStartPrefill\n request: QuoteRequestRef\n files: QuoteResponseFile[]\n emailThread: QuoteEmailThread | null\n} {\n return {\n prefill: {\n customer: {\n ...(facts.capturedRecord ? { record: facts.capturedRecord } : {}),\n ...(facts.person.name ? { name: facts.person.name } : {}),\n ...(facts.person.email ? { email: facts.person.email } : {}),\n ...(facts.person.phone ? { phone: facts.person.phone } : {}),\n ...(facts.person.company ? { company: facts.person.company } : {}),\n },\n lines: quoteLinesFromFieldData(facts.fieldData),\n notes: facts.message,\n },\n request: { submissionId, formName: facts.formName, receivedAtMs: facts.createdAtMs },\n files: facts.files,\n emailThread: facts.emailThread,\n }\n}\n\n/** A request's files as the console may see them: no storage path. */\nexport function quoteRequestFileViews(files: readonly QuoteResponseFile[] | null | undefined): Array<Omit<QuoteResponseFile, 'path'>> {\n return (files ?? []).map(({ id, name, contentType, size }) => ({ id, name, contentType, size }))\n}\n"],"names":["QUOTE_REQUEST_FACTS_RESOURCE","ID","MAX_OPTIONS","MAX_MODIFIERS","text","value","max","trim","slice","encodeProductFieldValue","JSON","stringify","productId","variantId","options","Object","keys","length","modifiers","parseProductFieldValue","raw","parsed","parse","Array","isArray","source","test","rawOptions","name","entries","key","picked","map","entry","pick","groupId","optionId","filter","productFieldLabel","productName","values","Boolean","join","readQuoteRequestFacts","person","request","files","thread","captured","references","String","hostId","formName","createdAtMs","requestKind","kind","email","phone","company","capturedRecord","id","message","fieldData","fromEntries","flatMap","file","path","startsWith","contentType","size","Number","emailThread","messageId","subject","quoteLinesFromFieldData","lines","push","quantity","quoteStartFromRequest","submissionId","facts","prefill","customer","record","notes","receivedAtMs","quoteRequestFileViews"],"mappings":"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"}
@@ -15,6 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import type { HostProduct } from './commerce';
18
+ import type { QuoteRequestRef } from './commerce-quote-request';
18
19
  import type { HostOrder, OrderAddress, OrderLineItem, OrderStatus, OrderTotals } from './commerce-orders';
19
20
  import { type TaxSettings } from './commerce-tax';
20
21
  import { type StorefrontTaxMode } from './commerce-tax-decision';
@@ -67,11 +68,60 @@ export interface QuoteTaxBasis {
67
68
  providerLabel?: string;
68
69
  reason?: QuoteNoTaxReason;
69
70
  }
70
- /** A proof attached to one revision of a quote. Written by the proofs feature. */
71
+ /** A proof attached to one revision of a quote: what names it, and the revision it is for. */
71
72
  export interface QuoteProofRef {
72
73
  id: string;
73
74
  revision: number;
74
75
  }
76
+ /**
77
+ * A proof the business attached to a revision (AGL-4096): an image or a
78
+ * design file in the site's private storage, under the quote.
79
+ */
80
+ export interface QuoteProof extends QuoteProofRef {
81
+ name: string;
82
+ contentType: string;
83
+ size: number;
84
+ /** The private Storage object. Never sent to the customer. */
85
+ path: string;
86
+ addedAtMs: number;
87
+ /** Who attached it: an email, else a uid. */
88
+ addedBy: string;
89
+ }
90
+ /** A file the customer sent with a response. */
91
+ export interface QuoteResponseFile {
92
+ id: string;
93
+ name: string;
94
+ contentType: string;
95
+ size: number;
96
+ /** The private Storage object. Never sent to the customer. */
97
+ path: string;
98
+ }
99
+ /**
100
+ * The customer's answer to a revision's proofs (AGL-4096): approved under a
101
+ * typed name, or changes asked for in a comment, with artwork if they had
102
+ * some. The address and browser are kept as the record of who answered.
103
+ */
104
+ export interface QuoteCustomerResponse {
105
+ action: 'approve' | 'changes';
106
+ revision: number;
107
+ atMs: number;
108
+ /** With `approve`: the name they typed. */
109
+ name?: string;
110
+ /** With `changes`: what to change. */
111
+ comment?: string;
112
+ /** With `changes`: files they sent. */
113
+ files?: QuoteResponseFile[];
114
+ ip: string | null;
115
+ userAgent: string | null;
116
+ }
117
+ /** The record of a revision's approval. */
118
+ export interface QuoteApproval {
119
+ revision: number;
120
+ name: string;
121
+ atMs: number;
122
+ ip: string | null;
123
+ userAgent: string | null;
124
+ }
75
125
  /**
76
126
  * A payment that reached a quote it could not pay: one made through a pay
77
127
  * link the business replaced or for a price it has since changed
@@ -86,6 +136,73 @@ export interface QuoteUnmatchedPayment {
86
136
  atMs: number;
87
137
  reason: 'superseded' | 'already-paid';
88
138
  }
139
+ /**
140
+ * The conversation the quote's emails keep in the customer's mailbox
141
+ * (AGL-4097): the latest message's `Message-ID`, every id in the
142
+ * conversation oldest first, and the subject it runs under. The shape of
143
+ * `EmailThread` (`@aglyn/shared-util-email`), spelled out so the model needs
144
+ * no email library. Started by the first email about the quote (a request's
145
+ * confirmation, or the quote itself); every later one replies into it.
146
+ */
147
+ export interface QuoteEmailThread {
148
+ messageId: string;
149
+ references: string[];
150
+ subject: string;
151
+ }
152
+ /** One time the quote was emailed to the customer (AGL-4097). */
153
+ export interface QuoteSendRecord {
154
+ /** The revision the email carried. */
155
+ revision: number;
156
+ to: string;
157
+ subject: string;
158
+ atMs: number;
159
+ /** Who sent it: an email, else a uid. */
160
+ by: string;
161
+ /** The `Message-ID` the email was sent with. */
162
+ messageId: string;
163
+ /** The mail provider's own id for the message, when it answered one. */
164
+ providerId: string | null;
165
+ /** The pay link version the email carried. */
166
+ linkVersion: number;
167
+ }
168
+ /** One shipping rate a draft order offered (AGL-4099), as checkout's resolved rates name it. */
169
+ export interface OrderDraftShippingRate {
170
+ rateId: string;
171
+ name: string;
172
+ amountCents: number;
173
+ }
174
+ /** The shipping a draft order offered when it was made (AGL-4099); see `HostOrder.draftShipping`. */
175
+ export interface OrderDraftShipping {
176
+ /** Where its rates ship; empty when it offers none. */
177
+ countries: string[];
178
+ /** The rates the buyer picks from at payment; empty when it ships nothing. */
179
+ options: OrderDraftShippingRate[];
180
+ }
181
+ /** The lasting pay link of an order that is not a quote (AGL-4099); see `HostOrder.payLink`. */
182
+ export interface OrderPayLink {
183
+ /** The link's version, absent reading as 1. */
184
+ version?: number;
185
+ /** Payments through the link the order could not take, as a quote records them. */
186
+ unmatchedPayments?: QuoteUnmatchedPayment[];
187
+ }
188
+ /** One time an order's (or a quote's) invoice was emailed to the customer (AGL-4099). */
189
+ export interface OrderInvoiceSendRecord {
190
+ to: string;
191
+ subject: string;
192
+ atMs: number;
193
+ /** Who sent it: an email, else a uid. */
194
+ by: string;
195
+ /** The `Message-ID` the email was sent with. */
196
+ messageId: string;
197
+ /** The mail provider's own id for the message, when it answered one. */
198
+ providerId: string | null;
199
+ /** The invoice said it was paid (the record), rather than asking for payment. */
200
+ paid: boolean;
201
+ /** The pay link version an unpaid invoice carried; null on a paid one. */
202
+ linkVersion: number | null;
203
+ /** The total the invoice showed. */
204
+ totalCents: number;
205
+ }
89
206
  /** What only a quote has, on its order document. */
90
207
  export interface OrderQuote {
91
208
  stage: QuoteStage;
@@ -101,7 +218,12 @@ export interface OrderQuote {
101
218
  tax: QuoteTaxBasis;
102
219
  /** The customer approves the current revision's proof before they can pay. Absent reads as off. */
103
220
  requireProofApproval?: boolean;
104
- proofs?: QuoteProofRef[];
221
+ /** The current revision's proofs; earlier revisions keep theirs in `revisions/{n}`. */
222
+ proofs?: QuoteProof[];
223
+ /** The customer's answers to the current revision, oldest first; the last one counts. */
224
+ responses?: QuoteCustomerResponse[];
225
+ /** The current revision's approval, while its latest answer is one. */
226
+ approval?: QuoteApproval | null;
105
227
  /** The revision the customer approved, when they have. */
106
228
  approvedRevision?: number | null;
107
229
  /**
@@ -110,6 +232,19 @@ export interface OrderQuote {
110
232
  * as 1. Bumped when the business replaces the link.
111
233
  */
112
234
  linkVersion?: number;
235
+ /** The conversation its emails keep in the customer's mailbox (AGL-4097). */
236
+ emailThread?: QuoteEmailThread | null;
237
+ /** Every time it was emailed to the customer, oldest first (AGL-4097). */
238
+ sends?: QuoteSendRecord[];
239
+ /**
240
+ * The request it was started from (AGL-4098): a "Request a quote" form's
241
+ * submission, by id. Its files are listed on the quote as the customer's
242
+ * ({@link requestFiles}), and its confirmation's conversation is the
243
+ * quote's {@link emailThread} until the quote is first sent.
244
+ */
245
+ request?: QuoteRequestRef | null;
246
+ /** The request's files, where the request keeps them; downloaded through the quote's own route. */
247
+ requestFiles?: QuoteResponseFile[];
113
248
  /** When the customer paid it. */
114
249
  paidAtMs?: number;
115
250
  /** Payments the quote could not take; see {@link QuoteUnmatchedPayment}. */
@@ -132,7 +267,9 @@ export declare function quoteRequiresProofApprovalByDefault(settings: QuoteStore
132
267
  * approval, its current revision has a proof, and that revision is not the
133
268
  * one the customer approved. A revision with no proof never holds a payment.
134
269
  */
135
- export declare function quotePaymentBlockedByApproval(quote: Pick<OrderQuote, 'requireProofApproval' | 'revision' | 'proofs' | 'approvedRevision'> | null | undefined): boolean;
270
+ export declare function quotePaymentBlockedByApproval(quote: (Pick<OrderQuote, 'requireProofApproval' | 'revision' | 'approvedRevision'> & {
271
+ proofs?: readonly QuoteProofRef[];
272
+ }) | null | undefined): boolean;
136
273
  /** Whether an order document is a quote, paid or not. */
137
274
  export declare function isQuote(order: Pick<Partial<HostOrder>, 'channel'> | null | undefined): boolean;
138
275
  /** Whether an order document is a quote nobody has paid yet, which no order list counts. */