@ic-labs/invoice-kit 0.1.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 +552 -0
- package/dist/core/document.d.ts +8 -0
- package/dist/core/document.d.ts.map +1 -0
- package/dist/core/document.js +22 -0
- package/dist/core/document.js.map +1 -0
- package/dist/core/factories.d.ts +7 -0
- package/dist/core/factories.d.ts.map +1 -0
- package/dist/core/factories.js +44 -0
- package/dist/core/factories.js.map +1 -0
- package/dist/core/types.d.ts +141 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/validation.d.ts +3 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +95 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/locales/ar.d.ts +3 -0
- package/dist/locales/ar.d.ts.map +1 -0
- package/dist/locales/ar.js +31 -0
- package/dist/locales/ar.js.map +1 -0
- package/dist/locales/fr.d.ts +3 -0
- package/dist/locales/fr.d.ts.map +1 -0
- package/dist/locales/fr.js +31 -0
- package/dist/locales/fr.js.map +1 -0
- package/dist/locales/index.d.ts +3 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/index.js +8 -0
- package/dist/locales/index.js.map +1 -0
- package/dist/renderers/pdf.d.ts +3 -0
- package/dist/renderers/pdf.d.ts.map +1 -0
- package/dist/renderers/pdf.js +337 -0
- package/dist/renderers/pdf.js.map +1 -0
- package/dist/templates/definitions.d.ts +3 -0
- package/dist/templates/definitions.d.ts.map +1 -0
- package/dist/templates/definitions.js +12 -0
- package/dist/templates/definitions.js.map +1 -0
- package/dist/templates/delivery-note.d.ts +3 -0
- package/dist/templates/delivery-note.d.ts.map +1 -0
- package/dist/templates/delivery-note.js +11 -0
- package/dist/templates/delivery-note.js.map +1 -0
- package/dist/templates/invoice.d.ts +3 -0
- package/dist/templates/invoice.d.ts.map +1 -0
- package/dist/templates/invoice.js +11 -0
- package/dist/templates/invoice.js.map +1 -0
- package/dist/templates/purchase-order.d.ts +3 -0
- package/dist/templates/purchase-order.d.ts.map +1 -0
- package/dist/templates/purchase-order.js +11 -0
- package/dist/templates/purchase-order.js.map +1 -0
- package/dist/templates/quote.d.ts +3 -0
- package/dist/templates/quote.d.ts.map +1 -0
- package/dist/templates/quote.js +11 -0
- package/dist/templates/quote.js.map +1 -0
- package/dist/utils/formatting.d.ts +4 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/formatting.js +18 -0
- package/dist/utils/formatting.js.map +1 -0
- package/dist/utils/math.d.ts +3 -0
- package/dist/utils/math.d.ts.map +1 -0
- package/dist/utils/math.js +14 -0
- package/dist/utils/math.js.map +1 -0
- package/dist/utils/taxes.d.ts +5 -0
- package/dist/utils/taxes.d.ts.map +1 -0
- package/dist/utils/taxes.js +44 -0
- package/dist/utils/taxes.js.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Itri Cloud Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
# @ic-labs/invoice-kit
|
|
2
|
+
|
|
3
|
+
TypeScript library for generating Moroccan business documents as PDFs.
|
|
4
|
+
|
|
5
|
+
It is designed for real business usage, not a demo. The package provides typed document factories, business calculations, French and Arabic labels, Moroccan invoice defaults, and PDF generation for:
|
|
6
|
+
|
|
7
|
+
- `Facture` / invoice
|
|
8
|
+
- `Devis` / quote
|
|
9
|
+
- `Bon de commande` / purchase order
|
|
10
|
+
- `Bon de livraison` / delivery note
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- Strict TypeScript API
|
|
15
|
+
- Node.js 18+
|
|
16
|
+
- Lightweight runtime based on `pdfkit`
|
|
17
|
+
- Clean factories for all document types
|
|
18
|
+
- Automatic subtotal, discount, VAT, taxable base, and total calculations
|
|
19
|
+
- Moroccan defaults:
|
|
20
|
+
- currency: `MAD`
|
|
21
|
+
- VAT label: `TVA`
|
|
22
|
+
- date format: `dd/MM/yyyy`
|
|
23
|
+
- company identifiers: `ICE`, `IF`, `RC`
|
|
24
|
+
- French labels by default
|
|
25
|
+
- Arabic labels with `locale: "ar-MA"`
|
|
26
|
+
- Bank details support:
|
|
27
|
+
- local transfer: bank name, holder name, RIB
|
|
28
|
+
- international transfer: bank name, holder name, SWIFT, IBAN
|
|
29
|
+
- Remote logo URL support in PDF headers
|
|
30
|
+
- Delivery notes rendered without pricing information
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @ic-labs/invoice-kit
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { createInvoice } from "@ic-labs/invoice-kit";
|
|
42
|
+
|
|
43
|
+
const invoice = createInvoice({
|
|
44
|
+
number: "FAC-2026-0001",
|
|
45
|
+
logo: "https://example.com/assets/logo.png",
|
|
46
|
+
seller: {
|
|
47
|
+
name: "IC Labs SARL",
|
|
48
|
+
addressLines: ["Casablanca, Maroc"],
|
|
49
|
+
ice: "001234567890123",
|
|
50
|
+
if: "12345678",
|
|
51
|
+
rc: "56789"
|
|
52
|
+
},
|
|
53
|
+
client: {
|
|
54
|
+
name: "Client Demo",
|
|
55
|
+
addressLines: ["Rabat, Maroc"]
|
|
56
|
+
},
|
|
57
|
+
items: [
|
|
58
|
+
{
|
|
59
|
+
name: "Service de conseil",
|
|
60
|
+
quantity: 1,
|
|
61
|
+
price: 1000
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
vatRate: 0.2,
|
|
65
|
+
paymentTerms: {
|
|
66
|
+
label: "Paiement sous 30 jours"
|
|
67
|
+
},
|
|
68
|
+
bankInfo: {
|
|
69
|
+
type: "local",
|
|
70
|
+
bankName: "Attijariwafa Bank",
|
|
71
|
+
holderName: "IC Labs SARL",
|
|
72
|
+
rib: "007 810 000123456789012345"
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await invoice.toPDF("./invoice.pdf");
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Public API
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import {
|
|
83
|
+
createDeliveryNote,
|
|
84
|
+
createInvoice,
|
|
85
|
+
createPurchaseOrder,
|
|
86
|
+
createQuote
|
|
87
|
+
} from "@ic-labs/invoice-kit";
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Each factory returns a `BusinessDocument` instance.
|
|
91
|
+
|
|
92
|
+
### `BusinessDocument`
|
|
93
|
+
|
|
94
|
+
- `toJSON(): BusinessDocumentData`
|
|
95
|
+
- `toPDF(outputPathOrOptions?): Promise<Uint8Array>`
|
|
96
|
+
|
|
97
|
+
Examples:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const bytes = await invoice.toPDF();
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
await invoice.toPDF("./invoice.pdf");
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
await invoice.toPDF({
|
|
109
|
+
outputPath: "./invoice.pdf"
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Document Factories
|
|
114
|
+
|
|
115
|
+
### `createInvoice(input)`
|
|
116
|
+
|
|
117
|
+
Creates a financial invoice with totals, discounts, VAT, bank details, and payment terms.
|
|
118
|
+
|
|
119
|
+
### `createQuote(input)`
|
|
120
|
+
|
|
121
|
+
Creates a quote/devis with the same financial model as invoices.
|
|
122
|
+
|
|
123
|
+
### `createPurchaseOrder(input)`
|
|
124
|
+
|
|
125
|
+
Creates a purchase order with pricing and totals.
|
|
126
|
+
|
|
127
|
+
### `createDeliveryNote(input)`
|
|
128
|
+
|
|
129
|
+
Creates a delivery note without pricing display in the PDF.
|
|
130
|
+
|
|
131
|
+
Important behavior for delivery notes:
|
|
132
|
+
|
|
133
|
+
- item prices are not required
|
|
134
|
+
- pricing columns are hidden in the PDF
|
|
135
|
+
- totals are hidden in the PDF
|
|
136
|
+
- `discounts`, `vatRate`, `paymentTerms`, and `bankInfo` may exist in input but are not rendered as financial output
|
|
137
|
+
- internal totals are normalized to zero for delivery notes
|
|
138
|
+
|
|
139
|
+
## Input Reference
|
|
140
|
+
|
|
141
|
+
All document factories accept a typed object shaped like `DocumentInput`.
|
|
142
|
+
|
|
143
|
+
### Required fields
|
|
144
|
+
|
|
145
|
+
- `number: string`
|
|
146
|
+
- `seller: Party`
|
|
147
|
+
- `client: Party`
|
|
148
|
+
- `items: LineItemInput[]`
|
|
149
|
+
|
|
150
|
+
### Common optional fields
|
|
151
|
+
|
|
152
|
+
- `logo?: string`
|
|
153
|
+
- `issueDate?: Date`
|
|
154
|
+
- `dueDate?: Date`
|
|
155
|
+
- `currency?: string`
|
|
156
|
+
- `vatRate?: number`
|
|
157
|
+
- `discounts?: DiscountInput[]`
|
|
158
|
+
- `notes?: string`
|
|
159
|
+
- `paymentTerms?: PaymentTerms`
|
|
160
|
+
- `bankInfo?: BankInfo`
|
|
161
|
+
- `locale?: "fr-MA" | "ar-MA"`
|
|
162
|
+
- `title?: string`
|
|
163
|
+
|
|
164
|
+
### `Party`
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
interface Party {
|
|
168
|
+
name: string;
|
|
169
|
+
addressLines: string[];
|
|
170
|
+
email?: string;
|
|
171
|
+
phone?: string;
|
|
172
|
+
city?: string;
|
|
173
|
+
country?: string;
|
|
174
|
+
taxId?: string;
|
|
175
|
+
ice?: string;
|
|
176
|
+
if?: string;
|
|
177
|
+
rc?: string;
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Notes:
|
|
182
|
+
|
|
183
|
+
- `addressLines` must contain at least one line
|
|
184
|
+
- seller identifiers `ICE`, `IF`, and `RC` are rendered directly inside seller details
|
|
185
|
+
|
|
186
|
+
### `LineItemInput`
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
interface LineItemInput {
|
|
190
|
+
name: string;
|
|
191
|
+
description?: string;
|
|
192
|
+
quantity: number;
|
|
193
|
+
unitPrice?: number;
|
|
194
|
+
price?: number;
|
|
195
|
+
unit?: string;
|
|
196
|
+
discountRate?: number;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Notes:
|
|
201
|
+
|
|
202
|
+
- financial documents require either `unitPrice` or `price`
|
|
203
|
+
- delivery notes do not require pricing
|
|
204
|
+
- `discountRate` is a decimal ratio, for example:
|
|
205
|
+
- `0.1` for 10%
|
|
206
|
+
- `0.05` for 5%
|
|
207
|
+
|
|
208
|
+
### `DiscountInput`
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
interface DiscountInput {
|
|
212
|
+
type: "percentage" | "fixed";
|
|
213
|
+
value: number;
|
|
214
|
+
label?: string;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Examples:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
{ type: "fixed", value: 150, label: "Remise commerciale" }
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
{ type: "percentage", value: 0.02, label: "Escompte" }
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### `PaymentTerms`
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
interface PaymentTerms {
|
|
232
|
+
label: string;
|
|
233
|
+
dueDate?: Date;
|
|
234
|
+
notes?: string;
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### `BankInfo`
|
|
239
|
+
|
|
240
|
+
#### Local transfer
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
{
|
|
244
|
+
type: "local",
|
|
245
|
+
bankName: "Attijariwafa Bank",
|
|
246
|
+
holderName: "IC Labs SARL",
|
|
247
|
+
rib: "007 810 000123456789012345"
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### International transfer
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
{
|
|
255
|
+
type: "international",
|
|
256
|
+
bankName: "BMCI Corporate",
|
|
257
|
+
holderName: "IC Labs SARL",
|
|
258
|
+
swiftCode: "BMCIMAMC",
|
|
259
|
+
iban: "MA64001122000001234567890123"
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Financial Model
|
|
264
|
+
|
|
265
|
+
For invoices, quotes, and purchase orders, the package calculates:
|
|
266
|
+
|
|
267
|
+
- line subtotal
|
|
268
|
+
- line discount amount
|
|
269
|
+
- line total
|
|
270
|
+
- subtotal
|
|
271
|
+
- document discount total
|
|
272
|
+
- taxable base
|
|
273
|
+
- VAT amount
|
|
274
|
+
- grand total
|
|
275
|
+
|
|
276
|
+
### VAT
|
|
277
|
+
|
|
278
|
+
- default VAT rate is `0.2`
|
|
279
|
+
- VAT is displayed as `TVA`
|
|
280
|
+
- rate input uses decimal form:
|
|
281
|
+
- `0.2` = 20%
|
|
282
|
+
- `0.1` = 10%
|
|
283
|
+
- `0` = 0%
|
|
284
|
+
|
|
285
|
+
### Currency
|
|
286
|
+
|
|
287
|
+
- default currency is `MAD`
|
|
288
|
+
- amounts are formatted with two decimals
|
|
289
|
+
|
|
290
|
+
## Moroccan Business Context
|
|
291
|
+
|
|
292
|
+
The package is tailored for Morocco:
|
|
293
|
+
|
|
294
|
+
- `MAD` default currency
|
|
295
|
+
- `TVA` terminology
|
|
296
|
+
- seller company fields:
|
|
297
|
+
- `ICE`
|
|
298
|
+
- `IF`
|
|
299
|
+
- `RC`
|
|
300
|
+
- French-first labels
|
|
301
|
+
- Arabic labels available via locale
|
|
302
|
+
- date formatting in `dd/MM/yyyy`
|
|
303
|
+
|
|
304
|
+
## Localization
|
|
305
|
+
|
|
306
|
+
Supported locales:
|
|
307
|
+
|
|
308
|
+
- `fr-MA`
|
|
309
|
+
- `ar-MA`
|
|
310
|
+
|
|
311
|
+
Example:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
const quote = createQuote({
|
|
315
|
+
number: "DEV-2026-0012",
|
|
316
|
+
locale: "ar-MA",
|
|
317
|
+
seller: {
|
|
318
|
+
name: "IC Labs SARL",
|
|
319
|
+
addressLines: ["Casablanca"]
|
|
320
|
+
},
|
|
321
|
+
client: {
|
|
322
|
+
name: "Client",
|
|
323
|
+
addressLines: ["Rabat"]
|
|
324
|
+
},
|
|
325
|
+
items: [{ name: "Audit", quantity: 1, price: 1200 }]
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Arabic PDF rendering
|
|
330
|
+
|
|
331
|
+
Arabic labels are supported, but Arabic PDFs require Arabic-capable font files when calling `toPDF()`.
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
await quote.toPDF({
|
|
335
|
+
outputPath: "./devis-ar.pdf",
|
|
336
|
+
fonts: {
|
|
337
|
+
regular: "./fonts/NotoSansArabic-Regular.ttf",
|
|
338
|
+
bold: "./fonts/NotoSansArabic-Bold.ttf"
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Without custom fonts, Arabic glyph rendering is not reliable in PDF output.
|
|
344
|
+
|
|
345
|
+
## Remote Logo Support
|
|
346
|
+
|
|
347
|
+
You can pass a remote image URL through `logo`.
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
logo: "https://example.com/assets/logo.png"
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Behavior:
|
|
354
|
+
|
|
355
|
+
- the library fetches the image itself during `toPDF()`
|
|
356
|
+
- the user only provides the URL
|
|
357
|
+
- the logo is rendered in the document header
|
|
358
|
+
|
|
359
|
+
Currently supported remote image formats:
|
|
360
|
+
|
|
361
|
+
- `PNG`
|
|
362
|
+
- `JPEG` / `JPG`
|
|
363
|
+
|
|
364
|
+
Currently rejected remote image formats:
|
|
365
|
+
|
|
366
|
+
- `SVG`
|
|
367
|
+
- `WEBP`
|
|
368
|
+
|
|
369
|
+
Why:
|
|
370
|
+
|
|
371
|
+
- `pdfkit` does not natively embed `SVG` or `WEBP`
|
|
372
|
+
- this package intentionally keeps dependencies light and does not bundle image conversion tooling
|
|
373
|
+
|
|
374
|
+
## Validation Rules
|
|
375
|
+
|
|
376
|
+
The library validates document input before building the document model.
|
|
377
|
+
|
|
378
|
+
Examples of enforced rules:
|
|
379
|
+
|
|
380
|
+
- `number` must be present
|
|
381
|
+
- seller and client names must be non-empty
|
|
382
|
+
- seller and client must have at least one address line
|
|
383
|
+
- `items` must contain at least one line item
|
|
384
|
+
- quantity must be greater than zero
|
|
385
|
+
- financial documents require `unitPrice` or `price`
|
|
386
|
+
- negative prices are rejected
|
|
387
|
+
- negative discounts are rejected
|
|
388
|
+
- `vatRate` must be between `0` and `1`
|
|
389
|
+
- `logo` must be a valid `http` or `https` URL
|
|
390
|
+
- bank fields must be present according to bank mode
|
|
391
|
+
|
|
392
|
+
## Rendering Behavior
|
|
393
|
+
|
|
394
|
+
### Financial documents
|
|
395
|
+
|
|
396
|
+
Invoices, quotes, and purchase orders render:
|
|
397
|
+
|
|
398
|
+
- seller block
|
|
399
|
+
- client block
|
|
400
|
+
- issue/due date
|
|
401
|
+
- currency
|
|
402
|
+
- item table with amount columns
|
|
403
|
+
- totals section
|
|
404
|
+
- payment terms
|
|
405
|
+
- bank details
|
|
406
|
+
- notes
|
|
407
|
+
|
|
408
|
+
### Delivery notes
|
|
409
|
+
|
|
410
|
+
Delivery notes render:
|
|
411
|
+
|
|
412
|
+
- seller block
|
|
413
|
+
- client block
|
|
414
|
+
- issue/due date
|
|
415
|
+
- item table with designation and quantity only
|
|
416
|
+
- notes
|
|
417
|
+
|
|
418
|
+
Delivery notes do not render:
|
|
419
|
+
|
|
420
|
+
- unit price
|
|
421
|
+
- amount
|
|
422
|
+
- subtotal
|
|
423
|
+
- discount
|
|
424
|
+
- VAT
|
|
425
|
+
- total
|
|
426
|
+
- bank details
|
|
427
|
+
- payment terms block
|
|
428
|
+
|
|
429
|
+
## Examples
|
|
430
|
+
|
|
431
|
+
### Invoice with international transfer info
|
|
432
|
+
|
|
433
|
+
```ts
|
|
434
|
+
import { createInvoice } from "@ic-labs/invoice-kit";
|
|
435
|
+
|
|
436
|
+
const invoice = createInvoice({
|
|
437
|
+
number: "FAC-2026-0020",
|
|
438
|
+
seller: {
|
|
439
|
+
name: "IC Labs SARL",
|
|
440
|
+
addressLines: ["Casablanca"],
|
|
441
|
+
ice: "001234567890123",
|
|
442
|
+
if: "12345678",
|
|
443
|
+
rc: "56789"
|
|
444
|
+
},
|
|
445
|
+
client: {
|
|
446
|
+
name: "Noxel SAS",
|
|
447
|
+
addressLines: ["Paris"]
|
|
448
|
+
},
|
|
449
|
+
items: [
|
|
450
|
+
{
|
|
451
|
+
name: "ERP Deployment",
|
|
452
|
+
quantity: 3,
|
|
453
|
+
price: 1800,
|
|
454
|
+
unit: "jour"
|
|
455
|
+
}
|
|
456
|
+
],
|
|
457
|
+
vatRate: 0.2,
|
|
458
|
+
bankInfo: {
|
|
459
|
+
type: "international",
|
|
460
|
+
bankName: "BMCI Corporate",
|
|
461
|
+
holderName: "IC Labs SARL",
|
|
462
|
+
swiftCode: "BMCIMAMC",
|
|
463
|
+
iban: "MA64001122000001234567890123"
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Quote with local bank details
|
|
469
|
+
|
|
470
|
+
```ts
|
|
471
|
+
import { createQuote } from "@ic-labs/invoice-kit";
|
|
472
|
+
|
|
473
|
+
const quote = createQuote({
|
|
474
|
+
number: "DEV-2026-0012",
|
|
475
|
+
seller: {
|
|
476
|
+
name: "IC Labs SARL",
|
|
477
|
+
addressLines: ["Casablanca"]
|
|
478
|
+
},
|
|
479
|
+
client: {
|
|
480
|
+
name: "Client",
|
|
481
|
+
addressLines: ["Rabat"]
|
|
482
|
+
},
|
|
483
|
+
items: [
|
|
484
|
+
{
|
|
485
|
+
name: "Audit",
|
|
486
|
+
quantity: 1,
|
|
487
|
+
price: 1200
|
|
488
|
+
}
|
|
489
|
+
],
|
|
490
|
+
discounts: [{ type: "percentage", value: 0.05, label: "Remise devis" }],
|
|
491
|
+
bankInfo: {
|
|
492
|
+
type: "local",
|
|
493
|
+
bankName: "Attijariwafa Bank",
|
|
494
|
+
holderName: "IC Labs SARL",
|
|
495
|
+
rib: "007 810 000123456789012345"
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Delivery note without pricing
|
|
501
|
+
|
|
502
|
+
```ts
|
|
503
|
+
import { createDeliveryNote } from "@ic-labs/invoice-kit";
|
|
504
|
+
|
|
505
|
+
const deliveryNote = createDeliveryNote({
|
|
506
|
+
number: "BL-2026-0015",
|
|
507
|
+
seller: {
|
|
508
|
+
name: "IC Labs SARL",
|
|
509
|
+
addressLines: ["Casablanca"]
|
|
510
|
+
},
|
|
511
|
+
client: {
|
|
512
|
+
name: "Client",
|
|
513
|
+
addressLines: ["Rabat"]
|
|
514
|
+
},
|
|
515
|
+
items: [
|
|
516
|
+
{ name: "Laptop", quantity: 2, unit: "piece" },
|
|
517
|
+
{ name: "Mouse", quantity: 3, unit: "piece" }
|
|
518
|
+
],
|
|
519
|
+
notes: "Marchandise remise au responsable du site."
|
|
520
|
+
});
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## Development
|
|
524
|
+
|
|
525
|
+
Available scripts:
|
|
526
|
+
|
|
527
|
+
- `npm run typecheck`
|
|
528
|
+
- `npm run build`
|
|
529
|
+
- `npm run smoke`
|
|
530
|
+
|
|
531
|
+
## Publishing
|
|
532
|
+
|
|
533
|
+
The repository is configured for GitHub Actions based publishing.
|
|
534
|
+
|
|
535
|
+
Current release model:
|
|
536
|
+
|
|
537
|
+
- CI runs on pushes and pull requests
|
|
538
|
+
- publish workflow runs on pushes to `main`
|
|
539
|
+
- npm publish only happens if the current `package.json` version does not already exist on npm
|
|
540
|
+
|
|
541
|
+
That means a release requires a version bump before pushing to `main`.
|
|
542
|
+
|
|
543
|
+
## Limitations
|
|
544
|
+
|
|
545
|
+
- multi-page table layout is not implemented yet
|
|
546
|
+
- remote logos currently support PNG/JPEG only
|
|
547
|
+
- Arabic PDF output requires custom fonts
|
|
548
|
+
- delivery notes intentionally do not display financial values
|
|
549
|
+
|
|
550
|
+
## License
|
|
551
|
+
|
|
552
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BusinessDocumentData, PdfRenderOptions } from "./types.js";
|
|
2
|
+
export declare class BusinessDocument {
|
|
3
|
+
private readonly data;
|
|
4
|
+
constructor(data: BusinessDocumentData);
|
|
5
|
+
toJSON(): BusinessDocumentData;
|
|
6
|
+
toPDF(outputPathOrOptions?: string | PdfRenderOptions): Promise<Uint8Array>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/core/document.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGzE,qBAAa,gBAAgB;IACR,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,oBAAoB;IAEvD,MAAM,IAAI,oBAAoB;IAIxB,KAAK,CAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;CAazF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { renderDocumentPdf } from "../renderers/pdf.js";
|
|
3
|
+
export class BusinessDocument {
|
|
4
|
+
data;
|
|
5
|
+
constructor(data) {
|
|
6
|
+
this.data = data;
|
|
7
|
+
}
|
|
8
|
+
toJSON() {
|
|
9
|
+
return this.data;
|
|
10
|
+
}
|
|
11
|
+
async toPDF(outputPathOrOptions) {
|
|
12
|
+
const options = typeof outputPathOrOptions === "string"
|
|
13
|
+
? { outputPath: outputPathOrOptions }
|
|
14
|
+
: outputPathOrOptions;
|
|
15
|
+
const pdfBytes = await renderDocumentPdf(this.data, options);
|
|
16
|
+
if (options?.outputPath) {
|
|
17
|
+
await writeFile(options.outputPath, pdfBytes);
|
|
18
|
+
}
|
|
19
|
+
return pdfBytes;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/core/document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,OAAO,gBAAgB;IACS;IAApC,YAAoC,IAA0B;QAA1B,SAAI,GAAJ,IAAI,CAAsB;IAAG,CAAC;IAE3D,MAAM;QACX,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAEM,KAAK,CAAC,KAAK,CAAC,mBAA+C;QAChE,MAAM,OAAO,GAAG,OAAO,mBAAmB,KAAK,QAAQ;YACrD,CAAC,CAAC,EAAE,UAAU,EAAE,mBAAmB,EAAE;YACrC,CAAC,CAAC,mBAAmB,CAAC;QAExB,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAE7D,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC;YACxB,MAAM,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAChD,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BusinessDocument } from "./document.js";
|
|
2
|
+
import type { DocumentInput } from "./types.js";
|
|
3
|
+
export declare const createInvoice: (input: DocumentInput) => BusinessDocument;
|
|
4
|
+
export declare const createQuote: (input: DocumentInput) => BusinessDocument;
|
|
5
|
+
export declare const createPurchaseOrder: (input: DocumentInput) => BusinessDocument;
|
|
6
|
+
export declare const createDeliveryNote: (input: DocumentInput) => BusinessDocument;
|
|
7
|
+
//# sourceMappingURL=factories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factories.d.ts","sourceRoot":"","sources":["../../src/core/factories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAyB,aAAa,EAAgB,MAAM,YAAY,CAAC;AA6CrF,eAAO,MAAM,aAAa,UAHuB,aAAa,KAAG,gBAGjB,CAAC;AACjD,eAAO,MAAM,WAAW,UAJyB,aAAa,KAAG,gBAIrB,CAAC;AAC7C,eAAO,MAAM,mBAAmB,UALiB,aAAa,KAAG,gBAKL,CAAC;AAC7D,eAAO,MAAM,kBAAkB,UANkB,aAAa,KAAG,gBAMP,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { BusinessDocument } from "./document.js";
|
|
2
|
+
import { validateDocumentInput } from "./validation.js";
|
|
3
|
+
import { createTemplateDefinition } from "../templates/definitions.js";
|
|
4
|
+
import { getLabels } from "../locales/index.js";
|
|
5
|
+
import { computeLineItem, computeTotals, MOROCCO_DEFAULT_VAT_RATE } from "../utils/taxes.js";
|
|
6
|
+
const createBusinessDocument = (options) => {
|
|
7
|
+
validateDocumentInput(options);
|
|
8
|
+
const locale = options.locale ?? "fr-MA";
|
|
9
|
+
const template = createTemplateDefinition(options.type);
|
|
10
|
+
const isDeliveryNote = options.type === "deliveryNote";
|
|
11
|
+
const items = options.items.map(computeLineItem);
|
|
12
|
+
const discounts = isDeliveryNote ? [] : (options.discounts ?? []);
|
|
13
|
+
const vatRate = isDeliveryNote ? 0 : (options.vatRate ?? MOROCCO_DEFAULT_VAT_RATE);
|
|
14
|
+
const issueDate = options.issueDate ?? new Date();
|
|
15
|
+
const labels = getLabels(locale);
|
|
16
|
+
const data = {
|
|
17
|
+
type: options.type,
|
|
18
|
+
title: options.title ?? template.getTitle(locale) ?? labels.documentTitle,
|
|
19
|
+
number: options.number,
|
|
20
|
+
...(options.logo ? { logo: options.logo } : {}),
|
|
21
|
+
issueDate,
|
|
22
|
+
seller: options.seller,
|
|
23
|
+
client: options.client,
|
|
24
|
+
items,
|
|
25
|
+
currency: options.currency ?? "MAD",
|
|
26
|
+
vatRate,
|
|
27
|
+
discounts,
|
|
28
|
+
locale,
|
|
29
|
+
totals: computeTotals(items, discounts, vatRate)
|
|
30
|
+
};
|
|
31
|
+
return new BusinessDocument({
|
|
32
|
+
...data,
|
|
33
|
+
...(options.dueDate ? { dueDate: options.dueDate } : {}),
|
|
34
|
+
...(options.paymentTerms ? { paymentTerms: options.paymentTerms } : {}),
|
|
35
|
+
...(options.bankInfo ? { bankInfo: options.bankInfo } : {}),
|
|
36
|
+
...(options.notes ? { notes: options.notes } : {})
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
const withType = (type) => (input) => createBusinessDocument({ ...input, type });
|
|
40
|
+
export const createInvoice = withType("invoice");
|
|
41
|
+
export const createQuote = withType("quote");
|
|
42
|
+
export const createPurchaseOrder = withType("purchaseOrder");
|
|
43
|
+
export const createDeliveryNote = withType("deliveryNote");
|
|
44
|
+
//# sourceMappingURL=factories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factories.js","sourceRoot":"","sources":["../../src/core/factories.ts"],"names":[],"mappings":"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"}
|