@ekwo-ai/core 0.11.1 → 0.12.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/README.md +5 -0
- package/dist/books/document-pdf.d.ts +87 -0
- package/dist/books/document-pdf.d.ts.map +1 -0
- package/dist/books/document-pdf.js +222 -0
- package/dist/books/document-pdf.js.map +1 -0
- package/dist/books/index.d.ts +1 -0
- package/dist/books/index.d.ts.map +1 -1
- package/dist/books/index.js +1 -0
- package/dist/books/index.js.map +1 -1
- package/dist/einvoicing/directory.d.ts +38 -0
- package/dist/einvoicing/directory.d.ts.map +1 -0
- package/dist/einvoicing/directory.js +121 -0
- package/dist/einvoicing/directory.js.map +1 -0
- package/dist/einvoicing/formats.d.ts +65 -0
- package/dist/einvoicing/formats.d.ts.map +1 -0
- package/dist/einvoicing/formats.js +380 -0
- package/dist/einvoicing/formats.js.map +1 -0
- package/dist/einvoicing/index.d.ts +11 -0
- package/dist/einvoicing/index.d.ts.map +1 -0
- package/dist/einvoicing/index.js +11 -0
- package/dist/einvoicing/index.js.map +1 -0
- package/dist/einvoicing/issue.d.ts +98 -0
- package/dist/einvoicing/issue.d.ts.map +1 -0
- package/dist/einvoicing/issue.js +398 -0
- package/dist/einvoicing/issue.js.map +1 -0
- package/dist/einvoicing/transport.d.ts +130 -0
- package/dist/einvoicing/transport.d.ts.map +1 -0
- package/dist/einvoicing/transport.js +60 -0
- package/dist/einvoicing/transport.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/proofs/filing-proofs.d.ts +93 -0
- package/dist/proofs/filing-proofs.d.ts.map +1 -0
- package/dist/proofs/filing-proofs.js +161 -0
- package/dist/proofs/filing-proofs.js.map +1 -0
- package/dist/proofs/index.d.ts +7 -0
- package/dist/proofs/index.d.ts.map +1 -0
- package/dist/proofs/index.js +7 -0
- package/dist/proofs/index.js.map +1 -0
- package/dist/proofs/opentimestamps.d.ts +185 -0
- package/dist/proofs/opentimestamps.d.ts.map +1 -0
- package/dist/proofs/opentimestamps.js +508 -0
- package/dist/proofs/opentimestamps.js.map +1 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +1 -1
- package/package.json +14 -11
package/README.md
CHANGED
|
@@ -12,6 +12,9 @@ TypeScript row types and a typed client for the
|
|
|
12
12
|
- **A thin client** over the functions that carry the accounting rules —
|
|
13
13
|
`post_document`, `reconcile`, `trial_balance`, `vat_return`, `fec_lines`.
|
|
14
14
|
Everything else is a table, and Supabase already exposes those as REST.
|
|
15
|
+
- **The functions the command line and the MCP server share** — keeping the
|
|
16
|
+
books, issuing an electronic invoice, proving a filing by its hash — so a
|
|
17
|
+
rule exists once.
|
|
15
18
|
- **No runtime dependency of its own.** `@supabase/supabase-js` is an optional
|
|
16
19
|
peer; `@ekwo-ai/fec` is there because `generateFec()` on the client writes
|
|
17
20
|
the file it has just fetched.
|
|
@@ -64,6 +67,8 @@ const file = generateFec(lines, { decimalSeparator: ',', fieldSeparator: '|' });
|
|
|
64
67
|
| `isRegistered(instance)` | Whether the operator opted into registering with Ekwo. |
|
|
65
68
|
| `isSale`, `isCreditNote`, `isAccountable` | Document type predicates. |
|
|
66
69
|
| `createContact`, `createDocument`, `addDocumentLine`, `updateDocumentLines`, `postDocument`, `recordPayment`, `reconcile`, `settleFromStatement`, `listDocuments`, `getDocument`, … | Keeping the books, once for every surface: the functions the MCP server and the `ekwo` command line both call. Each takes a `Backend` — five operations, `select` `insert` `update` `remove` `rpc` — that the caller implements over whatever reaches the database as a user. No accounting rule is in them; the schema holds those. |
|
|
70
|
+
| `validateEinvoice`, `issueEinvoice`, `einvoiceStatus`, `listEinvoiceTransmissions`, `EinvoiceTransport`, `directoryTransport` | The electronic invoice of a posted sale, through the `einvoicing` module, and the contract a transport implements — `send`, `status`, `receive`, `lookup`, with no credential crossing it. The one transport that ships writes files to a folder. |
|
|
71
|
+
| `proveFile`, `upgradeFilingProofs`, `lookupFilingProof`, `checkProof` | The sha256 of a filed file committed to Bitcoin through the public OpenTimestamps calendars, and checked later by anybody holding the file ([`docs/filing-proofs.md`](../../docs/filing-proofs.md)). |
|
|
67
72
|
| `BooksError`, `isRefusalState`, `socleCode`, `isServiceRoleKey` | What an error of that layer looks like, what counts as the database refusing, and the one key no surface accepts. |
|
|
68
73
|
| Types | `Instance`, `InstanceAdmin`, `MemberRole`, `CompanyRole`, `Account`, `Journal`, `Entry`, `EntryLine`, `EkwoDocument`, `DocumentLine`, `Tax`, `TaxPosting`, `Contact`, `TrialBalanceRow`, `AgedBalanceRow`, `VatReturnRow`, … |
|
|
69
74
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The PDF of a sale invoice or credit note, for every surface at once.
|
|
3
|
+
*
|
|
4
|
+
* The command line (`ekwo doc pdf`) and the MCP server (`render_invoice_pdf`)
|
|
5
|
+
* call this with their own `Backend`, acting as the person signed in. It reads
|
|
6
|
+
* the four views a document is printed from — `document_header`,
|
|
7
|
+
* `document_line_items`, `document_tax_summary`, `document_legal_mentions` —
|
|
8
|
+
* and hands the rows to `@ekwo-ai/invoice-pdf`, which renders them and knows
|
|
9
|
+
* nothing of a database. Here is what only a client can do: read the views,
|
|
10
|
+
* fetch the logo the company names by URL, and, when asked, write the CII of
|
|
11
|
+
* the same document with the Factur-X adapter of the e-invoicing formats and
|
|
12
|
+
* embed it, so that the PDF is a Factur-X invoice.
|
|
13
|
+
*
|
|
14
|
+
* Nothing is recorded. A posted document reproduces from the books; the copy
|
|
15
|
+
* that was sent to a customer is another thing, and keeping it is not done
|
|
16
|
+
* here.
|
|
17
|
+
*/
|
|
18
|
+
import { type InvoiceLabels, type InvoicePdfInput, type PageSize } from '@ekwo-ai/invoice-pdf';
|
|
19
|
+
import { type EinvoiceViolation } from '../einvoicing/formats.js';
|
|
20
|
+
import { type Backend } from './backend.js';
|
|
21
|
+
/** The four views of one document. */
|
|
22
|
+
export declare function readDocumentForPdf(backend: Backend, documentId: string): Promise<Omit<InvoicePdfInput, 'logo'>>;
|
|
23
|
+
/** What a logo fetch may be handed: the global `fetch`, or a test's. */
|
|
24
|
+
export type LogoFetch = (url: string, init: {
|
|
25
|
+
signal: AbortSignal;
|
|
26
|
+
redirect: 'follow' | 'error';
|
|
27
|
+
}) => Promise<{
|
|
28
|
+
ok: boolean;
|
|
29
|
+
status: number;
|
|
30
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Why a logo URL is not fetched, or null when it may be. Only `http` and
|
|
34
|
+
* `https`, and never a host that names this machine or a private network: the
|
|
35
|
+
* URL is a value a member of the company typed, and the server that fetches it
|
|
36
|
+
* may be somebody else's.
|
|
37
|
+
*/
|
|
38
|
+
export declare function logoUrlRefusal(url: string): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* The bytes of the logo at `url`, or why there are none. A logo that cannot be
|
|
41
|
+
* had is not a reason to refuse an invoice: the PDF is rendered with the
|
|
42
|
+
* company's name instead, and the reason is said.
|
|
43
|
+
*/
|
|
44
|
+
export declare function fetchLogo(url: string, fetchImpl: LogoFetch): Promise<{
|
|
45
|
+
bytes: Uint8Array;
|
|
46
|
+
} | {
|
|
47
|
+
skipped: string;
|
|
48
|
+
}>;
|
|
49
|
+
export interface DocumentPdfArgs {
|
|
50
|
+
document_id: string;
|
|
51
|
+
/** Embed the CII of the document (Factur-X, EN 16931 profile): the PDF becomes a PDF/A-3 Factur-X invoice. */
|
|
52
|
+
factur_x?: boolean | undefined;
|
|
53
|
+
page_size?: PageSize | undefined;
|
|
54
|
+
/** The words of the layout in the document's language; what is left out is English. */
|
|
55
|
+
labels?: Partial<InvoiceLabels> | undefined;
|
|
56
|
+
}
|
|
57
|
+
export interface DocumentPdf {
|
|
58
|
+
document_id: string;
|
|
59
|
+
doc_type: string;
|
|
60
|
+
number: string | null;
|
|
61
|
+
state: string | null;
|
|
62
|
+
language: string | null;
|
|
63
|
+
filename: string;
|
|
64
|
+
title: string;
|
|
65
|
+
media_type: 'application/pdf';
|
|
66
|
+
byte_size: number;
|
|
67
|
+
page_count: number;
|
|
68
|
+
/** `embedded`, `none` (the company names no logo), or why the one it names was not used. */
|
|
69
|
+
logo: string;
|
|
70
|
+
/** Null without --factur-x; otherwise the rules of EN 16931 the embedded XML breaks, as the e-invoicing adapter names them. */
|
|
71
|
+
factur_x: {
|
|
72
|
+
profile: 'en16931';
|
|
73
|
+
violations: EinvoiceViolation[];
|
|
74
|
+
} | null;
|
|
75
|
+
file: Uint8Array;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Renders the PDF of a sale invoice or credit note from the views, and
|
|
79
|
+
* records nothing. With `factur_x`, the document must be a posted sale: the
|
|
80
|
+
* CII is written from the books by the same adapter `einvoicing` uses, its
|
|
81
|
+
* broken rules are returned, and the XML is embedded all the same — a check
|
|
82
|
+
* that found something, which the caller reports.
|
|
83
|
+
*/
|
|
84
|
+
export declare function renderDocumentPdf(backend: Backend, args: DocumentPdfArgs, deps?: {
|
|
85
|
+
fetch?: LogoFetch | undefined;
|
|
86
|
+
}): Promise<DocumentPdf>;
|
|
87
|
+
//# sourceMappingURL=document-pdf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-pdf.d.ts","sourceRoot":"","sources":["../../src/books/document-pdf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAQL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,QAAQ,EACd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAEpF,OAAO,EAAc,KAAK,OAAO,EAAY,MAAM,cAAc,CAAC;AA6ClE,sCAAsC;AACtC,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CA0BxC;AAED,wEAAwE;AACxE,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAA;CAAE,KAAK,OAAO,CAAC;IAC5G,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;CACrC,CAAC,CAAC;AAKH;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuBzD;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAmBvH;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,8GAA8G;IAC9G,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,SAAS,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACjC,uFAAuF;IACvF,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,SAAS,CAAC;CAC7C;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,iBAAiB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAC;IACb,+HAA+H;IAC/H,QAAQ,EAAE;QAAE,OAAO,EAAE,SAAS,CAAC;QAAC,UAAU,EAAE,iBAAiB,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IACzE,IAAI,EAAE,UAAU,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,eAAe,EACrB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CAAO,GAC3C,OAAO,CAAC,WAAW,CAAC,CA+DtB"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The PDF of a sale invoice or credit note, for every surface at once.
|
|
3
|
+
*
|
|
4
|
+
* The command line (`ekwo doc pdf`) and the MCP server (`render_invoice_pdf`)
|
|
5
|
+
* call this with their own `Backend`, acting as the person signed in. It reads
|
|
6
|
+
* the four views a document is printed from — `document_header`,
|
|
7
|
+
* `document_line_items`, `document_tax_summary`, `document_legal_mentions` —
|
|
8
|
+
* and hands the rows to `@ekwo-ai/invoice-pdf`, which renders them and knows
|
|
9
|
+
* nothing of a database. Here is what only a client can do: read the views,
|
|
10
|
+
* fetch the logo the company names by URL, and, when asked, write the CII of
|
|
11
|
+
* the same document with the Factur-X adapter of the e-invoicing formats and
|
|
12
|
+
* embed it, so that the PDF is a Factur-X invoice.
|
|
13
|
+
*
|
|
14
|
+
* Nothing is recorded. A posted document reproduces from the books; the copy
|
|
15
|
+
* that was sent to a customer is another thing, and keeping it is not done
|
|
16
|
+
* here.
|
|
17
|
+
*/
|
|
18
|
+
import { embedFacturX } from '@ekwo-ai/factur-x/pdf';
|
|
19
|
+
import { InvoicePdfError, renderFacturXPdf, renderInvoicePdf, } from '@ekwo-ai/invoice-pdf';
|
|
20
|
+
import { EINVOICE_FORMATS } from '../einvoicing/formats.js';
|
|
21
|
+
import { readEinvoiceSource } from '../einvoicing/issue.js';
|
|
22
|
+
import { BooksError } from './backend.js';
|
|
23
|
+
import { onlyVisible } from './shared.js';
|
|
24
|
+
/** Every column of `document_header`, dates and figures as the text the database holds. */
|
|
25
|
+
const HEADER = [
|
|
26
|
+
'document_id', 'company_id', 'doc_type', 'state', 'payment_state', 'number', 'supplier_reference',
|
|
27
|
+
'document_date::text', 'accounting_date::text', 'due_date::text', 'delivery_date::text', 'currency_code',
|
|
28
|
+
'amount_untaxed::text', 'amount_tax::text', 'amount_total::text', 'amount_paid::text', 'amount_residual::text',
|
|
29
|
+
'payment_terms', 'payment_means_code', 'payment_reference', 'buyer_reference', 'order_reference',
|
|
30
|
+
'contract_reference', 'project_reference', 'note', 'payee_iban', 'payee_bic',
|
|
31
|
+
'seller_name', 'seller_legal_name', 'seller_legal_form', 'seller_vat_number', 'seller_registration_number',
|
|
32
|
+
'seller_address_line1', 'seller_address_line2', 'seller_postal_code', 'seller_city', 'seller_country',
|
|
33
|
+
'seller_region', 'seller_email', 'seller_phone', 'seller_website', 'seller_logo_url',
|
|
34
|
+
'seller_share_capital::text', 'seller_share_capital_currency', 'seller_activity_code', 'seller_activity_scheme',
|
|
35
|
+
'document_template',
|
|
36
|
+
'buyer_id', 'buyer_name', 'buyer_vat_number', 'buyer_registration_number', 'buyer_address_line1',
|
|
37
|
+
'buyer_address_line2', 'buyer_postal_code', 'buyer_city', 'buyer_country', 'buyer_region', 'buyer_email',
|
|
38
|
+
'country', 'number_format', 'numbering_gapless', 'legal_payment_days', 'late_payment_reference', 'tax_point_rule',
|
|
39
|
+
'einvoice_profile', 'einvoice_mandatory_from::text', 'party_scheme', 'vat_scheme',
|
|
40
|
+
'language', 'tax_point_date::text', 'delivery_address_line1', 'delivery_postal_code', 'delivery_city',
|
|
41
|
+
'delivery_country', 'seller_peppol_scheme', 'seller_peppol_identifier', 'buyer_peppol_scheme',
|
|
42
|
+
'buyer_peppol_identifier',
|
|
43
|
+
];
|
|
44
|
+
/** Every column of `document_line_items`. */
|
|
45
|
+
const LINES = [
|
|
46
|
+
'document_line_id', 'document_id', 'company_id', 'sequence', 'line_type', 'item_name', 'item_description',
|
|
47
|
+
'seller_item_identifier', 'product_id', 'product_kind', 'quantity::text', 'unit_code', 'unit_price::text',
|
|
48
|
+
'discount_percent::text', 'amount_untaxed::text', 'tax_id', 'vat_category', 'vat_rate::text', 'account_id',
|
|
49
|
+
'tax_treatment', 'tax_exemption_code', 'tax_cash_basis', 'unit_price_includes_tax', 'amount_incl_tax::text',
|
|
50
|
+
];
|
|
51
|
+
/** Every column of `document_tax_summary`. */
|
|
52
|
+
const TAXES = [
|
|
53
|
+
'document_id', 'company_id', 'doc_type', 'tax_id', 'tax_code', 'tax_name', 'vat_category', 'tax_rate::text',
|
|
54
|
+
'base_amount::text', 'tax_amount::text', 'tax_charged::text', 'exemption_code', 'exemption_reason',
|
|
55
|
+
'legal_reference',
|
|
56
|
+
];
|
|
57
|
+
/** Every column of `document_legal_mentions`. */
|
|
58
|
+
const MENTIONS = [
|
|
59
|
+
'document_id', 'company_id', 'country', 'code', 'applies_when', 'language', 'text', 'text_i18n', 'sequence',
|
|
60
|
+
'legal_reference',
|
|
61
|
+
];
|
|
62
|
+
/** The four views of one document. */
|
|
63
|
+
export async function readDocumentForPdf(backend, documentId) {
|
|
64
|
+
const header = onlyVisible(await backend.select({
|
|
65
|
+
table: 'document_header',
|
|
66
|
+
columns: HEADER,
|
|
67
|
+
where: [{ column: 'document_id', op: 'eq', value: documentId }],
|
|
68
|
+
}), `document ${documentId}`);
|
|
69
|
+
const where = [{ column: 'document_id', op: 'eq', value: documentId }];
|
|
70
|
+
const [lines, taxes, mentions] = await Promise.all([
|
|
71
|
+
backend.select({ table: 'document_line_items', columns: LINES, where, order: [{ column: 'sequence' }] }),
|
|
72
|
+
backend.select({ table: 'document_tax_summary', columns: TAXES, where, order: [{ column: 'tax_code' }] }),
|
|
73
|
+
backend.select({
|
|
74
|
+
table: 'document_legal_mentions',
|
|
75
|
+
columns: MENTIONS,
|
|
76
|
+
where,
|
|
77
|
+
order: [{ column: 'sequence' }, { column: 'code' }],
|
|
78
|
+
}),
|
|
79
|
+
]);
|
|
80
|
+
return {
|
|
81
|
+
header: header,
|
|
82
|
+
lines: lines,
|
|
83
|
+
taxes: taxes,
|
|
84
|
+
mentions: mentions,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const LOGO_MAX_BYTES = 2 * 1024 * 1024;
|
|
88
|
+
const LOGO_TIMEOUT_MS = 10_000;
|
|
89
|
+
/**
|
|
90
|
+
* Why a logo URL is not fetched, or null when it may be. Only `http` and
|
|
91
|
+
* `https`, and never a host that names this machine or a private network: the
|
|
92
|
+
* URL is a value a member of the company typed, and the server that fetches it
|
|
93
|
+
* may be somebody else's.
|
|
94
|
+
*/
|
|
95
|
+
export function logoUrlRefusal(url) {
|
|
96
|
+
let parsed;
|
|
97
|
+
try {
|
|
98
|
+
parsed = new URL(url);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return 'it is not a URL';
|
|
102
|
+
}
|
|
103
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
|
|
104
|
+
return `${parsed.protocol} is not http or https`;
|
|
105
|
+
const host = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
106
|
+
if (host === 'localhost' || host.endsWith('.localhost') || host.endsWith('.local') || host.endsWith('.internal')) {
|
|
107
|
+
return `${host} is a local name`;
|
|
108
|
+
}
|
|
109
|
+
const v4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
110
|
+
if (v4 !== null) {
|
|
111
|
+
const [a, b] = [Number(v4[1]), Number(v4[2])];
|
|
112
|
+
if (a === 0 || a === 10 || a === 127 || (a === 169 && b === 254) || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168) || (a === 100 && b >= 64 && b <= 127)) {
|
|
113
|
+
return `${host} is a private or local address`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (host.includes(':') && (host === '::1' || host === '::' || /^f[cd]/.test(host) || /^fe[89ab]/.test(host) || host.startsWith('::ffff:'))) {
|
|
117
|
+
return `${host} is a private or local address`;
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The bytes of the logo at `url`, or why there are none. A logo that cannot be
|
|
123
|
+
* had is not a reason to refuse an invoice: the PDF is rendered with the
|
|
124
|
+
* company's name instead, and the reason is said.
|
|
125
|
+
*/
|
|
126
|
+
export async function fetchLogo(url, fetchImpl) {
|
|
127
|
+
const refusal = logoUrlRefusal(url);
|
|
128
|
+
if (refusal !== null)
|
|
129
|
+
return { skipped: `the logo URL is not fetched: ${refusal}` };
|
|
130
|
+
const controller = new AbortController();
|
|
131
|
+
const timer = setTimeout(() => controller.abort(), LOGO_TIMEOUT_MS);
|
|
132
|
+
try {
|
|
133
|
+
const response = await fetchImpl(url, { signal: controller.signal, redirect: 'error' });
|
|
134
|
+
if (!response.ok)
|
|
135
|
+
return { skipped: `the logo URL answered ${response.status}` };
|
|
136
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
137
|
+
if (bytes.byteLength > LOGO_MAX_BYTES)
|
|
138
|
+
return { skipped: `the logo is ${bytes.byteLength} bytes, over ${LOGO_MAX_BYTES}` };
|
|
139
|
+
const png = bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47;
|
|
140
|
+
const jpeg = bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff;
|
|
141
|
+
if (!png && !jpeg)
|
|
142
|
+
return { skipped: 'the logo is neither a PNG nor a JPEG' };
|
|
143
|
+
return { bytes };
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
return { skipped: `the logo could not be fetched: ${error instanceof Error ? error.message : String(error)}` };
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Renders the PDF of a sale invoice or credit note from the views, and
|
|
154
|
+
* records nothing. With `factur_x`, the document must be a posted sale: the
|
|
155
|
+
* CII is written from the books by the same adapter `einvoicing` uses, its
|
|
156
|
+
* broken rules are returned, and the XML is embedded all the same — a check
|
|
157
|
+
* that found something, which the caller reports.
|
|
158
|
+
*/
|
|
159
|
+
export async function renderDocumentPdf(backend, args, deps = {}) {
|
|
160
|
+
const input = await readDocumentForPdf(backend, args.document_id);
|
|
161
|
+
const { header } = input;
|
|
162
|
+
let logo = 'none';
|
|
163
|
+
const url = typeof header.seller_logo_url === 'string' ? header.seller_logo_url.trim() : '';
|
|
164
|
+
if (url !== '') {
|
|
165
|
+
if (deps.fetch === undefined)
|
|
166
|
+
logo = 'the logo URL is not fetched: no fetch was given';
|
|
167
|
+
else {
|
|
168
|
+
const fetched = await fetchLogo(url, deps.fetch);
|
|
169
|
+
if ('bytes' in fetched) {
|
|
170
|
+
input.logo = fetched.bytes;
|
|
171
|
+
logo = 'embedded';
|
|
172
|
+
}
|
|
173
|
+
else
|
|
174
|
+
logo = fetched.skipped;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const options = {
|
|
178
|
+
...(args.page_size === undefined ? {} : { pageSize: args.page_size }),
|
|
179
|
+
...(args.labels === undefined ? {} : { labels: args.labels }),
|
|
180
|
+
producer: '@ekwo-ai/core',
|
|
181
|
+
};
|
|
182
|
+
let factur = null;
|
|
183
|
+
let rendered;
|
|
184
|
+
try {
|
|
185
|
+
if (args.factur_x === true) {
|
|
186
|
+
if (header.state !== 'posted') {
|
|
187
|
+
throw new BooksError(`document_not_posted: ${String(header.doc_type)} ${String(header.number ?? args.document_id)} is ${String(header.state)}, and a Factur-X invoice is written from a posted document only.`, { hint: 'post_document (ekwo post <document>) books it; without --factur-x the PDF of a draft is rendered and titled as one.' });
|
|
188
|
+
}
|
|
189
|
+
const format = EINVOICE_FORMATS['factur-x-en16931'];
|
|
190
|
+
if (format === undefined)
|
|
191
|
+
throw new BooksError('format_without_brick: no brick writes factur-x-en16931 in this release.');
|
|
192
|
+
const written = format.write(await readEinvoiceSource(backend, args.document_id));
|
|
193
|
+
factur = { profile: 'en16931', violations: written.violations };
|
|
194
|
+
rendered = await renderFacturXPdf(input, { ...options, xml: written.content, profile: 'en16931', embed: embedFacturX });
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
rendered = await renderInvoicePdf(input, options);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (error instanceof InvoicePdfError) {
|
|
202
|
+
throw new BooksError(`invoice_pdf_unrenderable: ${error.message}`);
|
|
203
|
+
}
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
document_id: args.document_id,
|
|
208
|
+
doc_type: String(header.doc_type),
|
|
209
|
+
number: header.number ?? null,
|
|
210
|
+
state: header.state ?? null,
|
|
211
|
+
language: header.language ?? null,
|
|
212
|
+
filename: rendered.filename,
|
|
213
|
+
title: rendered.title,
|
|
214
|
+
media_type: 'application/pdf',
|
|
215
|
+
byte_size: rendered.file.byteLength,
|
|
216
|
+
page_count: rendered.pageCount,
|
|
217
|
+
logo,
|
|
218
|
+
factur_x: factur,
|
|
219
|
+
file: rendered.file,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=document-pdf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-pdf.js","sourceRoot":"","sources":["../../src/books/document-pdf.ts"],"names":[],"mappings":"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"}
|
package/dist/books/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/books/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,cAAc,CAAC;AAC7B,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/books/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,cAAc,CAAC;AAC7B,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC"}
|
package/dist/books/index.js
CHANGED
package/dist/books/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/books/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,cAAc,CAAC;AAC7B,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/books/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,cAAc,CAAC;AAC7B,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transport that ships: a folder.
|
|
3
|
+
*
|
|
4
|
+
* It writes the file into `<root>/outbox/` and says where. No network, no
|
|
5
|
+
* credential, no account anywhere — which is what makes it the default, the
|
|
6
|
+
* one a self-hosted installation can use on the first day, and the one the
|
|
7
|
+
* tests run end to end. Whatever carries the files further — a script that
|
|
8
|
+
* uploads them to a portal, the client of an access point, a person — reads
|
|
9
|
+
* the folder, and may answer through it:
|
|
10
|
+
*
|
|
11
|
+
* <root>/outbox/<file> written once, never overwritten
|
|
12
|
+
* <root>/outbox/<file>.accepted_by_access_point
|
|
13
|
+
* <root>/outbox/<file>.delivered a receipt: its text is the
|
|
14
|
+
* <root>/outbox/<file>.rejected message, kept word for word
|
|
15
|
+
* <root>/inbox/<file> what arrived, read by receive()
|
|
16
|
+
*
|
|
17
|
+
* The channel is `self`: the company takes the file where it goes, through a
|
|
18
|
+
* program of its own. A folder cannot tell whether an address is reachable,
|
|
19
|
+
* and `lookup()` says exactly that.
|
|
20
|
+
*
|
|
21
|
+
* A file name carries the first twelve characters of the file's checksum and
|
|
22
|
+
* the first eight of the sending's identifier. Two issues of one document
|
|
23
|
+
* never meet in the folder, and neither do two sendings of one file: a file
|
|
24
|
+
* sent again after a refusal is a new file beside the refused one, so the
|
|
25
|
+
* receipt of the first is never read as the answer to the second. Writing for
|
|
26
|
+
* the same sending twice is the same file — the second write finds it and
|
|
27
|
+
* checks it is byte for byte the one it would have written.
|
|
28
|
+
*/
|
|
29
|
+
import { type EinvoiceFile, type EinvoiceTransport } from './transport.js';
|
|
30
|
+
/** `invoice-INV-1.xml`, a checksum and a sending → `invoice-INV-1.0f343b093112.5c1e0a7d.xml`. */
|
|
31
|
+
export declare function directoryFilename(file: Pick<EinvoiceFile, 'filename' | 'checksum'>, transmissionId: string): string;
|
|
32
|
+
export interface DirectoryTransportOptions {
|
|
33
|
+
/** Recorded as the service's name. A folder is the company's own act, so none by default. */
|
|
34
|
+
service?: string | null;
|
|
35
|
+
}
|
|
36
|
+
/** The folder transport, rooted at `root`. The folders are created on first use. */
|
|
37
|
+
export declare function directoryTransport(root: string, options?: DirectoryTransportOptions): EinvoiceTransport;
|
|
38
|
+
//# sourceMappingURL=directory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directory.d.ts","sourceRoot":"","sources":["../../src/einvoicing/directory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EAMvB,MAAM,gBAAgB,CAAC;AAaxB,iGAAiG;AACjG,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,UAAU,CAAC,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAMnH;AAeD,MAAM,WAAW,yBAAyB;IACxC,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,yBAA8B,GAAG,iBAAiB,CAgE3G"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transport that ships: a folder.
|
|
3
|
+
*
|
|
4
|
+
* It writes the file into `<root>/outbox/` and says where. No network, no
|
|
5
|
+
* credential, no account anywhere — which is what makes it the default, the
|
|
6
|
+
* one a self-hosted installation can use on the first day, and the one the
|
|
7
|
+
* tests run end to end. Whatever carries the files further — a script that
|
|
8
|
+
* uploads them to a portal, the client of an access point, a person — reads
|
|
9
|
+
* the folder, and may answer through it:
|
|
10
|
+
*
|
|
11
|
+
* <root>/outbox/<file> written once, never overwritten
|
|
12
|
+
* <root>/outbox/<file>.accepted_by_access_point
|
|
13
|
+
* <root>/outbox/<file>.delivered a receipt: its text is the
|
|
14
|
+
* <root>/outbox/<file>.rejected message, kept word for word
|
|
15
|
+
* <root>/inbox/<file> what arrived, read by receive()
|
|
16
|
+
*
|
|
17
|
+
* The channel is `self`: the company takes the file where it goes, through a
|
|
18
|
+
* program of its own. A folder cannot tell whether an address is reachable,
|
|
19
|
+
* and `lookup()` says exactly that.
|
|
20
|
+
*
|
|
21
|
+
* A file name carries the first twelve characters of the file's checksum and
|
|
22
|
+
* the first eight of the sending's identifier. Two issues of one document
|
|
23
|
+
* never meet in the folder, and neither do two sendings of one file: a file
|
|
24
|
+
* sent again after a refusal is a new file beside the refused one, so the
|
|
25
|
+
* receipt of the first is never read as the answer to the second. Writing for
|
|
26
|
+
* the same sending twice is the same file — the second write finds it and
|
|
27
|
+
* checks it is byte for byte the one it would have written.
|
|
28
|
+
*/
|
|
29
|
+
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
30
|
+
import { basename, extname, join } from 'node:path';
|
|
31
|
+
import { TransportError, } from './transport.js';
|
|
32
|
+
/** The answers a carrier may leave beside a file, in the order they are read: the furthest first. */
|
|
33
|
+
const RECEIPTS = ['delivered', 'rejected', 'accepted_by_access_point'];
|
|
34
|
+
const OUTBOX = 'outbox';
|
|
35
|
+
const INBOX = 'inbox';
|
|
36
|
+
/** The media type of a file that arrived, from its name. Nothing is guessed beyond the extension. */
|
|
37
|
+
function mediaTypeOf(name) {
|
|
38
|
+
return extname(name).toLowerCase() === '.xml' ? 'application/xml' : 'application/octet-stream';
|
|
39
|
+
}
|
|
40
|
+
/** `invoice-INV-1.xml`, a checksum and a sending → `invoice-INV-1.0f343b093112.5c1e0a7d.xml`. */
|
|
41
|
+
export function directoryFilename(file, transmissionId) {
|
|
42
|
+
const safe = basename(file.filename).replace(/[^A-Za-z0-9._-]+/g, '-');
|
|
43
|
+
const extension = extname(safe);
|
|
44
|
+
const stem = extension === '' ? safe : safe.slice(0, -extension.length);
|
|
45
|
+
const sending = transmissionId.replace(/[^A-Za-z0-9]+/g, '').slice(0, 8);
|
|
46
|
+
return `${stem === '' ? 'einvoice' : stem}.${file.checksum.slice(0, 12)}${sending === '' ? '' : `.${sending}`}${extension}`;
|
|
47
|
+
}
|
|
48
|
+
/** A reference this transport gave, and nothing else: `outbox/<one file name>`. */
|
|
49
|
+
function outboxPath(root, reference) {
|
|
50
|
+
const name = reference.startsWith(`${OUTBOX}/`) ? reference.slice(OUTBOX.length + 1) : '';
|
|
51
|
+
if (name === '' || name !== basename(name) || name.startsWith('.')) {
|
|
52
|
+
throw new TransportError(`unknown_reference: ${reference} is not a file this directory wrote`);
|
|
53
|
+
}
|
|
54
|
+
return join(root, OUTBOX, name);
|
|
55
|
+
}
|
|
56
|
+
async function exists(path) {
|
|
57
|
+
return (await stat(path).catch(() => undefined)) !== undefined;
|
|
58
|
+
}
|
|
59
|
+
/** The folder transport, rooted at `root`. The folders are created on first use. */
|
|
60
|
+
export function directoryTransport(root, options = {}) {
|
|
61
|
+
return {
|
|
62
|
+
channel: 'self',
|
|
63
|
+
service: options.service ?? null,
|
|
64
|
+
async send(file, metadata) {
|
|
65
|
+
const name = directoryFilename(file, metadata.transmissionId);
|
|
66
|
+
const path = join(root, OUTBOX, name);
|
|
67
|
+
try {
|
|
68
|
+
await mkdir(join(root, OUTBOX), { recursive: true });
|
|
69
|
+
await writeFile(path, file.content, { encoding: 'utf8', flag: 'wx' });
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const code = error.code;
|
|
73
|
+
if (code !== 'EEXIST') {
|
|
74
|
+
throw new TransportError(`directory_unwritable: ${error.message}`);
|
|
75
|
+
}
|
|
76
|
+
// Written already: the same file, or a refusal. Never overwritten.
|
|
77
|
+
const there = await readFile(path, 'utf8');
|
|
78
|
+
if (there !== file.content) {
|
|
79
|
+
throw new TransportError(`directory_conflict: ${path} already holds another file under this name, and it is not overwritten`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { reference: `${OUTBOX}/${name}`, state: 'submitted', message: null };
|
|
83
|
+
},
|
|
84
|
+
async status(reference) {
|
|
85
|
+
const path = outboxPath(root, reference);
|
|
86
|
+
for (const state of RECEIPTS) {
|
|
87
|
+
const receipt = `${path}.${state}`;
|
|
88
|
+
if (await exists(receipt)) {
|
|
89
|
+
const text = await readFile(receipt, 'utf8');
|
|
90
|
+
return { state, message: text === '' ? null : text };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// No receipt: the file is where it was written, or whatever carries the
|
|
94
|
+
// files took it and has said nothing yet. Either way it was handed over.
|
|
95
|
+
return { state: 'submitted', message: null };
|
|
96
|
+
},
|
|
97
|
+
async receive() {
|
|
98
|
+
const dir = join(root, INBOX);
|
|
99
|
+
const names = (await readdir(dir).catch(() => [])).filter((name) => !name.startsWith('.')).sort();
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const name of names) {
|
|
102
|
+
const path = join(dir, name);
|
|
103
|
+
const info = await stat(path);
|
|
104
|
+
if (!info.isFile())
|
|
105
|
+
continue;
|
|
106
|
+
out.push({
|
|
107
|
+
reference: `${INBOX}/${name}`,
|
|
108
|
+
filename: name,
|
|
109
|
+
mediaType: mediaTypeOf(name),
|
|
110
|
+
content: await readFile(path, 'utf8'),
|
|
111
|
+
receivedAt: info.mtime.toISOString(),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
},
|
|
116
|
+
async lookup() {
|
|
117
|
+
return { reachable: null, message: 'a directory cannot tell whether an address is reachable' };
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=directory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directory.js","sourceRoot":"","sources":["../../src/einvoicing/directory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which brick writes which profile, and how each is fed from the books.
|
|
3
|
+
*
|
|
4
|
+
* A country pack says which e-invoicing profile its companies issue in —
|
|
5
|
+
* `peppol-bis-3`, `factur-x-en16931`, `xrechnung`, a PINT — and the socle
|
|
6
|
+
* compiles it into `country_defaults.einvoice_profile`, which
|
|
7
|
+
* `document_header` publishes. A profile is a format, not a country: the
|
|
8
|
+
* mapping below names no country and would be the same for any pack that
|
|
9
|
+
* declared the same profile.
|
|
10
|
+
*
|
|
11
|
+
* **A profile without a brick is refused by name**, `format_without_brick`,
|
|
12
|
+
* and never written in the nearest format that exists: a file in another
|
|
13
|
+
* profile is a file the network or the platform refuses, after it has left.
|
|
14
|
+
* Adding a profile is adding an entry here and a brick under
|
|
15
|
+
* `packages/formats/`, nothing else.
|
|
16
|
+
*
|
|
17
|
+
* Every writer reads the same three rows the views publish and nothing else,
|
|
18
|
+
* plus one fact the views do not carry and the books do: which invoice a
|
|
19
|
+
* credit note credits (BG-3), read from `documents.reversed_document_id`.
|
|
20
|
+
* Nothing is defaulted on the way: a value the books do not hold is a value
|
|
21
|
+
* the file does not hold, and the rule that wanted it is reported.
|
|
22
|
+
*/
|
|
23
|
+
import { type Row } from '../books/backend.js';
|
|
24
|
+
/** One rule of the format a file breaks, named as the brick or the published rule names it. */
|
|
25
|
+
export interface EinvoiceViolation {
|
|
26
|
+
code: string;
|
|
27
|
+
message: string;
|
|
28
|
+
line?: string;
|
|
29
|
+
}
|
|
30
|
+
/** A posted sale as the books publish it: three views, and the invoice a credit note credits. */
|
|
31
|
+
export interface EinvoiceSource {
|
|
32
|
+
header: Row;
|
|
33
|
+
lines: Row[];
|
|
34
|
+
taxes: Row[];
|
|
35
|
+
preceding: {
|
|
36
|
+
number: string;
|
|
37
|
+
issueDate: string | null;
|
|
38
|
+
} | null;
|
|
39
|
+
}
|
|
40
|
+
/** What a writer hands back. */
|
|
41
|
+
export interface WrittenEinvoice {
|
|
42
|
+
content: string;
|
|
43
|
+
filename: string;
|
|
44
|
+
violations: EinvoiceViolation[];
|
|
45
|
+
}
|
|
46
|
+
/** One profile a brick writes. */
|
|
47
|
+
export interface EinvoiceFormat {
|
|
48
|
+
profile: string;
|
|
49
|
+
/** The package of `packages/formats/` that writes it, by its published name. */
|
|
50
|
+
brick: string;
|
|
51
|
+
syntax: string;
|
|
52
|
+
mediaType: string;
|
|
53
|
+
/** What the file declares it follows: a customization identifier, a guideline. */
|
|
54
|
+
specification: string;
|
|
55
|
+
write(source: EinvoiceSource): WrittenEinvoice;
|
|
56
|
+
}
|
|
57
|
+
/** Every profile a brick of this release writes, by the name a pack declares it under. */
|
|
58
|
+
export declare const EINVOICE_FORMATS: Readonly<Record<string, EinvoiceFormat>>;
|
|
59
|
+
/**
|
|
60
|
+
* The format of a profile, or the refusal that names what is missing: no
|
|
61
|
+
* profile in the pack (`no_einvoicing_profile`), or a profile no brick of this
|
|
62
|
+
* release writes (`format_without_brick`).
|
|
63
|
+
*/
|
|
64
|
+
export declare function einvoiceFormat(profile: string | null | undefined): EinvoiceFormat;
|
|
65
|
+
//# sourceMappingURL=formats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formats.d.ts","sourceRoot":"","sources":["../../src/einvoicing/formats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAMH,OAAO,EAAc,KAAK,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAE3D,+FAA+F;AAC/F,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,iGAAiG;AACjG,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,GAAG,CAAC;IACZ,KAAK,EAAE,GAAG,EAAE,CAAC;IACb,KAAK,EAAE,GAAG,EAAE,CAAC;IACb,SAAS,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CAChE;AAED,gCAAgC;AAChC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,iBAAiB,EAAE,CAAC;CACjC;AAED,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,MAAM,EAAE,cAAc,GAAG,eAAe,CAAC;CAChD;AAmVD,0FAA0F;AAC1F,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAGrE,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,cAAc,CAejF"}
|