@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.
- package/LICENSE +21 -0
- package/README.md +59 -2
- package/dist/admin/components/InvoiceSectionTabs.d.ts +22 -0
- package/dist/admin/components/InvoiceSectionTabs.d.ts.map +1 -0
- package/dist/admin/components/InvoiceSectionTabs.js +34 -0
- package/dist/admin/components/InvoiceSectionTabs.js.map +1 -0
- package/dist/admin/index.d.ts +34 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +69 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/InvoiceDetail.d.ts +10 -0
- package/dist/admin/pages/InvoiceDetail.d.ts.map +1 -0
- package/dist/admin/pages/InvoiceDetail.js +111 -0
- package/dist/admin/pages/InvoiceDetail.js.map +1 -0
- package/dist/admin/pages/InvoiceTemplateEditor.d.ts +11 -0
- package/dist/admin/pages/InvoiceTemplateEditor.d.ts.map +1 -0
- package/dist/admin/pages/InvoiceTemplateEditor.js +188 -0
- package/dist/admin/pages/InvoiceTemplateEditor.js.map +1 -0
- package/dist/admin/pages/InvoiceTemplatesPage.d.ts +10 -0
- package/dist/admin/pages/InvoiceTemplatesPage.d.ts.map +1 -0
- package/dist/admin/pages/InvoiceTemplatesPage.js +60 -0
- package/dist/admin/pages/InvoiceTemplatesPage.js.map +1 -0
- package/dist/admin/pages/InvoicesList.d.ts +9 -0
- package/dist/admin/pages/InvoicesList.d.ts.map +1 -0
- package/dist/admin/pages/InvoicesList.js +116 -0
- package/dist/admin/pages/InvoicesList.js.map +1 -0
- package/dist/admin/templates/described-blocks.d.ts +67 -0
- package/dist/admin/templates/described-blocks.d.ts.map +1 -0
- package/dist/admin/templates/described-blocks.js +83 -0
- package/dist/admin/templates/described-blocks.js.map +1 -0
- package/dist/admin/templates/invoice-builder-plugin.d.ts +4 -0
- package/dist/admin/templates/invoice-builder-plugin.d.ts.map +1 -0
- package/dist/admin/templates/invoice-builder-plugin.js +12 -0
- package/dist/admin/templates/invoice-builder-plugin.js.map +1 -0
- package/dist/admin/templates/invoice-puck-config.d.ts +37 -0
- package/dist/admin/templates/invoice-puck-config.d.ts.map +1 -0
- package/dist/admin/templates/invoice-puck-config.js +733 -0
- package/dist/admin/templates/invoice-puck-config.js.map +1 -0
- package/dist/backend/email-templates/invoice-issued.default.d.ts +10 -0
- package/dist/backend/email-templates/invoice-issued.default.d.ts.map +1 -0
- package/dist/backend/email-templates/invoice-issued.default.js +45 -0
- package/dist/backend/email-templates/invoice-issued.default.js.map +1 -0
- package/dist/backend/entities/invoice-external-attachment.entity.d.ts +20 -0
- package/dist/backend/entities/invoice-external-attachment.entity.d.ts.map +1 -0
- package/dist/backend/entities/invoice-external-attachment.entity.js +77 -0
- package/dist/backend/entities/invoice-external-attachment.entity.js.map +1 -0
- package/dist/backend/entities/invoice-line.entity.d.ts +20 -0
- package/dist/backend/entities/invoice-line.entity.d.ts.map +1 -0
- package/dist/backend/entities/invoice-line.entity.js +82 -0
- package/dist/backend/entities/invoice-line.entity.js.map +1 -0
- package/dist/backend/entities/invoice-number-counter.entity.d.ts +18 -0
- package/dist/backend/entities/invoice-number-counter.entity.d.ts.map +1 -0
- package/dist/backend/entities/invoice-number-counter.entity.js +63 -0
- package/dist/backend/entities/invoice-number-counter.entity.js.map +1 -0
- package/dist/backend/entities/invoice-template.entity.d.ts +22 -0
- package/dist/backend/entities/invoice-template.entity.d.ts.map +1 -0
- package/dist/backend/entities/invoice-template.entity.js +84 -0
- package/dist/backend/entities/invoice-template.entity.js.map +1 -0
- package/dist/backend/entities/invoice.entity.d.ts +64 -0
- package/dist/backend/entities/invoice.entity.d.ts.map +1 -0
- package/dist/backend/entities/invoice.entity.js +202 -0
- package/dist/backend/entities/invoice.entity.js.map +1 -0
- package/dist/backend/index.d.ts +111 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +291 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/pdf-components/descriptor.d.ts +29 -0
- package/dist/backend/pdf-components/descriptor.d.ts.map +1 -0
- package/dist/backend/pdf-components/descriptor.js +227 -0
- package/dist/backend/pdf-components/descriptor.js.map +1 -0
- package/dist/backend/pdf-components/embed-logo-images.d.ts +35 -0
- package/dist/backend/pdf-components/embed-logo-images.d.ts.map +1 -0
- package/dist/backend/pdf-components/embed-logo-images.js +101 -0
- package/dist/backend/pdf-components/embed-logo-images.js.map +1 -0
- package/dist/backend/pdf-components/sample.d.ts +4 -0
- package/dist/backend/pdf-components/sample.d.ts.map +1 -0
- package/dist/backend/pdf-components/sample.js +75 -0
- package/dist/backend/pdf-components/sample.js.map +1 -0
- package/dist/backend/pdf-components/sections.d.ts +34 -0
- package/dist/backend/pdf-components/sections.d.ts.map +1 -0
- package/dist/backend/pdf-components/sections.js +445 -0
- package/dist/backend/pdf-components/sections.js.map +1 -0
- package/dist/backend/pdf-components/tree-mapper.d.ts +37 -0
- package/dist/backend/pdf-components/tree-mapper.d.ts.map +1 -0
- package/dist/backend/pdf-components/tree-mapper.js +76 -0
- package/dist/backend/pdf-components/tree-mapper.js.map +1 -0
- package/dist/backend/plugin.d.ts +124 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +87 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +41 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +219 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.customer.d.ts +33 -0
- package/dist/backend/routes.customer.d.ts.map +1 -0
- package/dist/backend/routes.customer.js +124 -0
- package/dist/backend/routes.customer.js.map +1 -0
- package/dist/backend/seeds/generic-invoice-template.d.ts +18 -0
- package/dist/backend/seeds/generic-invoice-template.d.ts.map +1 -0
- package/dist/backend/seeds/generic-invoice-template.js +174 -0
- package/dist/backend/seeds/generic-invoice-template.js.map +1 -0
- package/dist/backend/services/amount-to-words.d.ts +20 -0
- package/dist/backend/services/amount-to-words.d.ts.map +1 -0
- package/dist/backend/services/amount-to-words.js +232 -0
- package/dist/backend/services/amount-to-words.js.map +1 -0
- package/dist/backend/services/auto-issue-reactor.d.ts +53 -0
- package/dist/backend/services/auto-issue-reactor.d.ts.map +1 -0
- package/dist/backend/services/auto-issue-reactor.js +122 -0
- package/dist/backend/services/auto-issue-reactor.js.map +1 -0
- package/dist/backend/services/corrective-invoice.d.ts +38 -0
- package/dist/backend/services/corrective-invoice.d.ts.map +1 -0
- package/dist/backend/services/corrective-invoice.js +257 -0
- package/dist/backend/services/corrective-invoice.js.map +1 -0
- package/dist/backend/services/cross-module-context.d.ts +82 -0
- package/dist/backend/services/cross-module-context.d.ts.map +1 -0
- package/dist/backend/services/cross-module-context.js +93 -0
- package/dist/backend/services/cross-module-context.js.map +1 -0
- package/dist/backend/services/duplicate-number-refusal.d.ts +33 -0
- package/dist/backend/services/duplicate-number-refusal.d.ts.map +1 -0
- package/dist/backend/services/duplicate-number-refusal.js +92 -0
- package/dist/backend/services/duplicate-number-refusal.js.map +1 -0
- package/dist/backend/services/erp-sale-document-write-port.d.ts +37 -0
- package/dist/backend/services/erp-sale-document-write-port.d.ts.map +1 -0
- package/dist/backend/services/erp-sale-document-write-port.js +192 -0
- package/dist/backend/services/erp-sale-document-write-port.js.map +1 -0
- package/dist/backend/services/invoice-attachment-fetch-registry.d.ts +75 -0
- package/dist/backend/services/invoice-attachment-fetch-registry.d.ts.map +1 -0
- package/dist/backend/services/invoice-attachment-fetch-registry.js +81 -0
- package/dist/backend/services/invoice-attachment-fetch-registry.js.map +1 -0
- package/dist/backend/services/invoice-copy-host.service.d.ts +9 -0
- package/dist/backend/services/invoice-copy-host.service.d.ts.map +1 -0
- package/dist/backend/services/invoice-copy-host.service.js +52 -0
- package/dist/backend/services/invoice-copy-host.service.js.map +1 -0
- package/dist/backend/services/invoice-email-dispatch.d.ts +89 -0
- package/dist/backend/services/invoice-email-dispatch.d.ts.map +1 -0
- package/dist/backend/services/invoice-email-dispatch.js +138 -0
- package/dist/backend/services/invoice-email-dispatch.js.map +1 -0
- package/dist/backend/services/invoice-line-builder.d.ts +29 -0
- package/dist/backend/services/invoice-line-builder.d.ts.map +1 -0
- package/dist/backend/services/invoice-line-builder.js +46 -0
- package/dist/backend/services/invoice-line-builder.js.map +1 -0
- package/dist/backend/services/invoice-number-collisions.d.ts +17 -0
- package/dist/backend/services/invoice-number-collisions.d.ts.map +1 -0
- package/dist/backend/services/invoice-number-collisions.js +154 -0
- package/dist/backend/services/invoice-number-collisions.js.map +1 -0
- package/dist/backend/services/invoice-number-generator.d.ts +96 -0
- package/dist/backend/services/invoice-number-generator.d.ts.map +1 -0
- package/dist/backend/services/invoice-number-generator.js +157 -0
- package/dist/backend/services/invoice-number-generator.js.map +1 -0
- package/dist/backend/services/invoice-pdf-block-registry.d.ts +65 -0
- package/dist/backend/services/invoice-pdf-block-registry.d.ts.map +1 -0
- package/dist/backend/services/invoice-pdf-block-registry.js +94 -0
- package/dist/backend/services/invoice-pdf-block-registry.js.map +1 -0
- package/dist/backend/services/invoice-pdf-renderer.d.ts +57 -0
- package/dist/backend/services/invoice-pdf-renderer.d.ts.map +1 -0
- package/dist/backend/services/invoice-pdf-renderer.js +158 -0
- package/dist/backend/services/invoice-pdf-renderer.js.map +1 -0
- package/dist/backend/services/invoice-pdf.d.ts +24 -0
- package/dist/backend/services/invoice-pdf.d.ts.map +1 -0
- package/dist/backend/services/invoice-pdf.js +52 -0
- package/dist/backend/services/invoice-pdf.js.map +1 -0
- package/dist/backend/services/invoice-placement-apply-port.d.ts +29 -0
- package/dist/backend/services/invoice-placement-apply-port.d.ts.map +1 -0
- package/dist/backend/services/invoice-placement-apply-port.js +40 -0
- package/dist/backend/services/invoice-placement-apply-port.js.map +1 -0
- package/dist/backend/services/invoice-read-port.d.ts +44 -0
- package/dist/backend/services/invoice-read-port.d.ts.map +1 -0
- package/dist/backend/services/invoice-read-port.js +95 -0
- package/dist/backend/services/invoice-read-port.js.map +1 -0
- package/dist/backend/services/invoice-service.d.ts +94 -0
- package/dist/backend/services/invoice-service.d.ts.map +1 -0
- package/dist/backend/services/invoice-service.js +374 -0
- package/dist/backend/services/invoice-service.js.map +1 -0
- package/dist/backend/services/invoice-template-service.d.ts +43 -0
- package/dist/backend/services/invoice-template-service.d.ts.map +1 -0
- package/dist/backend/services/invoice-template-service.js +147 -0
- package/dist/backend/services/invoice-template-service.js.map +1 -0
- package/dist/backend/services/numbering-configuration.d.ts +76 -0
- package/dist/backend/services/numbering-configuration.d.ts.map +1 -0
- package/dist/backend/services/numbering-configuration.js +176 -0
- package/dist/backend/services/numbering-configuration.js.map +1 -0
- package/dist/backend/services/numbering-write-validator.d.ts +22 -0
- package/dist/backend/services/numbering-write-validator.d.ts.map +1 -0
- package/dist/backend/services/numbering-write-validator.js +82 -0
- package/dist/backend/services/numbering-write-validator.js.map +1 -0
- package/dist/backend/services/seller-settings.d.ts +24 -0
- package/dist/backend/services/seller-settings.d.ts.map +1 -0
- package/dist/backend/services/seller-settings.js +38 -0
- package/dist/backend/services/seller-settings.js.map +1 -0
- package/dist/backend/services/vendor-number-hold.d.ts +10 -0
- package/dist/backend/services/vendor-number-hold.d.ts.map +1 -0
- package/dist/backend/services/vendor-number-hold.js +14 -0
- package/dist/backend/services/vendor-number-hold.js.map +1 -0
- package/dist/manifest.d.ts +203 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +390 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260629T125121_invoices_module.d.ts +17 -0
- package/dist/migrations/20260629T125121_invoices_module.d.ts.map +1 -0
- package/dist/migrations/20260629T125121_invoices_module.js +117 -0
- package/dist/migrations/20260629T125121_invoices_module.js.map +1 -0
- package/dist/migrations/20260801T111000_invoices_generic_template_reseed.d.ts +6 -0
- package/dist/migrations/20260801T111000_invoices_generic_template_reseed.d.ts.map +1 -0
- package/dist/migrations/20260801T111000_invoices_generic_template_reseed.js +25 -0
- package/dist/migrations/20260801T111000_invoices_generic_template_reseed.js.map +1 -0
- package/dist/migrations/20260817T201110_invoices_correction_idempotency_key.d.ts +20 -0
- package/dist/migrations/20260817T201110_invoices_correction_idempotency_key.d.ts.map +1 -0
- package/dist/migrations/20260817T201110_invoices_correction_idempotency_key.js +33 -0
- package/dist/migrations/20260817T201110_invoices_correction_idempotency_key.js.map +1 -0
- package/dist/migrations/20260903T101756_invoices_namespace_block_names.d.ts +6 -0
- package/dist/migrations/20260903T101756_invoices_namespace_block_names.d.ts.map +1 -0
- package/dist/migrations/20260903T101756_invoices_namespace_block_names.js +56 -0
- package/dist/migrations/20260903T101756_invoices_namespace_block_names.js.map +1 -0
- package/dist/migrations/20260914T133603_invoices_erp_imported_sale_documents.d.ts +12 -0
- package/dist/migrations/20260914T133603_invoices_erp_imported_sale_documents.d.ts.map +1 -0
- package/dist/migrations/20260914T133603_invoices_erp_imported_sale_documents.js +87 -0
- package/dist/migrations/20260914T133603_invoices_erp_imported_sale_documents.js.map +1 -0
- package/dist/migrations/20260926T230239_invoices_import_identity.d.ts +33 -0
- package/dist/migrations/20260926T230239_invoices_import_identity.d.ts.map +1 -0
- package/dist/migrations/20260926T230239_invoices_import_identity.js +83 -0
- package/dist/migrations/20260926T230239_invoices_import_identity.js.map +1 -0
- package/dist/migrations/index.d.ts +32 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +39 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/ports/index.d.ts +93 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/docs/invoices.md +113 -0
- package/i18n/en.json +45 -0
- package/i18n/pl.json +45 -0
- package/package.json +119 -3
- 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
|