@endora-commerce/mod-invoices 0.0.0-stage → 0.100.0

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 (235) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -2
  3. package/dist/admin/components/InvoiceSectionTabs.d.ts +22 -0
  4. package/dist/admin/components/InvoiceSectionTabs.d.ts.map +1 -0
  5. package/dist/admin/components/InvoiceSectionTabs.js +34 -0
  6. package/dist/admin/components/InvoiceSectionTabs.js.map +1 -0
  7. package/dist/admin/index.d.ts +34 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +69 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/InvoiceDetail.d.ts +10 -0
  12. package/dist/admin/pages/InvoiceDetail.d.ts.map +1 -0
  13. package/dist/admin/pages/InvoiceDetail.js +111 -0
  14. package/dist/admin/pages/InvoiceDetail.js.map +1 -0
  15. package/dist/admin/pages/InvoiceTemplateEditor.d.ts +11 -0
  16. package/dist/admin/pages/InvoiceTemplateEditor.d.ts.map +1 -0
  17. package/dist/admin/pages/InvoiceTemplateEditor.js +188 -0
  18. package/dist/admin/pages/InvoiceTemplateEditor.js.map +1 -0
  19. package/dist/admin/pages/InvoiceTemplatesPage.d.ts +10 -0
  20. package/dist/admin/pages/InvoiceTemplatesPage.d.ts.map +1 -0
  21. package/dist/admin/pages/InvoiceTemplatesPage.js +60 -0
  22. package/dist/admin/pages/InvoiceTemplatesPage.js.map +1 -0
  23. package/dist/admin/pages/InvoicesList.d.ts +9 -0
  24. package/dist/admin/pages/InvoicesList.d.ts.map +1 -0
  25. package/dist/admin/pages/InvoicesList.js +116 -0
  26. package/dist/admin/pages/InvoicesList.js.map +1 -0
  27. package/dist/admin/templates/described-blocks.d.ts +67 -0
  28. package/dist/admin/templates/described-blocks.d.ts.map +1 -0
  29. package/dist/admin/templates/described-blocks.js +83 -0
  30. package/dist/admin/templates/described-blocks.js.map +1 -0
  31. package/dist/admin/templates/invoice-builder-plugin.d.ts +4 -0
  32. package/dist/admin/templates/invoice-builder-plugin.d.ts.map +1 -0
  33. package/dist/admin/templates/invoice-builder-plugin.js +12 -0
  34. package/dist/admin/templates/invoice-builder-plugin.js.map +1 -0
  35. package/dist/admin/templates/invoice-puck-config.d.ts +37 -0
  36. package/dist/admin/templates/invoice-puck-config.d.ts.map +1 -0
  37. package/dist/admin/templates/invoice-puck-config.js +733 -0
  38. package/dist/admin/templates/invoice-puck-config.js.map +1 -0
  39. package/dist/backend/email-templates/invoice-issued.default.d.ts +10 -0
  40. package/dist/backend/email-templates/invoice-issued.default.d.ts.map +1 -0
  41. package/dist/backend/email-templates/invoice-issued.default.js +45 -0
  42. package/dist/backend/email-templates/invoice-issued.default.js.map +1 -0
  43. package/dist/backend/entities/invoice-external-attachment.entity.d.ts +20 -0
  44. package/dist/backend/entities/invoice-external-attachment.entity.d.ts.map +1 -0
  45. package/dist/backend/entities/invoice-external-attachment.entity.js +77 -0
  46. package/dist/backend/entities/invoice-external-attachment.entity.js.map +1 -0
  47. package/dist/backend/entities/invoice-line.entity.d.ts +20 -0
  48. package/dist/backend/entities/invoice-line.entity.d.ts.map +1 -0
  49. package/dist/backend/entities/invoice-line.entity.js +82 -0
  50. package/dist/backend/entities/invoice-line.entity.js.map +1 -0
  51. package/dist/backend/entities/invoice-number-counter.entity.d.ts +18 -0
  52. package/dist/backend/entities/invoice-number-counter.entity.d.ts.map +1 -0
  53. package/dist/backend/entities/invoice-number-counter.entity.js +63 -0
  54. package/dist/backend/entities/invoice-number-counter.entity.js.map +1 -0
  55. package/dist/backend/entities/invoice-template.entity.d.ts +22 -0
  56. package/dist/backend/entities/invoice-template.entity.d.ts.map +1 -0
  57. package/dist/backend/entities/invoice-template.entity.js +84 -0
  58. package/dist/backend/entities/invoice-template.entity.js.map +1 -0
  59. package/dist/backend/entities/invoice.entity.d.ts +64 -0
  60. package/dist/backend/entities/invoice.entity.d.ts.map +1 -0
  61. package/dist/backend/entities/invoice.entity.js +202 -0
  62. package/dist/backend/entities/invoice.entity.js.map +1 -0
  63. package/dist/backend/index.d.ts +111 -0
  64. package/dist/backend/index.d.ts.map +1 -0
  65. package/dist/backend/index.js +291 -0
  66. package/dist/backend/index.js.map +1 -0
  67. package/dist/backend/pdf-components/descriptor.d.ts +29 -0
  68. package/dist/backend/pdf-components/descriptor.d.ts.map +1 -0
  69. package/dist/backend/pdf-components/descriptor.js +227 -0
  70. package/dist/backend/pdf-components/descriptor.js.map +1 -0
  71. package/dist/backend/pdf-components/embed-logo-images.d.ts +35 -0
  72. package/dist/backend/pdf-components/embed-logo-images.d.ts.map +1 -0
  73. package/dist/backend/pdf-components/embed-logo-images.js +101 -0
  74. package/dist/backend/pdf-components/embed-logo-images.js.map +1 -0
  75. package/dist/backend/pdf-components/sample.d.ts +4 -0
  76. package/dist/backend/pdf-components/sample.d.ts.map +1 -0
  77. package/dist/backend/pdf-components/sample.js +75 -0
  78. package/dist/backend/pdf-components/sample.js.map +1 -0
  79. package/dist/backend/pdf-components/sections.d.ts +34 -0
  80. package/dist/backend/pdf-components/sections.d.ts.map +1 -0
  81. package/dist/backend/pdf-components/sections.js +445 -0
  82. package/dist/backend/pdf-components/sections.js.map +1 -0
  83. package/dist/backend/pdf-components/tree-mapper.d.ts +37 -0
  84. package/dist/backend/pdf-components/tree-mapper.d.ts.map +1 -0
  85. package/dist/backend/pdf-components/tree-mapper.js +76 -0
  86. package/dist/backend/pdf-components/tree-mapper.js.map +1 -0
  87. package/dist/backend/plugin.d.ts +124 -0
  88. package/dist/backend/plugin.d.ts.map +1 -0
  89. package/dist/backend/plugin.js +87 -0
  90. package/dist/backend/plugin.js.map +1 -0
  91. package/dist/backend/routes.admin.d.ts +41 -0
  92. package/dist/backend/routes.admin.d.ts.map +1 -0
  93. package/dist/backend/routes.admin.js +219 -0
  94. package/dist/backend/routes.admin.js.map +1 -0
  95. package/dist/backend/routes.customer.d.ts +33 -0
  96. package/dist/backend/routes.customer.d.ts.map +1 -0
  97. package/dist/backend/routes.customer.js +124 -0
  98. package/dist/backend/routes.customer.js.map +1 -0
  99. package/dist/backend/seeds/generic-invoice-template.d.ts +18 -0
  100. package/dist/backend/seeds/generic-invoice-template.d.ts.map +1 -0
  101. package/dist/backend/seeds/generic-invoice-template.js +174 -0
  102. package/dist/backend/seeds/generic-invoice-template.js.map +1 -0
  103. package/dist/backend/services/amount-to-words.d.ts +20 -0
  104. package/dist/backend/services/amount-to-words.d.ts.map +1 -0
  105. package/dist/backend/services/amount-to-words.js +232 -0
  106. package/dist/backend/services/amount-to-words.js.map +1 -0
  107. package/dist/backend/services/auto-issue-reactor.d.ts +53 -0
  108. package/dist/backend/services/auto-issue-reactor.d.ts.map +1 -0
  109. package/dist/backend/services/auto-issue-reactor.js +122 -0
  110. package/dist/backend/services/auto-issue-reactor.js.map +1 -0
  111. package/dist/backend/services/corrective-invoice.d.ts +38 -0
  112. package/dist/backend/services/corrective-invoice.d.ts.map +1 -0
  113. package/dist/backend/services/corrective-invoice.js +257 -0
  114. package/dist/backend/services/corrective-invoice.js.map +1 -0
  115. package/dist/backend/services/cross-module-context.d.ts +82 -0
  116. package/dist/backend/services/cross-module-context.d.ts.map +1 -0
  117. package/dist/backend/services/cross-module-context.js +93 -0
  118. package/dist/backend/services/cross-module-context.js.map +1 -0
  119. package/dist/backend/services/duplicate-number-refusal.d.ts +33 -0
  120. package/dist/backend/services/duplicate-number-refusal.d.ts.map +1 -0
  121. package/dist/backend/services/duplicate-number-refusal.js +92 -0
  122. package/dist/backend/services/duplicate-number-refusal.js.map +1 -0
  123. package/dist/backend/services/erp-sale-document-write-port.d.ts +37 -0
  124. package/dist/backend/services/erp-sale-document-write-port.d.ts.map +1 -0
  125. package/dist/backend/services/erp-sale-document-write-port.js +192 -0
  126. package/dist/backend/services/erp-sale-document-write-port.js.map +1 -0
  127. package/dist/backend/services/invoice-attachment-fetch-registry.d.ts +75 -0
  128. package/dist/backend/services/invoice-attachment-fetch-registry.d.ts.map +1 -0
  129. package/dist/backend/services/invoice-attachment-fetch-registry.js +81 -0
  130. package/dist/backend/services/invoice-attachment-fetch-registry.js.map +1 -0
  131. package/dist/backend/services/invoice-copy-host.service.d.ts +9 -0
  132. package/dist/backend/services/invoice-copy-host.service.d.ts.map +1 -0
  133. package/dist/backend/services/invoice-copy-host.service.js +52 -0
  134. package/dist/backend/services/invoice-copy-host.service.js.map +1 -0
  135. package/dist/backend/services/invoice-email-dispatch.d.ts +89 -0
  136. package/dist/backend/services/invoice-email-dispatch.d.ts.map +1 -0
  137. package/dist/backend/services/invoice-email-dispatch.js +138 -0
  138. package/dist/backend/services/invoice-email-dispatch.js.map +1 -0
  139. package/dist/backend/services/invoice-line-builder.d.ts +29 -0
  140. package/dist/backend/services/invoice-line-builder.d.ts.map +1 -0
  141. package/dist/backend/services/invoice-line-builder.js +46 -0
  142. package/dist/backend/services/invoice-line-builder.js.map +1 -0
  143. package/dist/backend/services/invoice-number-collisions.d.ts +17 -0
  144. package/dist/backend/services/invoice-number-collisions.d.ts.map +1 -0
  145. package/dist/backend/services/invoice-number-collisions.js +154 -0
  146. package/dist/backend/services/invoice-number-collisions.js.map +1 -0
  147. package/dist/backend/services/invoice-number-generator.d.ts +96 -0
  148. package/dist/backend/services/invoice-number-generator.d.ts.map +1 -0
  149. package/dist/backend/services/invoice-number-generator.js +157 -0
  150. package/dist/backend/services/invoice-number-generator.js.map +1 -0
  151. package/dist/backend/services/invoice-pdf-block-registry.d.ts +65 -0
  152. package/dist/backend/services/invoice-pdf-block-registry.d.ts.map +1 -0
  153. package/dist/backend/services/invoice-pdf-block-registry.js +94 -0
  154. package/dist/backend/services/invoice-pdf-block-registry.js.map +1 -0
  155. package/dist/backend/services/invoice-pdf-renderer.d.ts +57 -0
  156. package/dist/backend/services/invoice-pdf-renderer.d.ts.map +1 -0
  157. package/dist/backend/services/invoice-pdf-renderer.js +158 -0
  158. package/dist/backend/services/invoice-pdf-renderer.js.map +1 -0
  159. package/dist/backend/services/invoice-pdf.d.ts +24 -0
  160. package/dist/backend/services/invoice-pdf.d.ts.map +1 -0
  161. package/dist/backend/services/invoice-pdf.js +52 -0
  162. package/dist/backend/services/invoice-pdf.js.map +1 -0
  163. package/dist/backend/services/invoice-placement-apply-port.d.ts +29 -0
  164. package/dist/backend/services/invoice-placement-apply-port.d.ts.map +1 -0
  165. package/dist/backend/services/invoice-placement-apply-port.js +40 -0
  166. package/dist/backend/services/invoice-placement-apply-port.js.map +1 -0
  167. package/dist/backend/services/invoice-read-port.d.ts +44 -0
  168. package/dist/backend/services/invoice-read-port.d.ts.map +1 -0
  169. package/dist/backend/services/invoice-read-port.js +95 -0
  170. package/dist/backend/services/invoice-read-port.js.map +1 -0
  171. package/dist/backend/services/invoice-service.d.ts +94 -0
  172. package/dist/backend/services/invoice-service.d.ts.map +1 -0
  173. package/dist/backend/services/invoice-service.js +374 -0
  174. package/dist/backend/services/invoice-service.js.map +1 -0
  175. package/dist/backend/services/invoice-template-service.d.ts +43 -0
  176. package/dist/backend/services/invoice-template-service.d.ts.map +1 -0
  177. package/dist/backend/services/invoice-template-service.js +147 -0
  178. package/dist/backend/services/invoice-template-service.js.map +1 -0
  179. package/dist/backend/services/numbering-configuration.d.ts +76 -0
  180. package/dist/backend/services/numbering-configuration.d.ts.map +1 -0
  181. package/dist/backend/services/numbering-configuration.js +176 -0
  182. package/dist/backend/services/numbering-configuration.js.map +1 -0
  183. package/dist/backend/services/numbering-write-validator.d.ts +22 -0
  184. package/dist/backend/services/numbering-write-validator.d.ts.map +1 -0
  185. package/dist/backend/services/numbering-write-validator.js +82 -0
  186. package/dist/backend/services/numbering-write-validator.js.map +1 -0
  187. package/dist/backend/services/seller-settings.d.ts +24 -0
  188. package/dist/backend/services/seller-settings.d.ts.map +1 -0
  189. package/dist/backend/services/seller-settings.js +38 -0
  190. package/dist/backend/services/seller-settings.js.map +1 -0
  191. package/dist/backend/services/vendor-number-hold.d.ts +10 -0
  192. package/dist/backend/services/vendor-number-hold.d.ts.map +1 -0
  193. package/dist/backend/services/vendor-number-hold.js +14 -0
  194. package/dist/backend/services/vendor-number-hold.js.map +1 -0
  195. package/dist/manifest.d.ts +203 -0
  196. package/dist/manifest.d.ts.map +1 -0
  197. package/dist/manifest.js +390 -0
  198. package/dist/manifest.js.map +1 -0
  199. package/dist/migrations/20260629T125121_invoices_module.d.ts +17 -0
  200. package/dist/migrations/20260629T125121_invoices_module.d.ts.map +1 -0
  201. package/dist/migrations/20260629T125121_invoices_module.js +117 -0
  202. package/dist/migrations/20260629T125121_invoices_module.js.map +1 -0
  203. package/dist/migrations/20260801T111000_invoices_generic_template_reseed.d.ts +6 -0
  204. package/dist/migrations/20260801T111000_invoices_generic_template_reseed.d.ts.map +1 -0
  205. package/dist/migrations/20260801T111000_invoices_generic_template_reseed.js +25 -0
  206. package/dist/migrations/20260801T111000_invoices_generic_template_reseed.js.map +1 -0
  207. package/dist/migrations/20260817T201110_invoices_correction_idempotency_key.d.ts +20 -0
  208. package/dist/migrations/20260817T201110_invoices_correction_idempotency_key.d.ts.map +1 -0
  209. package/dist/migrations/20260817T201110_invoices_correction_idempotency_key.js +33 -0
  210. package/dist/migrations/20260817T201110_invoices_correction_idempotency_key.js.map +1 -0
  211. package/dist/migrations/20260903T101756_invoices_namespace_block_names.d.ts +6 -0
  212. package/dist/migrations/20260903T101756_invoices_namespace_block_names.d.ts.map +1 -0
  213. package/dist/migrations/20260903T101756_invoices_namespace_block_names.js +56 -0
  214. package/dist/migrations/20260903T101756_invoices_namespace_block_names.js.map +1 -0
  215. package/dist/migrations/20260914T133603_invoices_erp_imported_sale_documents.d.ts +12 -0
  216. package/dist/migrations/20260914T133603_invoices_erp_imported_sale_documents.d.ts.map +1 -0
  217. package/dist/migrations/20260914T133603_invoices_erp_imported_sale_documents.js +87 -0
  218. package/dist/migrations/20260914T133603_invoices_erp_imported_sale_documents.js.map +1 -0
  219. package/dist/migrations/20260926T230239_invoices_import_identity.d.ts +33 -0
  220. package/dist/migrations/20260926T230239_invoices_import_identity.d.ts.map +1 -0
  221. package/dist/migrations/20260926T230239_invoices_import_identity.js +83 -0
  222. package/dist/migrations/20260926T230239_invoices_import_identity.js.map +1 -0
  223. package/dist/migrations/index.d.ts +32 -0
  224. package/dist/migrations/index.d.ts.map +1 -0
  225. package/dist/migrations/index.js +39 -0
  226. package/dist/migrations/index.js.map +1 -0
  227. package/dist/ports/index.d.ts +93 -0
  228. package/dist/ports/index.d.ts.map +1 -0
  229. package/dist/ports/index.js +2 -0
  230. package/dist/ports/index.js.map +1 -0
  231. package/docs/invoices.md +113 -0
  232. package/i18n/en.json +45 -0
  233. package/i18n/pl.json +45 -0
  234. package/package.json +119 -3
  235. package/tailwind.css +14 -0
