@endora-commerce/mod-invoices 0.0.0-stage → 0.100.1
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,33 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
/**
|
|
3
|
+
* Turn a duplicated invoice number into the operator's account of it —
|
|
4
|
+
* feature 078, D-95.2.
|
|
5
|
+
*
|
|
6
|
+
* This is the layer that carries the **guarantee**. The channel-distinct
|
|
7
|
+
* default stops the ordinary path from arming a duplicate and the settings
|
|
8
|
+
* refusal stops a misconfiguration from being saved, but issuance is the one
|
|
9
|
+
* chokepoint every number passes through, so it is what makes platform-wide
|
|
10
|
+
* uniqueness hold. It refuses only an **actual** collision — a row the database
|
|
11
|
+
* has already rejected — never a possible one: a pre-flight "could this
|
|
12
|
+
* collide" at issuance would block sales that would have succeeded and would
|
|
13
|
+
* cost a settings read per channel on the hot path to do it.
|
|
14
|
+
*
|
|
15
|
+
* The transaction has already rolled back by the time this runs, so the counter
|
|
16
|
+
* draw is undone and a refused issuance leaves no gap in the sequence.
|
|
17
|
+
*
|
|
18
|
+
* Returns normally when `error` is a different failure; the caller re-throws it
|
|
19
|
+
* unchanged. `invoices_order_kind_uq` in particular is a different fact with a
|
|
20
|
+
* different answer and must keep surfacing as it does today.
|
|
21
|
+
*/
|
|
22
|
+
export declare function refuseDuplicateInvoiceNumber(input: {
|
|
23
|
+
readonly error: unknown;
|
|
24
|
+
/**
|
|
25
|
+
* The number drawn inside the transaction. It has to be captured into the
|
|
26
|
+
* enclosing scope by the caller: the callback's result is lost when the
|
|
27
|
+
* transaction aborts, and this refusal exists to name the number.
|
|
28
|
+
*/
|
|
29
|
+
readonly drawnNumber: string | null;
|
|
30
|
+
readonly salesChannelId: string | null;
|
|
31
|
+
readonly emFactory: () => EntityManager;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
//# sourceMappingURL=duplicate-number-refusal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-number-refusal.d.ts","sourceRoot":"","sources":["../../../src/backend/services/duplicate-number-refusal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAc3D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,4BAA4B,CAAC,KAAK,EAAE;IACxD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;CACzC,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDhB"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { UniqueConstraintViolationException } from '@mikro-orm/core';
|
|
2
|
+
import { ERROR_CODES } from '@endora-commerce/contracts';
|
|
3
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
4
|
+
import { SalesChannel } from '@endora-commerce/platform/kernel';
|
|
5
|
+
import { withSystemScope } from '@endora-commerce/platform/tenancy';
|
|
6
|
+
import { Invoice } from '../entities/invoice.entity.js';
|
|
7
|
+
import { channelName } from './numbering-configuration.js';
|
|
8
|
+
/**
|
|
9
|
+
* The unique index that makes an invoice number mean one document
|
|
10
|
+
* platform-wide (`20260425T050720_core_commerce_init.ts`).
|
|
11
|
+
*/
|
|
12
|
+
const NUMBER_UNIQUE_CONSTRAINT = 'invoices_number_unique';
|
|
13
|
+
/**
|
|
14
|
+
* Turn a duplicated invoice number into the operator's account of it —
|
|
15
|
+
* feature 078, D-95.2.
|
|
16
|
+
*
|
|
17
|
+
* This is the layer that carries the **guarantee**. The channel-distinct
|
|
18
|
+
* default stops the ordinary path from arming a duplicate and the settings
|
|
19
|
+
* refusal stops a misconfiguration from being saved, but issuance is the one
|
|
20
|
+
* chokepoint every number passes through, so it is what makes platform-wide
|
|
21
|
+
* uniqueness hold. It refuses only an **actual** collision — a row the database
|
|
22
|
+
* has already rejected — never a possible one: a pre-flight "could this
|
|
23
|
+
* collide" at issuance would block sales that would have succeeded and would
|
|
24
|
+
* cost a settings read per channel on the hot path to do it.
|
|
25
|
+
*
|
|
26
|
+
* The transaction has already rolled back by the time this runs, so the counter
|
|
27
|
+
* draw is undone and a refused issuance leaves no gap in the sequence.
|
|
28
|
+
*
|
|
29
|
+
* Returns normally when `error` is a different failure; the caller re-throws it
|
|
30
|
+
* unchanged. `invoices_order_kind_uq` in particular is a different fact with a
|
|
31
|
+
* different answer and must keep surfacing as it does today.
|
|
32
|
+
*/
|
|
33
|
+
export async function refuseDuplicateInvoiceNumber(input) {
|
|
34
|
+
if (!(input.error instanceof UniqueConstraintViolationException))
|
|
35
|
+
return;
|
|
36
|
+
if (violatedConstraint(input.error) !== NUMBER_UNIQUE_CONSTRAINT)
|
|
37
|
+
return;
|
|
38
|
+
const number = input.drawnNumber;
|
|
39
|
+
if (number === null)
|
|
40
|
+
return;
|
|
41
|
+
// The invoice already holding the number is very often another
|
|
42
|
+
// organization's, and `Invoice` is `@TransitivelyScoped(() => Order,
|
|
43
|
+
// 'orderId')`, so the read crosses organizations deliberately. It exposes
|
|
44
|
+
// **only** the number and the channel — never the buyer, the order or the
|
|
45
|
+
// amount.
|
|
46
|
+
const holder = await withSystemScope('invoices: name the channel already holding a duplicated invoice number', async () => {
|
|
47
|
+
const em = input.emFactory();
|
|
48
|
+
const existing = await em.findOne(Invoice, { number });
|
|
49
|
+
if (!existing)
|
|
50
|
+
return null;
|
|
51
|
+
const channel = existing.salesChannelId
|
|
52
|
+
? await em.findOne(SalesChannel, { id: existing.salesChannelId })
|
|
53
|
+
: null;
|
|
54
|
+
return { salesChannelId: existing.salesChannelId ?? null, channel };
|
|
55
|
+
});
|
|
56
|
+
// The holder was deleted between the violation and this read. Fall back to
|
|
57
|
+
// the tokenless base sentence rather than fabricating a channel.
|
|
58
|
+
if (!holder || !holder.channel) {
|
|
59
|
+
throw new HttpError(409, ERROR_CODES.INVOICE_NUMBER_ALREADY_ISSUED, `Invoice number "${number}" is already in use. Nothing was issued.`, { number });
|
|
60
|
+
}
|
|
61
|
+
const sameChannel = holder.salesChannelId === input.salesChannelId;
|
|
62
|
+
throw new HttpError(409, ERROR_CODES.INVOICE_NUMBER_ALREADY_ISSUED, sameChannel
|
|
63
|
+
? `Invoice number "${number}" has already been issued in this sales channel. ` +
|
|
64
|
+
`Nothing was issued.`
|
|
65
|
+
: `Invoice number "${number}" is already used by sales channel ` +
|
|
66
|
+
`"${channelName(holder.channel)}". Nothing was issued.`, {
|
|
67
|
+
code: sameChannel ? 'same_channel' : 'other_channel',
|
|
68
|
+
number,
|
|
69
|
+
channel: channelName(holder.channel),
|
|
70
|
+
channelCode: holder.channel.code,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The constraint the database named, dug out of the driver error.
|
|
75
|
+
*
|
|
76
|
+
* MikroORM wraps the `pg` error, and the constraint name is what tells
|
|
77
|
+
* `invoices_number_unique` apart from `invoices_order_kind_uq` — two different
|
|
78
|
+
* facts with two different answers, so guessing from the exception class alone
|
|
79
|
+
* would make the wrong one wear this refusal.
|
|
80
|
+
*/
|
|
81
|
+
function violatedConstraint(error) {
|
|
82
|
+
let cursor = error;
|
|
83
|
+
for (let depth = 0; cursor !== null && cursor !== undefined && depth < 5; depth += 1) {
|
|
84
|
+
const named = cursor.constraint;
|
|
85
|
+
if (typeof named === 'string')
|
|
86
|
+
return named;
|
|
87
|
+
const wrapped = cursor;
|
|
88
|
+
cursor = wrapped.previous ?? wrapped.cause;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=duplicate-number-refusal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-number-refusal.js","sourceRoot":"","sources":["../../../src/backend/services/duplicate-number-refusal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import { type ErpSaleDocumentAttachmentContext, type ErpSaleDocumentUpsertInput, type ErpSaleDocumentUpsertResult, type ErpSaleDocumentWritePort } from '@endora-commerce/contracts';
|
|
3
|
+
export declare class ErpSaleDocumentWritePortService implements ErpSaleDocumentWritePort {
|
|
4
|
+
private readonly emFactory;
|
|
5
|
+
constructor(emFactory: () => EntityManager);
|
|
6
|
+
upsertImportedDocument(untrusted: ErpSaleDocumentUpsertInput): Promise<ErpSaleDocumentUpsertResult>;
|
|
7
|
+
resolveAttachmentContext(input: {
|
|
8
|
+
invoiceId: string;
|
|
9
|
+
attachmentId: string;
|
|
10
|
+
organizationId: string;
|
|
11
|
+
}): Promise<ErpSaleDocumentAttachmentContext | null>;
|
|
12
|
+
linkAttachmentAsset(input: {
|
|
13
|
+
invoiceId: string;
|
|
14
|
+
attachmentId: string;
|
|
15
|
+
organizationId: string;
|
|
16
|
+
assetId: string;
|
|
17
|
+
contentType?: string | null;
|
|
18
|
+
}): Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* The imported document with this identity, in any organization.
|
|
21
|
+
*
|
|
22
|
+
* Raw SQL, deliberately outside the tenant global filter: identity belongs to
|
|
23
|
+
* the source document, not to an organization. Within one source system a
|
|
24
|
+
* document the source system reassigns to another contractor moves with it —
|
|
25
|
+
* that is the source system's decision, and `applyHeader` follows it. What the
|
|
26
|
+
* identity does guarantee is that a document from **another** system can never
|
|
27
|
+
* be reached (feature 134, T135; `research.md` D21 §3).
|
|
28
|
+
*
|
|
29
|
+
* The predicate states `invoices_external_document_uq`'s expression and
|
|
30
|
+
* partial-index condition verbatim, so the planner can answer it from that
|
|
31
|
+
* index.
|
|
32
|
+
*/
|
|
33
|
+
private findImportedDocument;
|
|
34
|
+
private applyHeader;
|
|
35
|
+
private syncAttachments;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=erp-sale-document-write-port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"erp-sale-document-write-port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/erp-sale-document-write-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAEL,KAAK,gCAAgC,EAErC,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAE9B,MAAM,4BAA4B,CAAC;AAMpC,qBAAa,+BAAgC,YAAW,wBAAwB;IAClE,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,sBAAsB,CAC1B,SAAS,EAAE,0BAA0B,GACpC,OAAO,CAAC,2BAA2B,CAAC;IAoEjC,wBAAwB,CAAC,KAAK,EAAE;QACpC,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;KACxB,GAAG,OAAO,CAAC,gCAAgC,GAAG,IAAI,CAAC;IA6B9C,mBAAmB,CAAC,KAAK,EAAE;QAC/B,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC7B,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BpB;;;;;;;;;;;;;OAaG;YACW,oBAAoB;IAgBlC,OAAO,CAAC,WAAW;YAuBL,eAAe;CA4B9B"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { ERROR_CODES, erpSaleDocumentUpsertInputSchema, } from '@endora-commerce/contracts';
|
|
2
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
3
|
+
import { isOrgInScope } from '@endora-commerce/platform/tenancy';
|
|
4
|
+
import { InvoiceExternalAttachment } from '../entities/invoice-external-attachment.entity.js';
|
|
5
|
+
import { Invoice } from '../entities/invoice.entity.js';
|
|
6
|
+
export class ErpSaleDocumentWritePortService {
|
|
7
|
+
emFactory;
|
|
8
|
+
constructor(emFactory) {
|
|
9
|
+
this.emFactory = emFactory;
|
|
10
|
+
}
|
|
11
|
+
async upsertImportedDocument(untrusted) {
|
|
12
|
+
// command-coverage-ignore: projection of a sale document the ERP owns.
|
|
13
|
+
// `origin: 'erp_import'` is the whole of this port's subject — the row
|
|
14
|
+
// mirrors a document the source system issued, and the decision to issue
|
|
15
|
+
// it was never taken in Endora, so there is no actor for a Command to name.
|
|
16
|
+
// The run that carried it is recorded by the calling connector in its own
|
|
17
|
+
// sync log. An Endora-issued invoice never reaches this method;
|
|
18
|
+
// `InvoiceService` owns that path and audits it.
|
|
19
|
+
//
|
|
20
|
+
// The input is parsed rather than trusted to its TypeScript type: a writer
|
|
21
|
+
// compiled against another version of the contract (feature 134, T135 —
|
|
22
|
+
// the pre-T135 key shape) is refused here, loudly and with
|
|
23
|
+
// nothing written, instead of by a database constraint.
|
|
24
|
+
const parsed = erpSaleDocumentUpsertInputSchema.safeParse(untrusted);
|
|
25
|
+
if (!parsed.success) {
|
|
26
|
+
throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'Imported sale document failed validation.', parsed.error.issues.map((issue) => ({
|
|
27
|
+
path: issue.path.map(String).join('.'),
|
|
28
|
+
issue: issue.message,
|
|
29
|
+
})));
|
|
30
|
+
}
|
|
31
|
+
const input = parsed.data;
|
|
32
|
+
if (!isOrgInScope(input.organizationId)) {
|
|
33
|
+
throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Organization not found.');
|
|
34
|
+
}
|
|
35
|
+
const em = this.emFactory();
|
|
36
|
+
const existing = await this.findImportedDocument(em, {
|
|
37
|
+
system: input.externalDocumentRef.system,
|
|
38
|
+
externalId: input.externalDocumentRef.externalId,
|
|
39
|
+
});
|
|
40
|
+
if (existing) {
|
|
41
|
+
this.applyHeader(existing, input);
|
|
42
|
+
await this.syncAttachments(em, existing, input.attachments ?? []);
|
|
43
|
+
await em.flush();
|
|
44
|
+
return { invoiceId: existing.id, created: false };
|
|
45
|
+
}
|
|
46
|
+
const invoice = em.create(Invoice, {
|
|
47
|
+
origin: 'erp_import',
|
|
48
|
+
organizationId: input.organizationId,
|
|
49
|
+
orderId: input.orderId ?? null,
|
|
50
|
+
externalDocumentRef: input.externalDocumentRef,
|
|
51
|
+
kind: input.kind,
|
|
52
|
+
number: input.number,
|
|
53
|
+
currency: input.currency,
|
|
54
|
+
issuedAt: new Date(input.issuedAt),
|
|
55
|
+
saleDate: input.saleDate ?? null,
|
|
56
|
+
paymentDueDate: input.paymentDueDate ?? null,
|
|
57
|
+
paymentMethod: input.paymentMethod ?? null,
|
|
58
|
+
netTotal: input.netTotal ?? null,
|
|
59
|
+
taxTotal: input.taxTotal ?? null,
|
|
60
|
+
total: input.grossTotal,
|
|
61
|
+
paidTotal: input.paidTotal ?? '0',
|
|
62
|
+
buyerSnapshot: input.buyerSnapshot ?? null,
|
|
63
|
+
status: 'ready',
|
|
64
|
+
});
|
|
65
|
+
await em.persistAndFlush(invoice);
|
|
66
|
+
await this.syncAttachments(em, invoice, input.attachments ?? []);
|
|
67
|
+
await em.flush();
|
|
68
|
+
return { invoiceId: invoice.id, created: true };
|
|
69
|
+
}
|
|
70
|
+
async resolveAttachmentContext(input) {
|
|
71
|
+
const em = this.emFactory();
|
|
72
|
+
const invoice = await em.findOne(Invoice, {
|
|
73
|
+
id: input.invoiceId,
|
|
74
|
+
origin: 'erp_import',
|
|
75
|
+
organizationId: input.organizationId,
|
|
76
|
+
status: 'ready',
|
|
77
|
+
});
|
|
78
|
+
if (!invoice?.externalDocumentRef?.externalId) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const attachment = await em.findOne(InvoiceExternalAttachment, {
|
|
82
|
+
id: input.attachmentId,
|
|
83
|
+
invoiceId: invoice.id,
|
|
84
|
+
});
|
|
85
|
+
if (!attachment) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
system: invoice.externalDocumentRef.system,
|
|
90
|
+
externalId: invoice.externalDocumentRef.externalId,
|
|
91
|
+
externalAttachmentId: attachment.externalAttachmentId,
|
|
92
|
+
fileName: attachment.fileName,
|
|
93
|
+
contentType: attachment.contentType ?? null,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async linkAttachmentAsset(input) {
|
|
97
|
+
// command-coverage-ignore: idempotent stamp of the asset id for an
|
|
98
|
+
// attachment this port already imported. It records where the file was
|
|
99
|
+
// stored, not that it should exist — the import above decided that.
|
|
100
|
+
const em = this.emFactory();
|
|
101
|
+
const invoice = await em.findOne(Invoice, {
|
|
102
|
+
id: input.invoiceId,
|
|
103
|
+
origin: 'erp_import',
|
|
104
|
+
organizationId: input.organizationId,
|
|
105
|
+
status: 'ready',
|
|
106
|
+
});
|
|
107
|
+
if (!invoice)
|
|
108
|
+
return false;
|
|
109
|
+
const attachment = await em.findOne(InvoiceExternalAttachment, {
|
|
110
|
+
id: input.attachmentId,
|
|
111
|
+
invoiceId: invoice.id,
|
|
112
|
+
});
|
|
113
|
+
if (!attachment)
|
|
114
|
+
return false;
|
|
115
|
+
attachment.assetId = input.assetId;
|
|
116
|
+
if (input.contentType !== undefined) {
|
|
117
|
+
attachment.contentType = input.contentType;
|
|
118
|
+
}
|
|
119
|
+
attachment.downloadedAt = new Date();
|
|
120
|
+
await em.flush();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The imported document with this identity, in any organization.
|
|
125
|
+
*
|
|
126
|
+
* Raw SQL, deliberately outside the tenant global filter: identity belongs to
|
|
127
|
+
* the source document, not to an organization. Within one source system a
|
|
128
|
+
* document the source system reassigns to another contractor moves with it —
|
|
129
|
+
* that is the source system's decision, and `applyHeader` follows it. What the
|
|
130
|
+
* identity does guarantee is that a document from **another** system can never
|
|
131
|
+
* be reached (feature 134, T135; `research.md` D21 §3).
|
|
132
|
+
*
|
|
133
|
+
* The predicate states `invoices_external_document_uq`'s expression and
|
|
134
|
+
* partial-index condition verbatim, so the planner can answer it from that
|
|
135
|
+
* index.
|
|
136
|
+
*/
|
|
137
|
+
async findImportedDocument(em, identity) {
|
|
138
|
+
const rows = await em.getConnection().execute(`select id from invoices
|
|
139
|
+
where origin = 'erp_import'
|
|
140
|
+
and (external_document_ref->>'system') = ?
|
|
141
|
+
and (external_document_ref->>'externalId') = ?
|
|
142
|
+
limit 1`, [identity.system, identity.externalId]);
|
|
143
|
+
const id = rows[0]?.id;
|
|
144
|
+
return id ? em.findOne(Invoice, { id }) : null;
|
|
145
|
+
}
|
|
146
|
+
applyHeader(invoice, input) {
|
|
147
|
+
invoice.organizationId = input.organizationId;
|
|
148
|
+
invoice.orderId = input.orderId ?? null;
|
|
149
|
+
invoice.externalDocumentRef = input.externalDocumentRef;
|
|
150
|
+
invoice.kind = input.kind;
|
|
151
|
+
invoice.number = input.number;
|
|
152
|
+
invoice.currency = input.currency;
|
|
153
|
+
invoice.issuedAt = new Date(input.issuedAt);
|
|
154
|
+
invoice.saleDate = input.saleDate ?? null;
|
|
155
|
+
invoice.paymentDueDate = input.paymentDueDate ?? null;
|
|
156
|
+
invoice.paymentMethod = input.paymentMethod ?? null;
|
|
157
|
+
invoice.netTotal = input.netTotal ?? null;
|
|
158
|
+
invoice.taxTotal = input.taxTotal ?? null;
|
|
159
|
+
invoice.total = input.grossTotal;
|
|
160
|
+
if (input.paidTotal !== undefined) {
|
|
161
|
+
invoice.paidTotal = input.paidTotal;
|
|
162
|
+
}
|
|
163
|
+
if (input.buyerSnapshot !== undefined) {
|
|
164
|
+
invoice.buyerSnapshot = input.buyerSnapshot;
|
|
165
|
+
}
|
|
166
|
+
invoice.status = 'ready';
|
|
167
|
+
}
|
|
168
|
+
async syncAttachments(em, invoice, attachments) {
|
|
169
|
+
// command-coverage-ignore: the attachment half of `upsertImportedDocument`,
|
|
170
|
+
// which carries the decision and the exemption's reason. Split out for
|
|
171
|
+
// readability only; it is never called from anywhere else.
|
|
172
|
+
if (attachments.length === 0)
|
|
173
|
+
return;
|
|
174
|
+
const existing = await em.find(InvoiceExternalAttachment, { invoiceId: invoice.id });
|
|
175
|
+
const byExternalId = new Map(existing.map((row) => [row.externalAttachmentId, row]));
|
|
176
|
+
for (const attachment of attachments) {
|
|
177
|
+
const row = byExternalId.get(attachment.externalAttachmentId);
|
|
178
|
+
if (row) {
|
|
179
|
+
row.fileName = attachment.fileName;
|
|
180
|
+
row.contentType = attachment.contentType ?? null;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
em.create(InvoiceExternalAttachment, {
|
|
184
|
+
invoiceId: invoice.id,
|
|
185
|
+
externalAttachmentId: attachment.externalAttachmentId,
|
|
186
|
+
fileName: attachment.fileName,
|
|
187
|
+
contentType: attachment.contentType ?? null,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=erp-sale-document-write-port.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"erp-sale-document-write-port.js","sourceRoot":"","sources":["../../../src/backend/services/erp-sale-document-write-port.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { InvoiceAttachmentFetchPort, InvoiceAttachmentFetchRegistration, InvoiceAttachmentFetchRegistryPort } from '@endora-commerce/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* The invoice-owned check that the attachment belongs to the calling
|
|
4
|
+
* organization, answering the imported document's source system, or `null`.
|
|
5
|
+
* `ErpSaleDocumentWritePortService.resolveAttachmentContext` in production.
|
|
6
|
+
*/
|
|
7
|
+
export type InvoiceAttachmentFetchContextResolver = (input: {
|
|
8
|
+
invoiceId: string;
|
|
9
|
+
attachmentId: string;
|
|
10
|
+
organizationId: string;
|
|
11
|
+
}) => Promise<{
|
|
12
|
+
system: string;
|
|
13
|
+
} | null>;
|
|
14
|
+
/** Two different providers for one source system. */
|
|
15
|
+
export declare class InvoiceAttachmentFetchConflictError extends Error {
|
|
16
|
+
constructor(system: string, existingModuleId: string, moduleId: string);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The first-download seam for ERP-imported attachments (feature 134, T061;
|
|
20
|
+
* `specs/134-paid-module-extraction/research.md` D12).
|
|
21
|
+
*
|
|
22
|
+
* `invoices` owns the customer route, the ownership check and what a first
|
|
23
|
+
* download does. What it does not own is the remote system the bytes live in,
|
|
24
|
+
* so a connector contributes a provider for the source system it imports from.
|
|
25
|
+
* Before this seam the route resolved one connector's port by name and this
|
|
26
|
+
* module's manifest carried an edge onto that connector.
|
|
27
|
+
*
|
|
28
|
+
* **A contribution registry in D-39's sense.** It is an ungated registration,
|
|
29
|
+
* because a connector pushes into it from a boot hook whatever this module's
|
|
30
|
+
* effective state is; the question of presence is answered here, on every
|
|
31
|
+
* download, keyed on the module recorded with each provider.
|
|
32
|
+
*
|
|
33
|
+
* **The policy it states: skip an absent contributor's provider.** A connector
|
|
34
|
+
* that is switched off must not reach its ERP with the operator's credentials,
|
|
35
|
+
* so its provider answers as if it were not registered: `null`, which the route
|
|
36
|
+
* turns into the 404 it already gave an attachment that could not be fetched.
|
|
37
|
+
* Nothing is lost with the skip — a file fetched earlier is stored in
|
|
38
|
+
* `assets_library` and the route serves it without asking any provider — and
|
|
39
|
+
* switching the connector back on takes effect on the next download with no
|
|
40
|
+
* restart, because nothing here is captured at composition.
|
|
41
|
+
*
|
|
42
|
+
* Three more rules, each stated because the alternative is plausible:
|
|
43
|
+
*
|
|
44
|
+
* 1. **Ownership is checked before any provider runs.** The context resolver
|
|
45
|
+
* is this module's own query, scoped to the calling organization, so a
|
|
46
|
+
* connector is never asked to fetch a foreign organization's file.
|
|
47
|
+
* 2. **There is no fallback.** A document is answered by the provider of its
|
|
48
|
+
* own source system or by nobody; another connector's provider is never
|
|
49
|
+
* tried, whatever is present.
|
|
50
|
+
* 3. **A second provider for one system is refused**, not ordered. Which
|
|
51
|
+
* provider serves a system must not depend on which module composed last.
|
|
52
|
+
* Registering the identical provider twice is accepted.
|
|
53
|
+
*/
|
|
54
|
+
export declare class InvoiceAttachmentFetchRegistry implements InvoiceAttachmentFetchRegistryPort, InvoiceAttachmentFetchPort {
|
|
55
|
+
private readonly resolveContext;
|
|
56
|
+
private readonly isModulePresent;
|
|
57
|
+
private readonly registrations;
|
|
58
|
+
/**
|
|
59
|
+
* @param resolveContext the invoice-owned ownership check.
|
|
60
|
+
* @param isModulePresent the effective-state probe. Defaults to
|
|
61
|
+
* always-present, so a registry a unit test builds keeps answering about
|
|
62
|
+
* the providers that test registered; the module's composition wires it to
|
|
63
|
+
* the kernel's effective state.
|
|
64
|
+
*/
|
|
65
|
+
constructor(resolveContext: InvoiceAttachmentFetchContextResolver, isModulePresent?: (moduleId: string) => boolean);
|
|
66
|
+
register(registration: InvoiceAttachmentFetchRegistration): void;
|
|
67
|
+
ensureAttachmentBytes(input: {
|
|
68
|
+
invoiceId: string;
|
|
69
|
+
attachmentId: string;
|
|
70
|
+
organizationId: string;
|
|
71
|
+
}): Promise<{
|
|
72
|
+
assetId: string;
|
|
73
|
+
} | null>;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=invoice-attachment-fetch-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoice-attachment-fetch-registry.d.ts","sourceRoot":"","sources":["../../../src/backend/services/invoice-attachment-fetch-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,kCAAkC,EAClC,kCAAkC,EACnC,MAAM,4BAA4B,CAAC;AAEpC;;;;GAIG;AACH,MAAM,MAAM,qCAAqC,GAAG,CAAC,KAAK,EAAE;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAAC;AAEzC,qDAAqD;AACrD,qBAAa,mCAAoC,SAAQ,KAAK;gBAChD,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAOvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,qBAAa,8BACX,YAAW,kCAAkC,EAAE,0BAA0B;IAYvE,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAXlC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyD;IAEvF;;;;;;OAMG;gBAEgB,cAAc,EAAE,qCAAqC,EACrD,eAAe,GAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAoB;IAG9E,QAAQ,CAAC,YAAY,EAAE,kCAAkC,GAAG,IAAI;IAkB1D,qBAAqB,CAAC,KAAK,EAAE;QACjC,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;KACxB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CASxC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Two different providers for one source system. */
|
|
2
|
+
export class InvoiceAttachmentFetchConflictError extends Error {
|
|
3
|
+
constructor(system, existingModuleId, moduleId) {
|
|
4
|
+
super(`invoiceAttachmentFetchRegistry: source system "${system}" already has a fetch provider ` +
|
|
5
|
+
`from module "${existingModuleId}"; module "${moduleId}" cannot register a second one.`);
|
|
6
|
+
this.name = 'InvoiceAttachmentFetchConflictError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The first-download seam for ERP-imported attachments (feature 134, T061;
|
|
11
|
+
* `specs/134-paid-module-extraction/research.md` D12).
|
|
12
|
+
*
|
|
13
|
+
* `invoices` owns the customer route, the ownership check and what a first
|
|
14
|
+
* download does. What it does not own is the remote system the bytes live in,
|
|
15
|
+
* so a connector contributes a provider for the source system it imports from.
|
|
16
|
+
* Before this seam the route resolved one connector's port by name and this
|
|
17
|
+
* module's manifest carried an edge onto that connector.
|
|
18
|
+
*
|
|
19
|
+
* **A contribution registry in D-39's sense.** It is an ungated registration,
|
|
20
|
+
* because a connector pushes into it from a boot hook whatever this module's
|
|
21
|
+
* effective state is; the question of presence is answered here, on every
|
|
22
|
+
* download, keyed on the module recorded with each provider.
|
|
23
|
+
*
|
|
24
|
+
* **The policy it states: skip an absent contributor's provider.** A connector
|
|
25
|
+
* that is switched off must not reach its ERP with the operator's credentials,
|
|
26
|
+
* so its provider answers as if it were not registered: `null`, which the route
|
|
27
|
+
* turns into the 404 it already gave an attachment that could not be fetched.
|
|
28
|
+
* Nothing is lost with the skip — a file fetched earlier is stored in
|
|
29
|
+
* `assets_library` and the route serves it without asking any provider — and
|
|
30
|
+
* switching the connector back on takes effect on the next download with no
|
|
31
|
+
* restart, because nothing here is captured at composition.
|
|
32
|
+
*
|
|
33
|
+
* Three more rules, each stated because the alternative is plausible:
|
|
34
|
+
*
|
|
35
|
+
* 1. **Ownership is checked before any provider runs.** The context resolver
|
|
36
|
+
* is this module's own query, scoped to the calling organization, so a
|
|
37
|
+
* connector is never asked to fetch a foreign organization's file.
|
|
38
|
+
* 2. **There is no fallback.** A document is answered by the provider of its
|
|
39
|
+
* own source system or by nobody; another connector's provider is never
|
|
40
|
+
* tried, whatever is present.
|
|
41
|
+
* 3. **A second provider for one system is refused**, not ordered. Which
|
|
42
|
+
* provider serves a system must not depend on which module composed last.
|
|
43
|
+
* Registering the identical provider twice is accepted.
|
|
44
|
+
*/
|
|
45
|
+
export class InvoiceAttachmentFetchRegistry {
|
|
46
|
+
resolveContext;
|
|
47
|
+
isModulePresent;
|
|
48
|
+
registrations = new Map();
|
|
49
|
+
/**
|
|
50
|
+
* @param resolveContext the invoice-owned ownership check.
|
|
51
|
+
* @param isModulePresent the effective-state probe. Defaults to
|
|
52
|
+
* always-present, so a registry a unit test builds keeps answering about
|
|
53
|
+
* the providers that test registered; the module's composition wires it to
|
|
54
|
+
* the kernel's effective state.
|
|
55
|
+
*/
|
|
56
|
+
constructor(resolveContext, isModulePresent = () => true) {
|
|
57
|
+
this.resolveContext = resolveContext;
|
|
58
|
+
this.isModulePresent = isModulePresent;
|
|
59
|
+
}
|
|
60
|
+
register(registration) {
|
|
61
|
+
const existing = this.registrations.get(registration.system);
|
|
62
|
+
if (existing) {
|
|
63
|
+
if (existing.moduleId === registration.moduleId &&
|
|
64
|
+
existing.provider === registration.provider) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
throw new InvoiceAttachmentFetchConflictError(registration.system, existing.moduleId, registration.moduleId);
|
|
68
|
+
}
|
|
69
|
+
this.registrations.set(registration.system, registration);
|
|
70
|
+
}
|
|
71
|
+
async ensureAttachmentBytes(input) {
|
|
72
|
+
const context = await this.resolveContext(input);
|
|
73
|
+
if (!context)
|
|
74
|
+
return null;
|
|
75
|
+
const registration = this.registrations.get(context.system);
|
|
76
|
+
if (!registration || !this.isModulePresent(registration.moduleId))
|
|
77
|
+
return null;
|
|
78
|
+
return registration.provider.ensureAttachmentBytes(input);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=invoice-attachment-fetch-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoice-attachment-fetch-registry.js","sourceRoot":"","sources":["../../../src/backend/services/invoice-attachment-fetch-registry.ts"],"names":[],"mappings":"AAiBA,qDAAqD;AACrD,MAAM,OAAO,mCAAoC,SAAQ,KAAK;IAC5D,YAAY,MAAc,EAAE,gBAAwB,EAAE,QAAgB;QACpE,KAAK,CACH,kDAAkD,MAAM,iCAAiC;YACvF,gBAAgB,gBAAgB,cAAc,QAAQ,iCAAiC,CAC1F,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qCAAqC,CAAC;IACpD,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,OAAO,8BAA8B;IAatB;IACA;IAXF,aAAa,GAAG,IAAI,GAAG,EAA8C,CAAC;IAEvF;;;;;;OAMG;IACH,YACmB,cAAqD,EACrD,kBAAiD,GAAG,EAAE,CAAC,IAAI;QAD3D,mBAAc,GAAd,cAAc,CAAuC;QACrD,oBAAe,GAAf,eAAe,CAA4C;IAC3E,CAAC;IAEJ,QAAQ,CAAC,YAAgD;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAC7D,IAAI,QAAQ,EAAE,CAAC;YACb,IACE,QAAQ,CAAC,QAAQ,KAAK,YAAY,CAAC,QAAQ;gBAC3C,QAAQ,CAAC,QAAQ,KAAK,YAAY,CAAC,QAAQ,EAC3C,CAAC;gBACD,OAAO;YACT,CAAC;YACD,MAAM,IAAI,mCAAmC,CAC3C,YAAY,CAAC,MAAM,EACnB,QAAQ,CAAC,QAAQ,EACjB,YAAY,CAAC,QAAQ,CACtB,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,qBAAqB,CAAC,KAI3B;QACC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QACjD,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAE1B,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC5D,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAE/E,OAAO,YAAY,CAAC,QAAQ,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAC5D,CAAC;CACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
2
|
+
import type { InvoiceCopyHostPort, InvoiceCopyRecord, OrderReadPort } from '@endora-commerce/contracts';
|
|
3
|
+
export declare class InvoiceCopyHostService implements InvoiceCopyHostPort {
|
|
4
|
+
private readonly emFactory;
|
|
5
|
+
private readonly orders;
|
|
6
|
+
constructor(emFactory: () => EntityManager, orders: OrderReadPort);
|
|
7
|
+
getById(invoiceId: string): Promise<InvoiceCopyRecord | null>;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=invoice-copy-host.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoice-copy-host.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/invoice-copy-host.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAEV,mBAAmB,EACnB,iBAAiB,EAEjB,aAAa,EACd,MAAM,4BAA4B,CAAC;AAcpC,qBAAa,sBAAuB,YAAW,mBAAmB;IAE9D,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,SAAS,EAAE,MAAM,aAAa,EAC9B,MAAM,EAAE,aAAa;IAGlC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;CAmCpE"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Invoice } from '../entities/invoice.entity.js';
|
|
2
|
+
import { InvoiceLine as InvoiceLineEntity } from '../entities/invoice-line.entity.js';
|
|
3
|
+
const EMPTY_BUYER = {
|
|
4
|
+
name: '',
|
|
5
|
+
taxId: '',
|
|
6
|
+
addressLine1: '',
|
|
7
|
+
addressLine2: '',
|
|
8
|
+
postalCode: '',
|
|
9
|
+
city: '',
|
|
10
|
+
country: '',
|
|
11
|
+
};
|
|
12
|
+
export class InvoiceCopyHostService {
|
|
13
|
+
emFactory;
|
|
14
|
+
orders;
|
|
15
|
+
constructor(emFactory, orders) {
|
|
16
|
+
this.emFactory = emFactory;
|
|
17
|
+
this.orders = orders;
|
|
18
|
+
}
|
|
19
|
+
async getById(invoiceId) {
|
|
20
|
+
const em = this.emFactory();
|
|
21
|
+
const invoice = await em.findOne(Invoice, { id: invoiceId });
|
|
22
|
+
if (!invoice)
|
|
23
|
+
return null;
|
|
24
|
+
const order = invoice.orderId ? await this.orders.findById(invoice.orderId) : null;
|
|
25
|
+
const lineRows = await em.find(InvoiceLineEntity, { invoiceId }, { orderBy: { ordinal: 'asc' } });
|
|
26
|
+
const lines = lineRows.map((line) => ({
|
|
27
|
+
ordinal: line.ordinal,
|
|
28
|
+
name: line.name,
|
|
29
|
+
unit: line.unit,
|
|
30
|
+
quantity: Number(line.quantity),
|
|
31
|
+
unitNetPrice: Number(line.unitNetPrice),
|
|
32
|
+
taxRate: Number(line.taxRate),
|
|
33
|
+
netValue: Number(line.netValue),
|
|
34
|
+
grossValue: Number(line.grossValue),
|
|
35
|
+
}));
|
|
36
|
+
return {
|
|
37
|
+
invoiceId: invoice.id,
|
|
38
|
+
organizationId: order?.organizationId ?? invoice.organizationId ?? '',
|
|
39
|
+
number: invoice.number,
|
|
40
|
+
kind: invoice.kind,
|
|
41
|
+
salesChannelId: invoice.salesChannelId ?? null,
|
|
42
|
+
currency: invoice.currency,
|
|
43
|
+
saleDate: invoice.saleDate ?? null,
|
|
44
|
+
paymentDueDate: invoice.paymentDueDate ?? null,
|
|
45
|
+
paymentMethod: invoice.paymentMethod ?? null,
|
|
46
|
+
originalInvoiceId: invoice.originalInvoiceId ?? null,
|
|
47
|
+
buyer: invoice.buyerSnapshot ?? EMPTY_BUYER,
|
|
48
|
+
lines,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=invoice-copy-host.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoice-copy-host.service.js","sourceRoot":"","sources":["../../../src/backend/services/invoice-copy-host.service.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACxD,OAAO,EAAE,WAAW,IAAI,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAEtF,MAAM,WAAW,GAAiB;IAChC,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;IACT,YAAY,EAAE,EAAE;IAChB,YAAY,EAAE,EAAE;IAChB,UAAU,EAAE,EAAE;IACd,IAAI,EAAE,EAAE;IACR,OAAO,EAAE,EAAE;CACZ,CAAC;AAEF,MAAM,OAAO,sBAAsB;IAEd;IACA;IAFnB,YACmB,SAA8B,EAC9B,MAAqB;QADrB,cAAS,GAAT,SAAS,CAAqB;QAC9B,WAAM,GAAN,MAAM,CAAe;IACrC,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,SAAiB;QAC7B,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACnF,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,IAAI,CAC5B,iBAAiB,EACjB,EAAE,SAAS,EAAE,EACb,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAChC,CAAC;QACF,MAAM,KAAK,GAAkB,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACnD,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;YAC/B,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC;YACvC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;YAC7B,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;YAC/B,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;SACpC,CAAC,CAAC,CAAC;QACJ,OAAO;YACL,SAAS,EAAE,OAAO,CAAC,EAAE;YACrB,cAAc,EAAE,KAAK,EAAE,cAAc,IAAI,OAAO,CAAC,cAAc,IAAI,EAAE;YACrE,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,IAAI;YAC9C,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI;YAClC,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,IAAI;YAC9C,aAAa,EAAE,OAAO,CAAC,aAAa,IAAI,IAAI;YAC5C,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,IAAI,IAAI;YACpD,KAAK,EAAE,OAAO,CAAC,aAAa,IAAI,WAAW;YAC3C,KAAK;SACN,CAAC;IACJ,CAAC;CACF"}
|