@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,157 @@
1
+ import { LockMode } from '@mikro-orm/core';
2
+ import { z } from 'zod';
3
+ import { ERROR_CODES } from '@endora-commerce/contracts';
4
+ import { HttpError } from '@endora-commerce/platform/http';
5
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
6
+ import { InvoiceNumberCounter } from '../entities/invoice-number-counter.entity.js';
7
+ /**
8
+ * The shipped defaults, which **must** stay identical to the manifest
9
+ * `defaultValue`s (`manifest.ts`): the manifest default is what production
10
+ * resolves, and this map is only reached when the settings read throws.
11
+ *
12
+ * The `/` on both sides of `{channel}` is load-bearing, not cosmetic. A
13
+ * discriminator concatenated straight onto the sequence loses the boundary
14
+ * between the two variable-length fields, so `FV {channel}{seq}/{YYYY}` renders
15
+ * `FV A12/2026` for both (`a1`, seq 2) and (`a`, seq 12) — see D-95.1
16
+ * property 3.
17
+ */
18
+ const DEFAULT_PATTERNS = {
19
+ invoice: 'FV {seq}/{channel}/{YYYY}',
20
+ proforma: 'PRO {seq}/{channel}/{YYYY}',
21
+ correction: 'KOR {seq}/{channel}/{YYYY}',
22
+ };
23
+ /** The pre-D-95 defaults, kept for the system-default channel (D-95.3 move 3). */
24
+ export const PRE_CHANNEL_DEFAULT_PATTERNS = {
25
+ invoice: 'FV {seq}/{YYYY}',
26
+ proforma: 'PRO {seq}/{YYYY}',
27
+ correction: 'KOR {seq}/{YYYY}',
28
+ };
29
+ /**
30
+ * The pattern a stored setting value really means: a blank value is not an
31
+ * empty pattern, it is "unset", and it resolves to the default. Two channels
32
+ * can therefore collide while their stored values differ, which is why the
33
+ * collision predicate compares what would be *resolved* and never the raw row.
34
+ */
35
+ export function effectivePattern(kind, stored) {
36
+ return stored && stored.trim() ? stored : DEFAULT_PATTERNS[kind];
37
+ }
38
+ /**
39
+ * Render an invoice-number pattern. Supported tokens:
40
+ * {seq} — sequence number
41
+ * {seq:N} — zero-padded to width N
42
+ * {channel} — the sales channel discriminator (feature 078, D-95)
43
+ * {YYYY} — 4-digit year
44
+ * {YY} — 2-digit year
45
+ * {MM} — 2-digit month
46
+ * Everything else is literal (so prefix/suffix are just literal text).
47
+ *
48
+ * `channel` is a **required** member of the context, not an optional one
49
+ * defaulting to `''`: an absent discriminator makes every channel render
50
+ * identically, which is the defect this token exists to remove. A caller that
51
+ * cannot name a channel must fail rather than render.
52
+ *
53
+ * Pure — the collision predicate and the boot report both call it directly.
54
+ */
55
+ export function formatInvoiceNumber(pattern, ctx) {
56
+ const year = ctx.date.getFullYear();
57
+ const month = ctx.date.getMonth() + 1;
58
+ return pattern.replace(/\{(seq(?::(\d+))?|channel|YYYY|YY|MM)\}/g, (_m, token, pad) => {
59
+ if (token === 'YYYY')
60
+ return String(year);
61
+ if (token === 'YY')
62
+ return String(year % 100).padStart(2, '0');
63
+ if (token === 'MM')
64
+ return String(month).padStart(2, '0');
65
+ if (token === 'channel')
66
+ return ctx.channel;
67
+ // seq or seq:N
68
+ if (pad)
69
+ return String(ctx.seq).padStart(Number(pad), '0');
70
+ return String(ctx.seq);
71
+ });
72
+ }
73
+ /**
74
+ * The discriminator a channel code renders as: uppercase, and otherwise
75
+ * verbatim. The code charset is `/^[a-z][a-z0-9_-]*$/`, so uppercasing is
76
+ * injective — no two channels can produce the same discriminator. Stripping or
77
+ * folding any character would break that and is forbidden.
78
+ */
79
+ export function channelDiscriminator(code) {
80
+ return code.toUpperCase();
81
+ }
82
+ /**
83
+ * Draws gap-free, per-(channel, kind, year) sequence numbers under a pessimistic
84
+ * row lock and assembles the human-readable number from the configured pattern.
85
+ */
86
+ export class InvoiceNumberGenerator {
87
+ patterns;
88
+ constructor(patterns) {
89
+ this.patterns = patterns;
90
+ }
91
+ /** Must run inside an open transaction (`em.transactional(...)`). */
92
+ async next(tx, kind, salesChannelId, issuedAt) {
93
+ // command-coverage-ignore: gap-free sequence-counter increment under a row
94
+ // lock, invoked within the audited issue()/createCorrection() transaction;
95
+ // internal numbering bookkeeping, not a standalone audited write.
96
+ const periodYear = issuedAt.getFullYear();
97
+ let counter = await tx.findOne(InvoiceNumberCounter, { salesChannelId, kind, periodYear }, { lockMode: LockMode.PESSIMISTIC_WRITE });
98
+ if (!counter) {
99
+ counter = tx.create(InvoiceNumberCounter, { salesChannelId, kind, periodYear, currentValue: 0 });
100
+ }
101
+ counter.currentValue += 1;
102
+ await tx.persistAndFlush(counter);
103
+ const pattern = await this.resolvePattern(kind, salesChannelId);
104
+ return formatInvoiceNumber(pattern, {
105
+ seq: counter.currentValue,
106
+ date: issuedAt,
107
+ channel: await this.resolveDiscriminator(tx, salesChannelId),
108
+ });
109
+ }
110
+ /**
111
+ * The `{channel}` discriminator for a channel id, read from the kernel's
112
+ * `sales_channels` row.
113
+ *
114
+ * An absent row **fails** rather than rendering an empty discriminator. A
115
+ * fallback here would put every channel back on one string and restore the
116
+ * defect this feature removes; a `salesChannelId` with no row behind it is a
117
+ * data defect in whatever produced it (`Order.salesChannelId` is non-nullable
118
+ * and a system-default channel always exists), and it is reported, not
119
+ * absorbed.
120
+ */
121
+ async resolveDiscriminator(tx, salesChannelId) {
122
+ const channel = await tx.findOne(SalesChannel, { id: salesChannelId });
123
+ if (!channel) {
124
+ throw new HttpError(500, ERROR_CODES.INTERNAL, `Sales channel "${salesChannelId}" has no row, so an invoice number cannot be ` +
125
+ `rendered for it.`);
126
+ }
127
+ return channelDiscriminator(channel.code);
128
+ }
129
+ async resolvePattern(kind, salesChannelId) {
130
+ if (!this.patterns)
131
+ return DEFAULT_PATTERNS[kind];
132
+ try {
133
+ return effectivePattern(kind, await this.patterns.resolvePattern(kind, salesChannelId));
134
+ }
135
+ catch {
136
+ return DEFAULT_PATTERNS[kind];
137
+ }
138
+ }
139
+ }
140
+ /**
141
+ * Settings-backed pattern resolver: reads `invoices.numbering.<kind>.pattern`
142
+ * per sales channel, falling back to the built-in default.
143
+ */
144
+ export function createSettingsPatternResolver(settingsService) {
145
+ return {
146
+ async resolvePattern(kind, salesChannelId) {
147
+ try {
148
+ return await settingsService.get(`invoices.numbering.${kind}.pattern`, salesChannelId, z.string());
149
+ }
150
+ catch {
151
+ return DEFAULT_PATTERNS[kind];
152
+ }
153
+ },
154
+ };
155
+ }
156
+ export { DEFAULT_PATTERNS };
157
+ //# sourceMappingURL=invoice-number-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice-number-generator.js","sourceRoot":"","sources":["../../../src/backend/services/invoice-number-generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import type { InvoicePdfBlockRegistration, InvoicePdfBlockRegistryPort } from '@endora-commerce/contracts';
2
+ /** Two different contributions for one block name. */
3
+ export declare class InvoicePdfBlockConflictError extends Error {
4
+ constructor(name: string, existingModuleId: string, moduleId: string);
5
+ }
6
+ /** A registration this registry refuses on its shape, before it is stored. */
7
+ export declare class InvoicePdfBlockRegistrationError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ /**
11
+ * Invoice template blocks another module renders onto the PDF
12
+ * (`specs/134-paid-module-extraction/` T063 and T126; `research.md` D11, D12
13
+ * and D16 §2(e)).
14
+ *
15
+ * **Why the seam exists.** `ksef.InvoiceSection` is declared by `ksef`, and
16
+ * until T063 this module seeded, rendered and described it, while the data its
17
+ * QR code needs reached the renderer only where a composition root called
18
+ * `setKsefVerificationResolver`. Both halves read as working for as long as
19
+ * both modules were in the tree, and the second did not work at all on an
20
+ * instance composed through the platform alone. The declaring module now
21
+ * renders, describes and enriches its own block, and registers all three here
22
+ * from its composition root; this module names no contributor.
23
+ *
24
+ * **A contribution registry in D-39's sense**, and a plain `di.register`: a
25
+ * contributor pushes from a boot hook whatever this module's effective state
26
+ * is, so a gate here would stop the backend from starting for an operator who
27
+ * switched invoicing off.
28
+ *
29
+ * **The policy it states: skip an absent contributor's block.** While the
30
+ * declaring module is not present its block is not described to the builder,
31
+ * not resolved and not rendered — the same answer a stored block of an
32
+ * uninstalled module gets (`specs/096-page-builder-block-ownership/` FR-019,
33
+ * FR-020): the PDF simply has no such section, and the stored props are
34
+ * untouched. Presence is read on every call, so switching the contributor back
35
+ * on takes effect on the next render with no restart.
36
+ *
37
+ * Three more rules, each stated because the alternative is plausible:
38
+ *
39
+ * 1. **The owner segment is the contributor.** A block name is
40
+ * `<moduleId>.<LocalName>` and is persisted in every template that places
41
+ * it, so a module may contribute only under its own id — the rule
42
+ * `check:block-names` holds manifests to.
43
+ * 2. **`invoices`' own blocks are not contributable.** This module renders its
44
+ * ten itself; a contribution under `invoices.` could only shadow one.
45
+ * 3. **A second contributor for one name is refused**, not ordered, so which
46
+ * module renders a block never depends on which composed last. The
47
+ * identical registration twice is accepted.
48
+ */
49
+ export declare class InvoicePdfBlockRegistry implements InvoicePdfBlockRegistryPort {
50
+ private readonly isModulePresent;
51
+ private readonly registrations;
52
+ /**
53
+ * @param isModulePresent the effective-state probe. Defaults to
54
+ * always-present, so a registry a unit test builds answers about what that
55
+ * test registered; the module's composition wires it to the kernel's
56
+ * effective state.
57
+ */
58
+ constructor(isModulePresent?: (moduleId: string) => boolean);
59
+ register(registration: InvoicePdfBlockRegistration): void;
60
+ /** The contributed block of that name, or `undefined` while its contributor is absent. */
61
+ find(name: string): InvoicePdfBlockRegistration | undefined;
62
+ /** Every contributed block whose contributor is present, in registration order. */
63
+ present(): InvoicePdfBlockRegistration[];
64
+ }
65
+ //# sourceMappingURL=invoice-pdf-block-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice-pdf-block-registry.d.ts","sourceRoot":"","sources":["../../../src/backend/services/invoice-pdf-block-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,2BAA2B,EAC3B,2BAA2B,EAC5B,MAAM,4BAA4B,CAAC;AAEpC,sDAAsD;AACtD,qBAAa,4BAA6B,SAAQ,KAAK;gBACzC,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAOrE;AAED,8EAA8E;AAC9E,qBAAa,gCAAiC,SAAQ,KAAK;gBAC7C,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,uBAAwB,YAAW,2BAA2B;IAS7D,OAAO,CAAC,QAAQ,CAAC,eAAe;IAR5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkD;IAEhF;;;;;OAKG;gBAC0B,eAAe,GAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAoB;IAExF,QAAQ,CAAC,YAAY,EAAE,2BAA2B,GAAG,IAAI;IAqBzD,0FAA0F;IAC1F,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,2BAA2B,GAAG,SAAS;IAK3D,mFAAmF;IACnF,OAAO,IAAI,2BAA2B,EAAE;CAKzC"}
@@ -0,0 +1,94 @@
1
+ /** Two different contributions for one block name. */
2
+ export class InvoicePdfBlockConflictError extends Error {
3
+ constructor(name, existingModuleId, moduleId) {
4
+ super(`invoicePdfBlockRegistry: block "${name}" is already contributed by module ` +
5
+ `"${existingModuleId}"; module "${moduleId}" cannot register a second one.`);
6
+ this.name = 'InvoicePdfBlockConflictError';
7
+ }
8
+ }
9
+ /** A registration this registry refuses on its shape, before it is stored. */
10
+ export class InvoicePdfBlockRegistrationError extends Error {
11
+ constructor(message) {
12
+ super(`invoicePdfBlockRegistry: ${message}`);
13
+ this.name = 'InvoicePdfBlockRegistrationError';
14
+ }
15
+ }
16
+ /**
17
+ * Invoice template blocks another module renders onto the PDF
18
+ * (`specs/134-paid-module-extraction/` T063 and T126; `research.md` D11, D12
19
+ * and D16 §2(e)).
20
+ *
21
+ * **Why the seam exists.** `ksef.InvoiceSection` is declared by `ksef`, and
22
+ * until T063 this module seeded, rendered and described it, while the data its
23
+ * QR code needs reached the renderer only where a composition root called
24
+ * `setKsefVerificationResolver`. Both halves read as working for as long as
25
+ * both modules were in the tree, and the second did not work at all on an
26
+ * instance composed through the platform alone. The declaring module now
27
+ * renders, describes and enriches its own block, and registers all three here
28
+ * from its composition root; this module names no contributor.
29
+ *
30
+ * **A contribution registry in D-39's sense**, and a plain `di.register`: a
31
+ * contributor pushes from a boot hook whatever this module's effective state
32
+ * is, so a gate here would stop the backend from starting for an operator who
33
+ * switched invoicing off.
34
+ *
35
+ * **The policy it states: skip an absent contributor's block.** While the
36
+ * declaring module is not present its block is not described to the builder,
37
+ * not resolved and not rendered — the same answer a stored block of an
38
+ * uninstalled module gets (`specs/096-page-builder-block-ownership/` FR-019,
39
+ * FR-020): the PDF simply has no such section, and the stored props are
40
+ * untouched. Presence is read on every call, so switching the contributor back
41
+ * on takes effect on the next render with no restart.
42
+ *
43
+ * Three more rules, each stated because the alternative is plausible:
44
+ *
45
+ * 1. **The owner segment is the contributor.** A block name is
46
+ * `<moduleId>.<LocalName>` and is persisted in every template that places
47
+ * it, so a module may contribute only under its own id — the rule
48
+ * `check:block-names` holds manifests to.
49
+ * 2. **`invoices`' own blocks are not contributable.** This module renders its
50
+ * ten itself; a contribution under `invoices.` could only shadow one.
51
+ * 3. **A second contributor for one name is refused**, not ordered, so which
52
+ * module renders a block never depends on which composed last. The
53
+ * identical registration twice is accepted.
54
+ */
55
+ export class InvoicePdfBlockRegistry {
56
+ isModulePresent;
57
+ registrations = new Map();
58
+ /**
59
+ * @param isModulePresent the effective-state probe. Defaults to
60
+ * always-present, so a registry a unit test builds answers about what that
61
+ * test registered; the module's composition wires it to the kernel's
62
+ * effective state.
63
+ */
64
+ constructor(isModulePresent = () => true) {
65
+ this.isModulePresent = isModulePresent;
66
+ }
67
+ register(registration) {
68
+ const { name, moduleId } = registration;
69
+ if (moduleId === 'invoices' || name.startsWith('invoices.')) {
70
+ throw new InvoicePdfBlockRegistrationError(`"${name}" is under \`invoices\`' own namespace — this module renders its own blocks itself.`);
71
+ }
72
+ if (!name.startsWith(`${moduleId}.`) || name.length <= moduleId.length + 1) {
73
+ throw new InvoicePdfBlockRegistrationError(`"${name}" does not carry "${moduleId}" as its owner segment — a module contributes ` +
74
+ 'only the blocks its own manifest declares.');
75
+ }
76
+ const existing = this.registrations.get(name);
77
+ if (existing) {
78
+ if (existing === registration)
79
+ return;
80
+ throw new InvoicePdfBlockConflictError(name, existing.moduleId, moduleId);
81
+ }
82
+ this.registrations.set(name, registration);
83
+ }
84
+ /** The contributed block of that name, or `undefined` while its contributor is absent. */
85
+ find(name) {
86
+ const registration = this.registrations.get(name);
87
+ return registration && this.isModulePresent(registration.moduleId) ? registration : undefined;
88
+ }
89
+ /** Every contributed block whose contributor is present, in registration order. */
90
+ present() {
91
+ return [...this.registrations.values()].filter((registration) => this.isModulePresent(registration.moduleId));
92
+ }
93
+ }
94
+ //# sourceMappingURL=invoice-pdf-block-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice-pdf-block-registry.js","sourceRoot":"","sources":["../../../src/backend/services/invoice-pdf-block-registry.ts"],"names":[],"mappings":"AAKA,sDAAsD;AACtD,MAAM,OAAO,4BAA6B,SAAQ,KAAK;IACrD,YAAY,IAAY,EAAE,gBAAwB,EAAE,QAAgB;QAClE,KAAK,CACH,mCAAmC,IAAI,qCAAqC;YAC1E,IAAI,gBAAgB,cAAc,QAAQ,iCAAiC,CAC9E,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,8BAA8B,CAAC;IAC7C,CAAC;CACF;AAED,8EAA8E;AAC9E,MAAM,OAAO,gCAAiC,SAAQ,KAAK;IACzD,YAAY,OAAe;QACzB,KAAK,CAAC,4BAA4B,OAAO,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,IAAI,GAAG,kCAAkC,CAAC;IACjD,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,OAAO,uBAAuB;IASL;IARZ,aAAa,GAAG,IAAI,GAAG,EAAuC,CAAC;IAEhF;;;;;OAKG;IACH,YAA6B,kBAAiD,GAAG,EAAE,CAAC,IAAI;QAA3D,oBAAe,GAAf,eAAe,CAA4C;IAAG,CAAC;IAE5F,QAAQ,CAAC,YAAyC;QAChD,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,YAAY,CAAC;QACxC,IAAI,QAAQ,KAAK,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5D,MAAM,IAAI,gCAAgC,CACxC,IAAI,IAAI,qFAAqF,CAC9F,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,QAAQ,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3E,MAAM,IAAI,gCAAgC,CACxC,IAAI,IAAI,qBAAqB,QAAQ,gDAAgD;gBACnF,4CAA4C,CAC/C,CAAC;QACJ,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,QAAQ,KAAK,YAAY;gBAAE,OAAO;YACtC,MAAM,IAAI,4BAA4B,CAAC,IAAI,EAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC5E,CAAC;QACD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC7C,CAAC;IAED,0FAA0F;IAC1F,IAAI,CAAC,IAAY;QACf,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,YAAY,IAAI,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;IAChG,CAAC;IAED,mFAAmF;IACnF,OAAO;QACL,OAAO,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY,EAAE,EAAE,CAC9D,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,QAAQ,CAAC,CAC5C,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,57 @@
1
+ import type { Content } from 'pdfmake/interfaces.js';
2
+ import type { InvoiceDetail } from '@endora-commerce/contracts';
3
+ import type { AmountToWordsLocale } from './amount-to-words.js';
4
+ import { InvoicePdfBlockRegistry } from './invoice-pdf-block-registry.js';
5
+ import { type LoadAssetImage } from '../pdf-components/embed-logo-images.js';
6
+ export type InvoicePdfRendererOptions = {
7
+ /** Load library asset bytes for InvoiceLogo (avoids pdfmake self-HTTP). */
8
+ loadAssetImage?: LoadAssetImage;
9
+ /**
10
+ * The blocks other modules render (T063/T126). Defaults to an empty,
11
+ * always-present registry, so a renderer a unit test builds renders this
12
+ * module's own blocks and nothing else.
13
+ */
14
+ blocks?: InvoicePdfBlockRegistry;
15
+ };
16
+ /**
17
+ * Renders an invoice to a PDF Buffer using pdfmake (feature 047, R1/R2/US6).
18
+ *
19
+ * When a WYSIWYG (Puck) template tree is supplied and yields renderable invoice
20
+ * components, the PDF follows that layout; otherwise it falls back to the
21
+ * built-in generic layout (FR-016). Both paths share the same section builders.
22
+ */
23
+ export declare class InvoicePdfRenderer {
24
+ #private;
25
+ /**
26
+ * The contributed-block seam — the generalisation of the
27
+ * `setKsefVerificationResolver` this class carried from feature 059 to 134.
28
+ *
29
+ * That setter was one vendor's contribution point: a composition root had to
30
+ * call it after building both modules, which only this repository's
31
+ * reference root did, so on an instance composed through the platform the
32
+ * PDF carried no KSeF verification block while `ksef` was installed and on
33
+ * (`research.md` D16 §1 item 7). A contributor now registers the whole block —
34
+ * renderer, description and the per-render data it needs — from its own
35
+ * composition, and every render path reads it here.
36
+ */
37
+ readonly blocks: InvoicePdfBlockRegistry;
38
+ constructor(opts?: InvoicePdfRendererOptions);
39
+ render(invoice: InvoiceDetail, locale?: AmountToWordsLocale, templateTree?: unknown): Promise<Buffer>;
40
+ /**
41
+ * The pdfmake content an invoice renders to — the template's blocks, or the
42
+ * built-in layout when the template places none this module or a present
43
+ * contributor renders (FR-016).
44
+ *
45
+ * Public because it is the one place every render path meets, which makes it
46
+ * the place a test asserts what a PDF carries without parsing one.
47
+ */
48
+ content(invoice: InvoiceDetail, locale?: AmountToWordsLocale, templateTree?: unknown): Promise<Content[]>;
49
+ /**
50
+ * Each block's per-render data, read once. A rejection is the block's `null`:
51
+ * the data is an enrichment, and an invoice — a legal document — never fails
52
+ * to render on it. The `catch` is around a contributed callback, not a port.
53
+ */
54
+ private resolveContributed;
55
+ private buildDoc;
56
+ }
57
+ //# sourceMappingURL=invoice-pdf-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice-pdf-renderer.d.ts","sourceRoot":"","sources":["../../../src/backend/services/invoice-pdf-renderer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAyC,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAC5F,OAAO,KAAK,EAAE,aAAa,EAA+B,MAAM,4BAA4B,CAAC;AAC7F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAchE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,wCAAwC,CAAC;AA+DhD,MAAM,MAAM,yBAAyB,GAAG;IACtC,2EAA2E;IAC3E,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;;OAIG;IACH,MAAM,CAAC,EAAE,uBAAuB,CAAC;CAClC,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,kBAAkB;;IAG7B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;gBAE7B,IAAI,GAAE,yBAA8B;IAK1C,MAAM,CACV,OAAO,EAAE,aAAa,EACtB,MAAM,GAAE,mBAA0B,EAClC,YAAY,CAAC,EAAE,OAAO,GACrB,OAAO,CAAC,MAAM,CAAC;IAWlB;;;;;;;OAOG;IACG,OAAO,CACX,OAAO,EAAE,aAAa,EACtB,MAAM,GAAE,mBAA0B,EAClC,YAAY,CAAC,EAAE,OAAO,GACrB,OAAO,CAAC,OAAO,EAAE,CAAC;IAgDrB;;;;OAIG;YACW,kBAAkB;IAgBhC,OAAO,CAAC,QAAQ;CASjB"}
@@ -0,0 +1,158 @@
1
+ import path from 'node:path';
2
+ import { createRequire } from 'node:module';
3
+ import { headerSection, partiesSection, lineItemsSection, vatSummarySection, totalsSection, } from '../pdf-components/sections.js';
4
+ import { INVOICE_COMPONENT_NAMES, placedBlockNames, treeToContent, } from '../pdf-components/tree-mapper.js';
5
+ import { InvoicePdfBlockRegistry } from './invoice-pdf-block-registry.js';
6
+ import { embedInvoiceLogoImages, } from '../pdf-components/embed-logo-images.js';
7
+ const requireFromHere = createRequire(import.meta.url);
8
+ const pdfMake = requireFromHere('pdfmake');
9
+ function buildFontDictionary() {
10
+ const fontsDir = requireFromHere
11
+ .resolve('pdfmake/package.json')
12
+ .replace(/package\.json$/, `fonts${path.sep}Roboto${path.sep}`);
13
+ return {
14
+ Roboto: {
15
+ normal: `${fontsDir}Roboto-Regular.ttf`,
16
+ bold: `${fontsDir}Roboto-Medium.ttf`,
17
+ italics: `${fontsDir}Roboto-Italic.ttf`,
18
+ bolditalics: `${fontsDir}Roboto-MediumItalic.ttf`,
19
+ },
20
+ };
21
+ }
22
+ let fontsRegistered = false;
23
+ /**
24
+ * Built-in generic layout — the FR-016 fallback when no valid template exists.
25
+ *
26
+ * Its last entries are the present contributors' blocks, with their default
27
+ * props. That is where `ksefSection(inv)` used to be written out: a use of the
28
+ * KSeF block the E4 ruling's table did not list, generalised with the other
29
+ * four rather than kept (`specs/134-paid-module-extraction/` T063).
30
+ */
31
+ function builtinLayout(inv, locale, contributed, suppressKsefNumber) {
32
+ return [
33
+ headerSection(inv, {}, { suppressKsefNumber }),
34
+ partiesSection(inv),
35
+ lineItemsSection(inv),
36
+ {
37
+ columns: [
38
+ { width: '55%', stack: [vatSummarySection(inv)] },
39
+ { width: '45%', stack: [totalsSection(inv, locale)] },
40
+ ],
41
+ margin: [0, 0, 0, 16],
42
+ },
43
+ ...(inv.kind === 'correction' && inv.originalInvoiceId
44
+ ? [{ text: 'Dokument korygujący do faktury pierwotnej.', italics: true, margin: [0, 0, 0, 8] }]
45
+ : []),
46
+ ...contributed,
47
+ ];
48
+ }
49
+ const OWN_BLOCKS = new Set(INVOICE_COMPONENT_NAMES);
50
+ /**
51
+ * Renders an invoice to a PDF Buffer using pdfmake (feature 047, R1/R2/US6).
52
+ *
53
+ * When a WYSIWYG (Puck) template tree is supplied and yields renderable invoice
54
+ * components, the PDF follows that layout; otherwise it falls back to the
55
+ * built-in generic layout (FR-016). Both paths share the same section builders.
56
+ */
57
+ export class InvoicePdfRenderer {
58
+ #loadAssetImage;
59
+ /**
60
+ * The contributed-block seam — the generalisation of the
61
+ * `setKsefVerificationResolver` this class carried from feature 059 to 134.
62
+ *
63
+ * That setter was one vendor's contribution point: a composition root had to
64
+ * call it after building both modules, which only this repository's
65
+ * reference root did, so on an instance composed through the platform the
66
+ * PDF carried no KSeF verification block while `ksef` was installed and on
67
+ * (`research.md` D16 §1 item 7). A contributor now registers the whole block —
68
+ * renderer, description and the per-render data it needs — from its own
69
+ * composition, and every render path reads it here.
70
+ */
71
+ blocks;
72
+ constructor(opts = {}) {
73
+ this.#loadAssetImage = opts.loadAssetImage;
74
+ this.blocks = opts.blocks ?? new InvoicePdfBlockRegistry();
75
+ }
76
+ async render(invoice, locale = 'pl', templateTree) {
77
+ if (!fontsRegistered) {
78
+ pdfMake.setFonts(buildFontDictionary());
79
+ // Images are inlined as data URIs by embedInvoiceLogoImages — deny network.
80
+ pdfMake.setUrlAccessPolicy(() => false);
81
+ fontsRegistered = true;
82
+ }
83
+ const content = await this.content(invoice, locale, templateTree);
84
+ return pdfMake.createPdf(this.buildDoc(content)).getBuffer();
85
+ }
86
+ /**
87
+ * The pdfmake content an invoice renders to — the template's blocks, or the
88
+ * built-in layout when the template places none this module or a present
89
+ * contributor renders (FR-016).
90
+ *
91
+ * Public because it is the one place every render path meets, which makes it
92
+ * the place a test asserts what a PDF carries without parsing one.
93
+ */
94
+ async content(invoice, locale = 'pl', templateTree) {
95
+ const tree = templateTree
96
+ ? await embedInvoiceLogoImages(templateTree, this.#loadAssetImage)
97
+ : undefined;
98
+ const present = this.blocks.present();
99
+ const placed = tree ? new Set(placedBlockNames(tree)) : null;
100
+ const resolved = await this.resolveContributed(invoice.id, present.filter((block) => placed === null || placed.has(block.name)));
101
+ const contributed = (name) => {
102
+ if (OWN_BLOCKS.has(name))
103
+ return undefined;
104
+ const block = this.blocks.find(name);
105
+ if (!block)
106
+ return undefined;
107
+ return (props) => block.render({ props, invoice, locale, resolved: resolved.get(name) ?? null });
108
+ };
109
+ // T137 (D22 §3(a)): the KSeF number is printed exactly once. The header
110
+ // stands down only while a block that prints it is present **and placed**
111
+ // in this tree; otherwise the header prints it, whatever any module's state.
112
+ const printsNumber = present.filter((block) => block.printsKsefReferenceNumber === true);
113
+ const suppressInTree = placed !== null && printsNumber.some((block) => placed.has(block.name));
114
+ const fromTemplate = tree
115
+ ? treeToContent(tree, invoice, locale, contributed, { suppressKsefNumber: suppressInTree })
116
+ : null;
117
+ if (fromTemplate)
118
+ return fromTemplate;
119
+ const fallbackResolved = placed === null ? resolved : await this.resolveContributed(invoice.id, present);
120
+ return builtinLayout(invoice, locale, present.map((block) => block.render({
121
+ props: {},
122
+ invoice,
123
+ locale,
124
+ resolved: fallbackResolved.get(block.name) ?? null,
125
+ })),
126
+ // The built-in layout places every present contributor's block.
127
+ printsNumber.length > 0);
128
+ }
129
+ /**
130
+ * Each block's per-render data, read once. A rejection is the block's `null`:
131
+ * the data is an enrichment, and an invoice — a legal document — never fails
132
+ * to render on it. The `catch` is around a contributed callback, not a port.
133
+ */
134
+ async resolveContributed(invoiceId, blocks) {
135
+ const out = new Map();
136
+ for (const block of blocks) {
137
+ if (!block.resolve)
138
+ continue;
139
+ try {
140
+ out.set(block.name, (await block.resolve(invoiceId)) ?? null);
141
+ }
142
+ catch {
143
+ out.set(block.name, null);
144
+ }
145
+ }
146
+ return out;
147
+ }
148
+ buildDoc(content) {
149
+ return {
150
+ pageSize: 'A4',
151
+ pageMargins: [40, 40, 40, 48],
152
+ defaultStyle: { fontSize: 10, font: 'Roboto' },
153
+ styles: { docTitle: { fontSize: 14, bold: true } },
154
+ content,
155
+ };
156
+ }
157
+ }
158
+ //# sourceMappingURL=invoice-pdf-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice-pdf-renderer.js","sourceRoot":"","sources":["../../../src/backend/services/invoice-pdf-renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Minimal PDF synthesis for invoice downloads (US2 T146).
3
+ *
4
+ * Not a production PDF generator — this produces a valid but trivial 1-page
5
+ * PDF with a plain-text block. Real typesetting waits for the pdfkit-based
6
+ * templating in a later polish sub-phase. The contract test only asserts the
7
+ * `%PDF-` magic header and the application/pdf Content-Type.
8
+ */
9
+ export declare function buildMinimalInvoicePdf(params: {
10
+ invoiceNumber: string;
11
+ total: string;
12
+ currency: string;
13
+ }): Buffer;
14
+ /**
15
+ * Bulk invoice "printout" (feature 038 US2) — one summary line per invoice in a
16
+ * single PDF. Same stub-grade synthesis as the single-invoice download; real
17
+ * per-invoice typesetting waits for the pdfkit templating phase.
18
+ */
19
+ export declare function buildBulkInvoicesPdf(invoices: Array<{
20
+ invoiceNumber: string;
21
+ total: string;
22
+ currency: string;
23
+ }>): Buffer;
24
+ //# sourceMappingURL=invoice-pdf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice-pdf.d.ts","sourceRoot":"","sources":["../../../src/backend/services/invoice-pdf.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAET;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,KAAK,CAAC;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,GAC1E,MAAM,CAMR"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Minimal PDF synthesis for invoice downloads (US2 T146).
3
+ *
4
+ * Not a production PDF generator — this produces a valid but trivial 1-page
5
+ * PDF with a plain-text block. Real typesetting waits for the pdfkit-based
6
+ * templating in a later polish sub-phase. The contract test only asserts the
7
+ * `%PDF-` magic header and the application/pdf Content-Type.
8
+ */
9
+ export function buildMinimalInvoicePdf(params) {
10
+ return buildTextPdf([`Invoice ${params.invoiceNumber}`, `Total: ${params.total} ${params.currency}`]);
11
+ }
12
+ /**
13
+ * Bulk invoice "printout" (feature 038 US2) — one summary line per invoice in a
14
+ * single PDF. Same stub-grade synthesis as the single-invoice download; real
15
+ * per-invoice typesetting waits for the pdfkit templating phase.
16
+ */
17
+ export function buildBulkInvoicesPdf(invoices) {
18
+ const lines = invoices.length === 0
19
+ ? ['No invoices selected.']
20
+ : invoices.map((i) => `${i.invoiceNumber} - ${i.total} ${i.currency}`);
21
+ return buildTextPdf(['Invoices', ...lines]);
22
+ }
23
+ /** Render an array of plain-text lines as a trivial single-page PDF. */
24
+ function buildTextPdf(lines) {
25
+ // One Tline per text line, stepping the text cursor down 16pt each time.
26
+ const escaped = lines.map((l) => l.replace(/([()\\])/g, '\\$1'));
27
+ const content = 'BT /F1 12 Tf 72 720 Td ' +
28
+ escaped.map((l, i) => (i === 0 ? `(${l}) Tj` : `0 -16 Td (${l}) Tj`)).join(' ') +
29
+ ' ET';
30
+ // Hand-authored objects are simpler than pulling in a PDF lib for a stub.
31
+ const objects = [
32
+ '1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n',
33
+ '2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n',
34
+ '3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n',
35
+ `4 0 obj\n<< /Length ${content.length} >>\nstream\n${content}\nendstream\nendobj\n`,
36
+ '5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n',
37
+ ];
38
+ let body = '%PDF-1.4\n';
39
+ const offsets = [];
40
+ for (const obj of objects) {
41
+ offsets.push(Buffer.byteLength(body, 'utf8'));
42
+ body += obj;
43
+ }
44
+ const xrefOffset = Buffer.byteLength(body, 'utf8');
45
+ body += `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`;
46
+ for (const off of offsets) {
47
+ body += `${off.toString().padStart(10, '0')} 00000 n \n`;
48
+ }
49
+ body += `trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF\n`;
50
+ return Buffer.from(body, 'utf8');
51
+ }
52
+ //# sourceMappingURL=invoice-pdf.js.map