@@ -0,0 +1,76 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { NumberingSeries, SettingsAdminPort } from '@endora-commerce/contracts';
3
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
4
+ import { type InvoiceKind } from './invoice-number-generator.js';
5
+ /** What this service writes to. `warn` is the report; nothing here blocks boot. */
6
+ export interface NumberingConfigurationLogger {
7
+ info(obj: object, msg: string): void;
8
+ warn(obj: object, msg: string): void;
9
+ }
10
+ /**
11
+ * The numbering configuration a deployment boots with — feature 078, D-95.3
12
+ * and D-95.4.
13
+ *
14
+ * Two jobs, in this order and for a stated reason:
15
+ *
16
+ * 1. **Grandfather the system-default channel.** The shipped defaults now
17
+ * carry `{channel}`, so a fresh deployment numbers `FV 1/DEFAULT/2026`
18
+ * unless something says otherwise. An accountant expects `FV 1/2026`, and
19
+ * an existing deployment's series must not change shape mid-year, so the
20
+ * system-default channel is pinned to the pre-D-95 pattern as an explicit
21
+ * per-channel value — **only** while the setting is otherwise untouched.
22
+ * The platform picks a default for you and never rewrites your choice.
23
+ * 2. **Report what is left.** Every `(kind, channel)` pair goes through the
24
+ * collision predicate and each colliding pair is logged as a `warn`. It
25
+ * runs *after* step 1 so it never names a pair the same boot has just
26
+ * repaired — a report full of noise is a report nobody reads within a week.
27
+ * It does not block boot: what survives step 1 is a pattern the operator
28
+ * configured themselves, and refusing to start would take down the
29
+ * storefront, the catalogue and the checkout to protect an invoice the
30
+ * issuance refusal already protects.
31
+ */
32
+ export declare class NumberingConfigurationService {
33
+ private readonly emFactory;
34
+ /**
35
+ * The audited settings write (Constitution XIII), read per call rather than
36
+ * captured: `settings` is non-deactivatable, so the gate has no "no" to
37
+ * give, but the accessor keeps this service from freezing a registration.
38
+ */
39
+ private readonly settingsAdmin;
40
+ private readonly log;
41
+ constructor(emFactory: () => EntityManager,
42
+ /**
43
+ * The audited settings write (Constitution XIII), read per call rather than
44
+ * captured: `settings` is non-deactivatable, so the gate has no "no" to
45
+ * give, but the accessor keeps this service from freezing a registration.
46
+ */
47
+ settingsAdmin: () => SettingsAdminPort, log: NumberingConfigurationLogger);
48
+ /** Both jobs, in order. Called once per boot. */
49
+ reconcile(): Promise<void>;
50
+ /**
51
+ * D-95.3 move 3 — pin the system-default channel to the pre-D-95 pattern,
52
+ * once, and only while nobody has configured the setting.
53
+ *
54
+ * "Untouched" is `globalValue === null` **and** no `SettingValue` row for the
55
+ * system-default channel. An operator who wrote a pattern — even one that now
56
+ * collides — keeps it; that case belongs to the report and to the two
57
+ * refusals, never to a silent rewrite.
58
+ */
59
+ private pinSystemDefaultChannel;
60
+ /** Every `(kind, channel)` series the platform currently resolves. */
61
+ seriesFor(kind: InvoiceKind): Promise<NumberingSeries[]>;
62
+ /**
63
+ * D-95.4 — name every surviving collision, loudly, and do not block boot.
64
+ *
65
+ * The count line is emitted even when the count is zero, so its absence means
66
+ * the report did not run rather than that it found nothing.
67
+ */
68
+ reportCollisions(): Promise<number>;
69
+ }
70
+ /**
71
+ * A channel's display name. `SalesChannel.name` is a per-language map, so a
72
+ * caller that needs one string picks English, then whatever is there, then the
73
+ * code — which is never empty.
74
+ */
75
+ export declare function channelName(channel: SalesChannel): string;
76
+ //# sourceMappingURL=numbering-configuration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"numbering-configuration.d.ts","sourceRoot":"","sources":["../../../src/backend/services/numbering-configuration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAEV,eAAe,EACf,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAKhE,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,+BAA+B,CAAC;AAWvC,mFAAmF;AACnF,MAAM,WAAW,4BAA4B;IAC3C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,6BAA6B;IAEtC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAPH,SAAS,EAAE,MAAM,aAAa;IAC/C;;;;OAIG;IACc,aAAa,EAAE,MAAM,iBAAiB,EACtC,GAAG,EAAE,4BAA4B;IAGpD,iDAAiD;IAC3C,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAKhC;;;;;;;;OAQG;YACW,uBAAuB;IAkErC,sEAAsE;IAChE,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAqB9D;;;;;OAKG;IACG,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;CAe1C;AAaD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAGzD"}
@@ -0,0 +1,176 @@
1
+ import { rethrowIfModuleDisabled } from '@endora-commerce/platform/kernel';
2
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
3
+ import { Setting } from '@endora-commerce/platform/kernel';
4
+ import { SettingValue } from '@endora-commerce/platform/kernel';
5
+ import { INVOICES_SETTING_CODES } from '../../manifest.js';
6
+ import { findNumberPatternCollisions } from './invoice-number-collisions.js';
7
+ import { PRE_CHANNEL_DEFAULT_PATTERNS, effectivePattern, } from './invoice-number-generator.js';
8
+ /** The three numbering settings, in the order an operator meets them. */
9
+ const PATTERN_SETTING_CODE = {
10
+ invoice: INVOICES_SETTING_CODES.NUMBERING_INVOICE_PATTERN,
11
+ proforma: INVOICES_SETTING_CODES.NUMBERING_PROFORMA_PATTERN,
12
+ correction: INVOICES_SETTING_CODES.NUMBERING_CORRECTION_PATTERN,
13
+ };
14
+ const KINDS = ['invoice', 'proforma', 'correction'];
15
+ /**
16
+ * The numbering configuration a deployment boots with — feature 078, D-95.3
17
+ * and D-95.4.
18
+ *
19
+ * Two jobs, in this order and for a stated reason:
20
+ *
21
+ * 1. **Grandfather the system-default channel.** The shipped defaults now
22
+ * carry `{channel}`, so a fresh deployment numbers `FV 1/DEFAULT/2026`
23
+ * unless something says otherwise. An accountant expects `FV 1/2026`, and
24
+ * an existing deployment's series must not change shape mid-year, so the
25
+ * system-default channel is pinned to the pre-D-95 pattern as an explicit
26
+ * per-channel value — **only** while the setting is otherwise untouched.
27
+ * The platform picks a default for you and never rewrites your choice.
28
+ * 2. **Report what is left.** Every `(kind, channel)` pair goes through the
29
+ * collision predicate and each colliding pair is logged as a `warn`. It
30
+ * runs *after* step 1 so it never names a pair the same boot has just
31
+ * repaired — a report full of noise is a report nobody reads within a week.
32
+ * It does not block boot: what survives step 1 is a pattern the operator
33
+ * configured themselves, and refusing to start would take down the
34
+ * storefront, the catalogue and the checkout to protect an invoice the
35
+ * issuance refusal already protects.
36
+ */
37
+ export class NumberingConfigurationService {
38
+ emFactory;
39
+ settingsAdmin;
40
+ log;
41
+ constructor(emFactory,
42
+ /**
43
+ * The audited settings write (Constitution XIII), read per call rather than
44
+ * captured: `settings` is non-deactivatable, so the gate has no "no" to
45
+ * give, but the accessor keeps this service from freezing a registration.
46
+ */
47
+ settingsAdmin, log) {
48
+ this.emFactory = emFactory;
49
+ this.settingsAdmin = settingsAdmin;
50
+ this.log = log;
51
+ }
52
+ /** Both jobs, in order. Called once per boot. */
53
+ async reconcile() {
54
+ await this.pinSystemDefaultChannel();
55
+ await this.reportCollisions();
56
+ }
57
+ /**
58
+ * D-95.3 move 3 — pin the system-default channel to the pre-D-95 pattern,
59
+ * once, and only while nobody has configured the setting.
60
+ *
61
+ * "Untouched" is `globalValue === null` **and** no `SettingValue` row for the
62
+ * system-default channel. An operator who wrote a pattern — even one that now
63
+ * collides — keeps it; that case belongs to the report and to the two
64
+ * refusals, never to a silent rewrite.
65
+ */
66
+ async pinSystemDefaultChannel() {
67
+ const em = this.emFactory();
68
+ const systemDefault = await em.findOne(SalesChannel, { systemDefault: true });
69
+ if (!systemDefault) {
70
+ // A system-default channel always exists (the boot reconciler owns that
71
+ // invariant). Reporting rather than inventing one: a fabricated channel
72
+ // here would pin a series to a row that is not the platform's default.
73
+ this.log.warn({ module: 'invoices' }, 'invoices: no system-default sales channel, so the pre-D-95 numbering pattern was not pinned');
74
+ return;
75
+ }
76
+ for (const kind of KINDS) {
77
+ const code = PATTERN_SETTING_CODE[kind];
78
+ const setting = await em.findOne(Setting, { code });
79
+ if (!setting)
80
+ continue;
81
+ if (setting.globalValue !== null && setting.globalValue !== undefined)
82
+ continue;
83
+ const existing = await em.findOne(SettingValue, {
84
+ setting,
85
+ salesChannel: systemDefault.id,
86
+ });
87
+ if (existing)
88
+ continue;
89
+ try {
90
+ await this.settingsAdmin().setValueForSubset(code, [systemDefault.code], PRE_CHANNEL_DEFAULT_PATTERNS[kind], null, { actorAdminUserId: null });
91
+ }
92
+ catch (err) {
93
+ // A switched-off `settings` is not a tolerable outcome here — the write
94
+ // is the whole point of the hook — so the gate's answer goes straight
95
+ // back out, before any other test. `ModuleDisabledError` is an
96
+ // `HttpError`, so an `instanceof` or status test would let it through
97
+ // by accident rather than by decision.
98
+ rethrowIfModuleDisabled(err);
99
+ // The narrow tolerance, and it is D-95.4 written as code: another
100
+ // channel already carries the historical pattern as an explicit value,
101
+ // so pinning it here would duplicate it and the module's own write
102
+ // validator refuses. That is a configuration the operator made, and
103
+ // refusing to boot over it would take down the storefront, the
104
+ // catalogue and the checkout to protect an invoice the issuance refusal
105
+ // already protects. The report below names the pair; nothing is
106
+ // silently absorbed.
107
+ this.log.warn({
108
+ module: 'invoices',
109
+ settingCode: code,
110
+ salesChannelCode: systemDefault.code,
111
+ reason: err instanceof Error ? err.message : String(err),
112
+ }, 'invoices: could not pin the system-default channel to its historical numbering pattern');
113
+ continue;
114
+ }
115
+ this.log.info({ module: 'invoices', settingCode: code, salesChannelCode: systemDefault.code }, 'invoices: pinned the system-default channel to its historical numbering pattern');
116
+ }
117
+ }
118
+ /** Every `(kind, channel)` series the platform currently resolves. */
119
+ async seriesFor(kind) {
120
+ const em = this.emFactory();
121
+ const channels = await em.find(SalesChannel, {});
122
+ const setting = await em.findOne(Setting, { code: PATTERN_SETTING_CODE[kind] });
123
+ const values = setting
124
+ ? await em.find(SettingValue, { setting }, { populate: ['salesChannel'] })
125
+ : [];
126
+ const byChannelId = new Map(values.map((value) => [value.salesChannel.id, value.value]));
127
+ const globalValue = setting?.globalValue;
128
+ return channels.map((channel) => {
129
+ const stored = byChannelId.has(channel.id) ? byChannelId.get(channel.id) : globalValue;
130
+ return {
131
+ salesChannelId: channel.id,
132
+ salesChannelCode: channel.code,
133
+ salesChannelName: channelName(channel),
134
+ pattern: effectivePattern(kind, typeof stored === 'string' ? stored : null),
135
+ };
136
+ });
137
+ }
138
+ /**
139
+ * D-95.4 — name every surviving collision, loudly, and do not block boot.
140
+ *
141
+ * The count line is emitted even when the count is zero, so its absence means
142
+ * the report did not run rather than that it found nothing.
143
+ */
144
+ async reportCollisions() {
145
+ let total = 0;
146
+ for (const kind of KINDS) {
147
+ const collisions = findNumberPatternCollisions(await this.seriesFor(kind));
148
+ total += collisions.length;
149
+ for (const collision of collisions) {
150
+ this.log.warn(describeCollision(kind, collision), 'invoices: two sales channels can produce the same document number');
151
+ }
152
+ }
153
+ this.log.info({ module: 'invoices', collidingPairs: total }, 'invoices: numbering pattern collision report');
154
+ return total;
155
+ }
156
+ }
157
+ /** The `warn` payload — both channel codes, both patterns and the proof. */
158
+ function describeCollision(kind, collision) {
159
+ return {
160
+ module: 'invoices',
161
+ kind,
162
+ salesChannelCodes: [collision.a.salesChannelCode, collision.b.salesChannelCode],
163
+ patterns: [collision.a.pattern, collision.b.pattern],
164
+ example: collision.example,
165
+ };
166
+ }
167
+ /**
168
+ * A channel's display name. `SalesChannel.name` is a per-language map, so a
169
+ * caller that needs one string picks English, then whatever is there, then the
170
+ * code — which is never empty.
171
+ */
172
+ export function channelName(channel) {
173
+ const names = channel.name ?? {};
174
+ return names['en'] ?? Object.values(names)[0] ?? channel.code;
175
+ }
176
+ //# sourceMappingURL=numbering-configuration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"numbering-configuration.js","sourceRoot":"","sources":["../../../src/backend/services/numbering-configuration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type SettingWriteValidator } from '@endora-commerce/contracts';
2
+ /**
3
+ * `invoices`' answer to "is this numbering configuration legal" — feature 078,
4
+ * D-95.2.
5
+ *
6
+ * `settings` hands over what every channel's value would be after the write;
7
+ * this decides whether two of them can render one string. It refuses a
8
+ * **possible** collision, not an actual one, and that asymmetry with the
9
+ * issuance refusal is the point: at a settings write the operator is
10
+ * configuring and nothing is at stake, so a conservative refusal costs them one
11
+ * edit; at issuance a customer's document is at stake, so only a duplicate the
12
+ * database has actually rejected is refused.
13
+ *
14
+ * The predicate is over the **post-write state**, not over the delta, so
15
+ * re-saving a value that already collides is refused too. That is what an
16
+ * operator needs: the question is whether the configuration they are about to
17
+ * confirm is one the platform can number, not whether they changed anything.
18
+ *
19
+ * Pure: no I/O. Everything it needs is in the projection.
20
+ */
21
+ export declare function createNumberingPatternValidator(): SettingWriteValidator;
22
+ //# sourceMappingURL=numbering-write-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"numbering-write-validator.d.ts","sourceRoot":"","sources":["../../../src/backend/services/numbering-write-validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,4BAA4B,CAAC;AAepC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,+BAA+B,IAAI,qBAAqB,CAQvE"}
@@ -0,0 +1,82 @@
1
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { INVOICES_SETTING_CODES } from '../../manifest.js';
4
+ import { findNumberPatternCollisions, patternSequenceDefect, } from './invoice-number-collisions.js';
5
+ import { effectivePattern } from './invoice-number-generator.js';
6
+ const KIND_BY_SETTING_CODE = {
7
+ [INVOICES_SETTING_CODES.NUMBERING_INVOICE_PATTERN]: 'invoice',
8
+ [INVOICES_SETTING_CODES.NUMBERING_PROFORMA_PATTERN]: 'proforma',
9
+ [INVOICES_SETTING_CODES.NUMBERING_CORRECTION_PATTERN]: 'correction',
10
+ };
11
+ /**
12
+ * `invoices`' answer to "is this numbering configuration legal" — feature 078,
13
+ * D-95.2.
14
+ *
15
+ * `settings` hands over what every channel's value would be after the write;
16
+ * this decides whether two of them can render one string. It refuses a
17
+ * **possible** collision, not an actual one, and that asymmetry with the
18
+ * issuance refusal is the point: at a settings write the operator is
19
+ * configuring and nothing is at stake, so a conservative refusal costs them one
20
+ * edit; at issuance a customer's document is at stake, so only a duplicate the
21
+ * database has actually rejected is refused.
22
+ *
23
+ * The predicate is over the **post-write state**, not over the delta, so
24
+ * re-saving a value that already collides is refused too. That is what an
25
+ * operator needs: the question is whether the configuration they are about to
26
+ * confirm is one the platform can number, not whether they changed anything.
27
+ *
28
+ * Pure: no I/O. Everything it needs is in the projection.
29
+ */
30
+ export function createNumberingPatternValidator() {
31
+ return {
32
+ contributorModuleId: 'invoices',
33
+ codes: Object.keys(KIND_BY_SETTING_CODE),
34
+ validate: async (input) => {
35
+ refuseIllegalNumbering(input);
36
+ },
37
+ };
38
+ }
39
+ function refuseIllegalNumbering(input) {
40
+ const kind = KIND_BY_SETTING_CODE[input.code];
41
+ if (kind === undefined)
42
+ return;
43
+ const series = input.projection.map((channel) => ({
44
+ salesChannelId: channel.salesChannelId,
45
+ salesChannelCode: channel.salesChannelCode,
46
+ salesChannelName: channel.salesChannelName,
47
+ pattern: effectivePattern(kind, typeof channel.value === 'string' ? channel.value : null),
48
+ }));
49
+ // A pattern with no sequence token renders one string for a whole year, so it
50
+ // collides with itself on the second document — a self-collision the pairwise
51
+ // sweep would never look for. Checked on the series this write sets: a
52
+ // platform-wide write reaches every channel without its own row, so it is
53
+ // checked against all of them.
54
+ const targeted = new Set(input.targetedChannelIds);
55
+ const written = targeted.size > 0
56
+ ? series.filter((one) => targeted.has(one.salesChannelId))
57
+ : series;
58
+ if (written.some((one) => patternSequenceDefect(one.pattern) !== null)) {
59
+ throw new HttpError(400, ERROR_CODES.INVOICE_NUMBER_PATTERN_COLLIDES, 'This numbering pattern has no sequence token, so every document in this sales ' +
60
+ 'channel would be given the same number.', { code: 'no_sequence_token', kind });
61
+ }
62
+ const [collision] = findNumberPatternCollisions(series);
63
+ if (collision === undefined)
64
+ return;
65
+ // Name the channel the operator is *not* editing — that is the one they have
66
+ // to reason about. A platform-wide write targets nobody, so either side is as
67
+ // informative as the other.
68
+ const other = targeted.has(collision.a.salesChannelId) ? collision.b : collision.a;
69
+ throw new HttpError(400, ERROR_CODES.INVOICE_NUMBER_PATTERN_COLLIDES, `This numbering pattern can produce the same document number as sales channel ` +
70
+ `"${other.salesChannelName}".`, {
71
+ code: 'other_channel',
72
+ kind,
73
+ channel: other.salesChannelName,
74
+ channelCode: other.salesChannelCode,
75
+ // Never interpolated into a sentence: a pattern contains `{seq}`, and the
76
+ // envelope discards a translation with an unfilled placeholder whole. It
77
+ // travels here for the admin UI to render outside the message.
78
+ otherPattern: other.pattern,
79
+ example: collision.example,
80
+ });
81
+ }
82
+ //# sourceMappingURL=numbering-write-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"numbering-write-validator.js","sourceRoot":"","sources":["../../../src/backend/services/numbering-write-validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ import { type SellerCompanyData } from '@endora-commerce/contracts';
3
+ /**
4
+ * The slice of `SettingsService` this module reads through.
5
+ *
6
+ * `salesChannelId` is `string | null`, matching the service (D-41): `null` is a
7
+ * platform-wide read, and it is the only honest thing to pass for an invoice
8
+ * whose order carries no channel. The narrower `string` this used to declare is
9
+ * what pushed `invoices` into the `''` sentinel (issue #103).
10
+ */
11
+ export interface SettingsReader {
12
+ get<T>(code: string, salesChannelId: string | null, schema: z.ZodType<T>): Promise<T>;
13
+ }
14
+ /**
15
+ * Resolves the seller (own company) data + VAT/NIP from settings for a sales
16
+ * channel and merges them. Throws an actionable 422 when the minimum required
17
+ * fields are missing so issuance never produces an invalid document (FR-006).
18
+ */
19
+ export declare class SellerSettingsResolver {
20
+ private readonly settings;
21
+ constructor(settings: SettingsReader);
22
+ resolve(salesChannelId: string): Promise<SellerCompanyData>;
23
+ }
24
+ //# sourceMappingURL=seller-settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seller-settings.d.ts","sourceRoot":"","sources":["../../../src/backend/services/seller-settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAG7F;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACvF;AAED;;;;GAIG;AACH,qBAAa,sBAAsB;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,cAAc;IAE/C,OAAO,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA6BlE"}
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { ERROR_CODES } from '@endora-commerce/contracts';
4
+ import { sellerCompanyDataSchema } from '@endora-commerce/contracts';
5
+ import { INVOICES_SETTING_CODES } from '../../manifest.js';
6
+ /**
7
+ * Resolves the seller (own company) data + VAT/NIP from settings for a sales
8
+ * channel and merges them. Throws an actionable 422 when the minimum required
9
+ * fields are missing so issuance never produces an invalid document (FR-006).
10
+ */
11
+ export class SellerSettingsResolver {
12
+ settings;
13
+ constructor(settings) {
14
+ this.settings = settings;
15
+ }
16
+ async resolve(salesChannelId) {
17
+ let taxId = '';
18
+ let raw = {};
19
+ try {
20
+ taxId = await this.settings.get(INVOICES_SETTING_CODES.SELLER_TAX_ID, salesChannelId, z.string());
21
+ }
22
+ catch {
23
+ taxId = '';
24
+ }
25
+ try {
26
+ raw = await this.settings.get(INVOICES_SETTING_CODES.SELLER_COMPANY_DATA, salesChannelId, z.record(z.string(), z.unknown()));
27
+ }
28
+ catch {
29
+ raw = {};
30
+ }
31
+ const parsed = sellerCompanyDataSchema.safeParse({ ...raw, taxId: taxId || raw.taxId });
32
+ if (!parsed.success || !parsed.data.legalName || !parsed.data.taxId) {
33
+ throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'Seller VAT/NIP and company data must be configured before issuing invoices.', { setting: 'invoices.seller.company_data' });
34
+ }
35
+ return parsed.data;
36
+ }
37
+ }
38
+ //# sourceMappingURL=seller-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seller-settings.js","sourceRoot":"","sources":["../../../src/backend/services/seller-settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAA0B,MAAM,4BAA4B,CAAC;AAC7F,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAc3D;;;;GAIG;AACH,MAAM,OAAO,sBAAsB;IACJ;IAA7B,YAA6B,QAAwB;QAAxB,aAAQ,GAAR,QAAQ,CAAgB;IAAG,CAAC;IAEzD,KAAK,CAAC,OAAO,CAAC,cAAsB;QAClC,IAAI,KAAK,GAAG,EAAE,CAAC;QACf,IAAI,GAAG,GAAY,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,sBAAsB,CAAC,aAAa,EAAE,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACpG,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,GAAG,EAAE,CAAC;QACb,CAAC;QACD,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAC3B,sBAAsB,CAAC,mBAAmB,EAC1C,cAAc,EACd,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAClC,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,GAAG,EAAE,CAAC;QACX,CAAC;QAED,MAAM,MAAM,GAAG,uBAAuB,CAAC,SAAS,CAAC,EAAE,GAAI,GAAc,EAAE,KAAK,EAAE,KAAK,IAAK,GAA0B,CAAC,KAAK,EAAE,CAAC,CAAC;QAC5H,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACpE,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,WAAW,CAAC,iBAAiB,EAC7B,6EAA6E,EAC7E,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAC5C,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;CACF"}
@@ -0,0 +1,10 @@
1
+ export interface LedgerNumberingLookup {
2
+ numberingModeFor(salesChannelId: string | null): Promise<'endora' | 'vendor'>;
3
+ activeVendorModuleId(): Promise<string | null>;
4
+ }
5
+ /**
6
+ * Mode B wait: vendor numbering and an active ledger vendor keep the invoice
7
+ * pending so PDF/email wait for applyVendorAssignedNumber.
8
+ */
9
+ export declare function shouldHoldForVendorNumber(routing: LedgerNumberingLookup | undefined, salesChannelId: string | null): Promise<boolean>;
10
+ //# sourceMappingURL=vendor-number-hold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vendor-number-hold.d.ts","sourceRoot":"","sources":["../../../src/backend/services/vendor-number-hold.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC,CAAC;IAC9E,oBAAoB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAChD;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,qBAAqB,GAAG,SAAS,EAC1C,cAAc,EAAE,MAAM,GAAG,IAAI,GAC5B,OAAO,CAAC,OAAO,CAAC,CAOlB"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Mode B wait: vendor numbering and an active ledger vendor keep the invoice
3
+ * pending so PDF/email wait for applyVendorAssignedNumber.
4
+ */
5
+ export async function shouldHoldForVendorNumber(routing, salesChannelId) {
6
+ if (!routing)
7
+ return false;
8
+ const [mode, vendor] = await Promise.all([
9
+ routing.numberingModeFor(salesChannelId),
10
+ routing.activeVendorModuleId(),
11
+ ]);
12
+ return mode === 'vendor' && vendor !== null;
13
+ }
14
+ //# sourceMappingURL=vendor-number-hold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vendor-number-hold.js","sourceRoot":"","sources":["../../../src/backend/services/vendor-number-hold.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,OAA0C,EAC1C,cAA6B;IAE7B,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3B,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACvC,OAAO,CAAC,gBAAgB,CAAC,cAAc,CAAC;QACxC,OAAO,CAAC,oBAAoB,EAAE;KAC/B,CAAC,CAAC;IACH,OAAO,IAAI,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,CAAC;AAC9C,CAAC"}