@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
@@ -29,8 +29,37 @@ export interface QuoteEditorDialogProps {
29
29
  canEdit: boolean;
30
30
  /** Holds "Send quotes and invoices to customers" (`commerce.quotes.send`). */
31
31
  canSend: boolean;
32
+ /**
33
+ * What a NEW quote opens with when it is started from a request or a
34
+ * contact (AGL-4098). The editable fields are filled in; a request's own
35
+ * parts (which request, its files, its conversation) are stamped by the
36
+ * server from `from` when the quote is created, never from here.
37
+ */
38
+ start?: QuoteEditorStart | null;
32
39
  onClose: () => void;
33
40
  }
41
+ /** A new quote's start: from a request (with what it carried) or from a contact. */
42
+ export interface QuoteEditorStart {
43
+ /** The request it is started from, which the server reads again on create; null for a contact. */
44
+ from: {
45
+ kind: 'submission';
46
+ id: string;
47
+ } | null;
48
+ prefill: CommerceModel.QuoteStartPrefill;
49
+ /** The request, as the editor says where the quote came from. */
50
+ request?: {
51
+ formName: string;
52
+ receivedAtMs: number | null;
53
+ } | null;
54
+ files?: ReadonlyArray<{
55
+ id: string;
56
+ name: string;
57
+ contentType: string;
58
+ size: number;
59
+ }>;
60
+ /** Whether the request started an email conversation the quote's first email will reply into. */
61
+ threaded?: boolean;
62
+ }
34
63
  /**
35
64
  * The quote editor (AGL-4093): who it is for, its lines (products with their
36
65
  * options and add-ons, typed items, things included at no charge), discount,
@@ -27,7 +27,10 @@ import { useFirestore, useFirestoreDoc, useStoreSettingsHref, useUser } from "@a
27
27
  import { Alert, Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, FormControlLabel, List, ListItemButton, ListItemText, MenuItem, Paper, Stack, Switch, TextField, Typography } from "@mui/material";
28
28
  import { doc } from "firebase/firestore";
29
29
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
30
+ import { downloadCommercePdf } from "./commerce-pdf-download.js";
30
31
  import { QuotePayLinkSection } from "./quote-pay-link.component.js";
32
+ import { QuoteProofsSection, QuoteRequestFilesSection } from "./quote-proofs.component.js";
33
+ import { QuoteSendSection } from "./quote-send.component.js";
31
34
  const REFUSAL = commerceSitePermissionRefusal('commerce.quotes');
32
35
  const SEARCH_DEBOUNCE_MS = 250;
33
36
  const money = (cents)=>posMoney(cents);
@@ -112,6 +115,34 @@ function formFromQuote(quote, requireProofApproval) {
112
115
  requireProofApproval: section ? section.requireProofApproval === true : requireProofApproval
113
116
  };
114
117
  }
118
+ /** A new quote's form, filled in from where it was started (AGL-4098). */ function formFromStart(start, requireProofApproval) {
119
+ var _customer_record, _customer_name, _customer_company, _customer_email, _customer_phone;
120
+ const blank = formFromQuote(null, requireProofApproval);
121
+ const { customer, lines, notes } = start.prefill;
122
+ return _extends({}, blank, {
123
+ customer: {
124
+ record: (_customer_record = customer.record) != null ? _customer_record : null,
125
+ name: (_customer_name = customer.name) != null ? _customer_name : '',
126
+ company: (_customer_company = customer.company) != null ? _customer_company : '',
127
+ email: (_customer_email = customer.email) != null ? _customer_email : '',
128
+ phone: (_customer_phone = customer.phone) != null ? _customer_phone : ''
129
+ },
130
+ lines: lines.map((line)=>{
131
+ var _line_variantId, _line_modifiers, _line_description;
132
+ return {
133
+ key: nextLineKey(),
134
+ kind: 'catalog',
135
+ productId: line.productId,
136
+ variantId: (_line_variantId = line.variantId) != null ? _line_variantId : '',
137
+ quantity: String(line.quantity || 1),
138
+ modifiers: (_line_modifiers = line.modifiers) != null ? _line_modifiers : [],
139
+ description: (_line_description = line.description) != null ? _line_description : '',
140
+ included: line.included === true
141
+ };
142
+ }),
143
+ notes: notes != null ? notes : ''
144
+ });
145
+ }
115
146
  function lineRequest(line) {
116
147
  var _centsOf;
117
148
  const quantity = Number(line.quantity);
@@ -190,9 +221,10 @@ function addressBody(address) {
190
221
  * server prices and saves it; the totals here are the same function's answer
191
222
  * over the same inputs, and a connected tax service's figure arrives on save.
192
223
  */ export function QuoteEditorDialog(props) {
193
- var _centsOf, _ref, _ref1;
194
- var _quote_quote_tax, _quote_quote, _quote_quote1, _quote_quote2;
224
+ var _props_start, _centsOf, _ref, _start_files, _ref1;
225
+ var _quote_quote_tax, _quote_quote, _quote_quote1, _quote_quote2, _quote_quote3;
195
226
  const { hostId, quote, products, canEdit, canSend, onClose } = props;
227
+ const start = quote ? null : (_props_start = props.start) != null ? _props_start : null;
196
228
  const firestore = useFirestore();
197
229
  const { data: user } = useUser();
198
230
  const { enqueueSnackbar } = useSnackbar();
@@ -202,11 +234,12 @@ function addressBody(address) {
202
234
  hostId
203
235
  ]);
204
236
  const storeDefault = CommerceModel.quoteRequiresProofApprovalByDefault(store == null ? void 0 : store.quotes);
205
- const [form, setForm] = useState(()=>formFromQuote(quote, storeDefault));
237
+ const [form, setForm] = useState(()=>start ? formFromStart(start, storeDefault) : formFromQuote(quote, storeDefault));
206
238
  // The version this form was opened on. A change saved elsewhere while it is
207
239
  // open arrives on `quote`; the save still names this one, so the server
208
- // refuses rather than overwrite that change.
209
- const [baseUpdatedAtMs] = useState(()=>{
240
+ // refuses rather than overwrite that change. A proof added or a revision
241
+ // started here (AGL-4096) moves it, and the save names the new one.
242
+ const [baseUpdatedAtMs, setBaseUpdatedAtMs] = useState(()=>{
210
243
  var _quote_quote;
211
244
  return quote == null ? void 0 : (_quote_quote = quote.quote) == null ? void 0 : _quote_quote.updatedAtMs;
212
245
  });
@@ -320,6 +353,8 @@ function addressBody(address) {
320
353
  }, quote ? {
321
354
  quoteId: quote.$id,
322
355
  baseUpdatedAtMs
356
+ } : {}, !quote && (start == null ? void 0 : start.from) ? {
357
+ from: start.from
323
358
  } : {}, {
324
359
  quote: quoteBody(_extends({}, form, {
325
360
  requireProofApproval
@@ -349,6 +384,7 @@ function addressBody(address) {
349
384
  quote,
350
385
  hostId,
351
386
  baseUpdatedAtMs,
387
+ start,
352
388
  form,
353
389
  requireProofApproval,
354
390
  enqueueSnackbar,
@@ -393,6 +429,36 @@ function addressBody(address) {
393
429
  enqueueSnackbar,
394
430
  onClose
395
431
  ]);
432
+ const [downloading, setDownloading] = useState(false);
433
+ const downloadPdf = useCallback(async ()=>{
434
+ if (!quote) return;
435
+ setDownloading(true);
436
+ try {
437
+ var _quote_number;
438
+ const refused = await downloadCommercePdf(user, '/api/commerce/quote', {
439
+ method: 'POST',
440
+ headers: {
441
+ 'Content-Type': 'application/json'
442
+ },
443
+ body: JSON.stringify({
444
+ action: 'pdf',
445
+ hostId,
446
+ quoteId: quote.$id
447
+ })
448
+ }, `quote-${(_quote_number = quote.number) != null ? _quote_number : quote.$id.slice(-6)}.pdf`);
449
+ if (refused) enqueueSnackbar(refused, {
450
+ variant: 'error',
451
+ allowDuplicate: true
452
+ });
453
+ } finally{
454
+ setDownloading(false);
455
+ }
456
+ }, [
457
+ user,
458
+ quote,
459
+ hostId,
460
+ enqueueSnackbar
461
+ ]);
396
462
  const readOnly = !canEdit || busy;
397
463
  const paid = quote ? !CommerceModel.isUnpaidQuote(quote) : false;
398
464
  const title = quote ? CommerceModel.formatQuoteNumber(quote, quote.$id) : 'New quote';
@@ -436,6 +502,13 @@ function addressBody(address) {
436
502
  severity: "success",
437
503
  children: (quote == null ? void 0 : (_quote_quote2 = quote.quote) == null ? void 0 : _quote_quote2.paidAtMs) ? `Paid ${new Date(quote.quote.paidAtMs).toLocaleDateString()}. It is an order now and can no longer be changed.` : 'This quote has been paid, so it is an order now and can no longer be changed.'
438
504
  }) : null,
505
+ quote ? /*#__PURE__*/ _jsx(QuoteSendSection, {
506
+ hostId: hostId,
507
+ quoteId: quote.$id,
508
+ quote: quote,
509
+ canSend: canSend,
510
+ edited: edited
511
+ }) : null,
439
512
  quote ? /*#__PURE__*/ _jsx(QuotePayLinkSection, {
440
513
  hostId: hostId,
441
514
  quoteId: quote.$id,
@@ -443,6 +516,26 @@ function addressBody(address) {
443
516
  canSend: canSend,
444
517
  edited: edited
445
518
  }) : null,
519
+ quote ? /*#__PURE__*/ _jsx(QuoteProofsSection, {
520
+ hostId: hostId,
521
+ quoteId: quote.$id,
522
+ quote: quote,
523
+ canEdit: canEdit,
524
+ onEdited: (updatedAtMs)=>{
525
+ if (updatedAtMs) setBaseUpdatedAtMs(updatedAtMs);
526
+ }
527
+ }) : null,
528
+ (start == null ? void 0 : start.request) ? /*#__PURE__*/ _jsx(QuoteStartedFromRequest, {
529
+ formName: start.request.formName,
530
+ receivedAtMs: start.request.receivedAtMs,
531
+ files: (_start_files = start.files) != null ? _start_files : [],
532
+ threaded: start.threaded === true
533
+ }) : null,
534
+ (quote == null ? void 0 : (_quote_quote3 = quote.quote) == null ? void 0 : _quote_quote3.request) ? /*#__PURE__*/ _jsx(QuoteRequestFilesSection, {
535
+ hostId: hostId,
536
+ quoteId: quote.$id,
537
+ quote: quote
538
+ }) : null,
446
539
  taxBasis.kind === 'none' && taxBasis.reason === 'undecided' ? /*#__PURE__*/ _jsx(PrerequisiteNotice, {
447
540
  href: taxesHref != null ? taxesHref : undefined,
448
541
  actionLabel: "Open Taxes",
@@ -677,6 +770,15 @@ function addressBody(address) {
677
770
  children: 'Delete'
678
771
  })
679
772
  }) : null,
773
+ quote ? /*#__PURE__*/ _jsx(BlockedControl, {
774
+ blocked: !canEdit || edited,
775
+ reason: edited ? 'Save your changes first: the PDF shows the quote as it was last saved.' : REFUSAL,
776
+ children: /*#__PURE__*/ _jsx(Button, {
777
+ disabled: busy || downloading,
778
+ onClick: ()=>void downloadPdf(),
779
+ children: downloading ? 'Making PDF…' : 'Download PDF'
780
+ })
781
+ }) : null,
680
782
  /*#__PURE__*/ _jsx(Button, {
681
783
  onClick: onClose,
682
784
  disabled: busy,
@@ -730,6 +832,53 @@ function addressBody(address) {
730
832
  });
731
833
  }
732
834
  QuoteEditorDialog.displayName = 'QuoteEditorDialog';
835
+ /** A new quote's request (AGL-4098): which form sent it and when, its files by name, and its conversation. */ function QuoteStartedFromRequest(props) {
836
+ const { formName, receivedAtMs, files, threaded } = props;
837
+ const when = receivedAtMs ? ` on ${new Date(receivedAtMs).toLocaleString(undefined, {
838
+ dateStyle: 'medium',
839
+ timeStyle: 'short'
840
+ })}` : '';
841
+ return /*#__PURE__*/ _jsx(Alert, {
842
+ severity: "info",
843
+ children: /*#__PURE__*/ _jsxs(Stack, {
844
+ spacing: 1,
845
+ children: [
846
+ /*#__PURE__*/ _jsx(Typography, {
847
+ variant: "body2",
848
+ children: `From a request sent through “${formName}”${when}.`
849
+ }),
850
+ files.length ? /*#__PURE__*/ _jsxs(Stack, {
851
+ spacing: 0.5,
852
+ children: [
853
+ /*#__PURE__*/ _jsx(Typography, {
854
+ variant: "caption",
855
+ color: "text.secondary",
856
+ children: 'Their files, kept on the quote as the customer’s:'
857
+ }),
858
+ /*#__PURE__*/ _jsx(Stack, {
859
+ direction: "row",
860
+ spacing: 1,
861
+ useFlexGap: true,
862
+ sx: {
863
+ flexWrap: 'wrap'
864
+ },
865
+ children: files.map((file)=>/*#__PURE__*/ _jsx(Chip, {
866
+ size: "small",
867
+ variant: "outlined",
868
+ label: file.name
869
+ }, file.id))
870
+ })
871
+ ]
872
+ }) : null,
873
+ /*#__PURE__*/ _jsx(Typography, {
874
+ variant: "caption",
875
+ color: "text.secondary",
876
+ children: threaded ? 'When you email the quote, it replies to the confirmation they were sent, in the same conversation.' : 'They weren’t sent a confirmation, so the quote’s email starts the conversation.'
877
+ })
878
+ ]
879
+ })
880
+ });
881
+ }
733
882
  /** Who the quote is for: a person the workspace keeps, or details typed here. */ function QuoteCustomerSection(props) {
734
883
  const { hostId, form, disabled, onChange } = props;
735
884
  const { data: user } = useUser();