@dloizides/invoice-core 0.0.0-stage → 0.1.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/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +42 -2
- package/dist/index.d.mts +399 -0
- package/dist/index.d.ts +399 -0
- package/dist/index.js +1032 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +982 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +70 -4
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 dloizides
|
|
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
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @dloizides/invoice-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Invoice logic for local-first apps: gap-free numbering, VAT rules, JSON layouts, PDFs with selectable Greek text, yearly schedules, credit notes and a quarter CSV export. Holds no file access: every function takes an `InvoiceStorage`.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createMemoryStorage, issueInvoice } from '@dloizides/invoice-core';
|
|
7
|
+
|
|
8
|
+
const storage = createMemoryStorage({ 'company.json': companyText, 'clients/acme.json': clientText });
|
|
9
|
+
const { number, pdfPath } = await issueInvoice(storage, {
|
|
10
|
+
clientId: 'acme', period: '2030-09', date: '2030-10-08', amounts: ['1234.50'], draft: false,
|
|
11
|
+
});
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Storage
|
|
15
|
+
|
|
16
|
+
Implement `InvoiceStorage` (`src/storage.ts`) for your platform: `readText`, `readBytes`, `list`, `writeNew` (must refuse an existing file with `StorageExistsError`), `replace`, `remove`, `withLock`. Number claims run inside `withLock(NUMBER_LOCK, ...)`, so a browser implementation should back it with Web Locks.
|
|
17
|
+
|
|
18
|
+
## Folder format
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
format.json {"version": 1}
|
|
22
|
+
company.json name, addressLines, regNo, vatNumber, bank, iban, bic, logo?, layout?
|
|
23
|
+
clients/<id>.json name, addressLines, taxLines, vatNumber, vatRule, paymentTermsDays, description, layout?
|
|
24
|
+
rules/<id>.json optional; overrides or adds to BUILT_IN_RULES
|
|
25
|
+
layouts/<name>.json optional; overrides or adds to BUILT_IN_LAYOUTS
|
|
26
|
+
schedules/<clientId>-<year>.json 12 months, each a draft until issued
|
|
27
|
+
invoices/<year>/<number>/invoice.json immutable record
|
|
28
|
+
invoices/<year>/<number>/<number>.pdf the PDF; never replaced
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`migrateLegacyFolder` moves `<year>/<number>.json` + `.pdf` into `invoices/`, never overwriting. Until then `nextNumber` counts both places.
|
|
32
|
+
|
|
33
|
+
## Layouts
|
|
34
|
+
|
|
35
|
+
A layout is plain data (`InvoiceLayout`): margins, sizes, three colours, title alignment. `parseLayout` refuses unknown fields, so a layout cannot carry a script. An unknown name is refused and named.
|
|
36
|
+
|
|
37
|
+
## Rendering
|
|
38
|
+
|
|
39
|
+
`renderInvoicePdf` uses pdfmake 0.2.12 and its bundled Roboto, loaded on first use. Pass `render` in the options to replace it.
|
|
40
|
+
|
|
41
|
+
## Numbers
|
|
42
|
+
|
|
43
|
+
Invoices number `2030-001`, credit notes `CN-2030-001`, each gap-free per year of their date. `issueDueMonths` dates each invoice on the day it issues; have your accountant confirm that suits your jurisdiction. Built-in VAT rules are not tax advice.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The data folder as the core sees it. Paths are relative, with forward slashes.
|
|
3
|
+
* Browser and Node implementations live outside this package.
|
|
4
|
+
*/
|
|
5
|
+
interface InvoiceStorage {
|
|
6
|
+
/** Returns the file as text, or null if it does not exist. */
|
|
7
|
+
readText(path: string): Promise<string | null>;
|
|
8
|
+
/** Returns the file as bytes, or null if it does not exist. */
|
|
9
|
+
readBytes(path: string): Promise<Uint8Array | null>;
|
|
10
|
+
/** Returns the names of a folder's direct children; empty if it does not exist. */
|
|
11
|
+
list(dir: string): Promise<string[]>;
|
|
12
|
+
/** Creates a file and its parents; throws StorageExistsError if the file exists. */
|
|
13
|
+
writeNew(path: string, data: string | Uint8Array): Promise<void>;
|
|
14
|
+
/** Creates or overwrites a file; never used on an issued record. */
|
|
15
|
+
replace(path: string, data: string | Uint8Array): Promise<void>;
|
|
16
|
+
/** Deletes a file; does nothing if it does not exist. */
|
|
17
|
+
remove(path: string): Promise<void>;
|
|
18
|
+
/** Runs work while holding the named lock; other holders of that name wait. */
|
|
19
|
+
withLock<T>(name: string, work: () => Promise<T>): Promise<T>;
|
|
20
|
+
}
|
|
21
|
+
/** Thrown by writeNew when the file already exists. */
|
|
22
|
+
declare class StorageExistsError extends Error {
|
|
23
|
+
constructor(path: string);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Whether an issued record is an invoice or a credit note. */
|
|
27
|
+
declare const enum InvoiceKind {
|
|
28
|
+
Invoice = "invoice",
|
|
29
|
+
CreditNote = "credit-note"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** One validation problem: a field path and what is wrong with it. */
|
|
33
|
+
interface FieldError {
|
|
34
|
+
path: string;
|
|
35
|
+
message: string;
|
|
36
|
+
}
|
|
37
|
+
/** The issuing company, read from company.json in the data folder. */
|
|
38
|
+
interface Company {
|
|
39
|
+
name: string;
|
|
40
|
+
addressLines: string[];
|
|
41
|
+
regNo: string;
|
|
42
|
+
vatNumber: string;
|
|
43
|
+
bank: string;
|
|
44
|
+
iban: string;
|
|
45
|
+
bic: string;
|
|
46
|
+
logo?: string;
|
|
47
|
+
template?: string;
|
|
48
|
+
layout?: string;
|
|
49
|
+
}
|
|
50
|
+
/** A client, read from clients/<id>.json in the data folder. */
|
|
51
|
+
interface Client {
|
|
52
|
+
name: string;
|
|
53
|
+
addressLines: string[];
|
|
54
|
+
taxLines: string[];
|
|
55
|
+
vatNumber: string;
|
|
56
|
+
vatRule: string;
|
|
57
|
+
paymentTermsDays: number | null;
|
|
58
|
+
description: string;
|
|
59
|
+
template?: string;
|
|
60
|
+
layout?: string;
|
|
61
|
+
}
|
|
62
|
+
/** A VAT treatment, built in or read from a rule file. */
|
|
63
|
+
interface VatRule {
|
|
64
|
+
id: string;
|
|
65
|
+
ratePercent: number;
|
|
66
|
+
labelEn: string;
|
|
67
|
+
labelEl: string;
|
|
68
|
+
rateTextEn: string;
|
|
69
|
+
rateTextEl: string;
|
|
70
|
+
noteEn: string;
|
|
71
|
+
noteEl: string;
|
|
72
|
+
source: string;
|
|
73
|
+
reviewedByAccountant?: boolean;
|
|
74
|
+
}
|
|
75
|
+
/** A line as entered: its own description and an amount such as 2952.50. */
|
|
76
|
+
interface InvoiceLine {
|
|
77
|
+
description: string;
|
|
78
|
+
amount: string;
|
|
79
|
+
}
|
|
80
|
+
/** A line ready to print and total. */
|
|
81
|
+
interface ResolvedLine {
|
|
82
|
+
description: string;
|
|
83
|
+
cents: number;
|
|
84
|
+
}
|
|
85
|
+
/** Totals in whole cents. */
|
|
86
|
+
interface Totals {
|
|
87
|
+
netCents: number;
|
|
88
|
+
vatCents: number;
|
|
89
|
+
totalCents: number;
|
|
90
|
+
}
|
|
91
|
+
/** The per-invoice request. Lines win over amounts, which use the client description. */
|
|
92
|
+
interface InvoiceRequest {
|
|
93
|
+
clientId: string;
|
|
94
|
+
period: string;
|
|
95
|
+
date: string;
|
|
96
|
+
amounts: string[];
|
|
97
|
+
lines?: InvoiceLine[];
|
|
98
|
+
template?: string;
|
|
99
|
+
layout?: string;
|
|
100
|
+
draft: boolean;
|
|
101
|
+
}
|
|
102
|
+
/** The three files an invoice is built from. */
|
|
103
|
+
interface Parties {
|
|
104
|
+
company: Company;
|
|
105
|
+
client: Client;
|
|
106
|
+
rule: VatRule;
|
|
107
|
+
}
|
|
108
|
+
/** Which schedule month an issued record came from. */
|
|
109
|
+
interface ScheduleRef {
|
|
110
|
+
id: string;
|
|
111
|
+
period: string;
|
|
112
|
+
}
|
|
113
|
+
/** The immutable record of an issued invoice or credit note. */
|
|
114
|
+
interface InvoiceRecord extends Parties {
|
|
115
|
+
kind: InvoiceKind;
|
|
116
|
+
number: string;
|
|
117
|
+
request: InvoiceRequest;
|
|
118
|
+
lines: ResolvedLine[];
|
|
119
|
+
totals: Totals;
|
|
120
|
+
schedule?: ScheduleRef;
|
|
121
|
+
creditFor?: string;
|
|
122
|
+
reason?: string;
|
|
123
|
+
}
|
|
124
|
+
/** Where an issued record and its PDF were saved. */
|
|
125
|
+
interface IssuedInvoice {
|
|
126
|
+
number: string;
|
|
127
|
+
pdfPath: string;
|
|
128
|
+
recordPath: string;
|
|
129
|
+
}
|
|
130
|
+
/** Thrown for any refusal; errors name each field. */
|
|
131
|
+
declare class InvoiceError extends Error {
|
|
132
|
+
readonly errors: FieldError[];
|
|
133
|
+
constructor(errors: FieldError[]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** What a renderer receives. A null number means a draft. */
|
|
137
|
+
interface RenderInput {
|
|
138
|
+
request: InvoiceRequest;
|
|
139
|
+
number: string | null;
|
|
140
|
+
parties: Parties;
|
|
141
|
+
lines?: ResolvedLine[];
|
|
142
|
+
kind?: InvoiceKind;
|
|
143
|
+
creditFor?: string;
|
|
144
|
+
reason?: string;
|
|
145
|
+
}
|
|
146
|
+
/** Everything a PDF prints, already formatted. */
|
|
147
|
+
interface InvoiceView {
|
|
148
|
+
draft: boolean;
|
|
149
|
+
creditNote: boolean;
|
|
150
|
+
number: string;
|
|
151
|
+
date: string;
|
|
152
|
+
dueDate: string;
|
|
153
|
+
periodEn: string;
|
|
154
|
+
periodEl: string;
|
|
155
|
+
company: {
|
|
156
|
+
name: string;
|
|
157
|
+
addressLines: string[];
|
|
158
|
+
regNo: string;
|
|
159
|
+
vatNumber: string;
|
|
160
|
+
bank: string;
|
|
161
|
+
iban: string;
|
|
162
|
+
bic: string;
|
|
163
|
+
};
|
|
164
|
+
client: {
|
|
165
|
+
name: string;
|
|
166
|
+
addressLines: string[];
|
|
167
|
+
taxLines: string[];
|
|
168
|
+
};
|
|
169
|
+
rows: {
|
|
170
|
+
description: string;
|
|
171
|
+
amount: string;
|
|
172
|
+
}[];
|
|
173
|
+
net: string;
|
|
174
|
+
vat: string;
|
|
175
|
+
total: string;
|
|
176
|
+
vatLabel: string;
|
|
177
|
+
noteEn: string;
|
|
178
|
+
noteEl: string;
|
|
179
|
+
creditFor?: string;
|
|
180
|
+
reason?: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** The lock every number claim holds, so two tabs never take one number. */
|
|
184
|
+
declare const NUMBER_LOCK = "invoice-number";
|
|
185
|
+
/** Renders PDF bytes; the default is renderInvoicePdf. */
|
|
186
|
+
type PdfRender = (input: RenderInput) => Promise<Uint8Array>;
|
|
187
|
+
/** Options for issuing; schedule marks which schedule month this came from. */
|
|
188
|
+
interface IssueOptions {
|
|
189
|
+
render?: PdfRender;
|
|
190
|
+
schedule?: ScheduleRef;
|
|
191
|
+
}
|
|
192
|
+
/** Issues a final invoice: renders first, then claims the number, so a failed render uses none. */
|
|
193
|
+
declare function issueInvoice(storage: InvoiceStorage, request: InvoiceRequest, options?: IssueOptions): Promise<IssuedInvoice>;
|
|
194
|
+
/** Renders a draft PDF that takes no number. */
|
|
195
|
+
declare function renderDraftPdf(storage: InvoiceStorage, request: InvoiceRequest, options?: IssueOptions): Promise<Uint8Array>;
|
|
196
|
+
/** Reads an issued record; a record from the old layout's shape gets its kind, lines and totals. */
|
|
197
|
+
declare function readInvoice(storage: InvoiceStorage, number: string): Promise<InvoiceRecord>;
|
|
198
|
+
/** Lists a year's issued invoices and credit notes; folders with no record are skipped. */
|
|
199
|
+
declare function listIssued(storage: InvoiceStorage, year: string): Promise<InvoiceRecord[]>;
|
|
200
|
+
/** Recreates the missing PDF of an issued record; never replaces an existing PDF. */
|
|
201
|
+
declare function reprintInvoice(storage: InvoiceStorage, number: string, options?: IssueOptions): Promise<{
|
|
202
|
+
number: string;
|
|
203
|
+
pdfPath: string;
|
|
204
|
+
}>;
|
|
205
|
+
|
|
206
|
+
/** What to credit. Without an amount the credit note covers all the invoice has left. */
|
|
207
|
+
interface CreditNoteRequest {
|
|
208
|
+
invoiceNumber: string;
|
|
209
|
+
date: string;
|
|
210
|
+
reason: string;
|
|
211
|
+
amount?: string;
|
|
212
|
+
}
|
|
213
|
+
/** Issues a credit note that references its invoice and takes its own gap-free CN- number. */
|
|
214
|
+
declare function issueCreditNote(storage: InvoiceStorage, input: CreditNoteRequest, options?: IssueOptions): Promise<IssuedInvoice>;
|
|
215
|
+
|
|
216
|
+
/** The accountant's CSV of one quarter's invoices and credit notes, by date; credit notes are negative. */
|
|
217
|
+
declare function exportQuarterCsv(storage: InvoiceStorage, year: string, quarter: number): Promise<string>;
|
|
218
|
+
|
|
219
|
+
/** The folder of an issued invoice or credit note. */
|
|
220
|
+
declare function invoiceDir(number: string): string;
|
|
221
|
+
/** The path of an issued record. */
|
|
222
|
+
declare function recordPath(number: string): string;
|
|
223
|
+
/** The path of an issued PDF. */
|
|
224
|
+
declare function pdfPath(number: string): string;
|
|
225
|
+
/** Creates format.json in a new folder and refuses a folder newer than this code. */
|
|
226
|
+
declare function ensureFormat(storage: InvoiceStorage): Promise<void>;
|
|
227
|
+
/** What a migration moved, and the numbers left in place because the target exists. */
|
|
228
|
+
interface MigrationResult {
|
|
229
|
+
moved: string[];
|
|
230
|
+
conflicts: string[];
|
|
231
|
+
}
|
|
232
|
+
/** Moves <year>/<number>.json + .pdf to invoices/<year>/<number>/. Never overwrites. */
|
|
233
|
+
declare function migrateLegacyFolder(storage: InvoiceStorage): Promise<MigrationResult>;
|
|
234
|
+
|
|
235
|
+
/** Where a layout places the document title. */
|
|
236
|
+
declare const enum LayoutAlign {
|
|
237
|
+
Left = "left",
|
|
238
|
+
Right = "right"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** The name a request uses when neither it, the client nor the company picks a layout. */
|
|
242
|
+
declare const DEFAULT_LAYOUT = "classic";
|
|
243
|
+
/** The look of a PDF: plain data only, so a layout cannot run anything. */
|
|
244
|
+
interface InvoiceLayout {
|
|
245
|
+
name: string;
|
|
246
|
+
pageMargin: number;
|
|
247
|
+
bodySize: number;
|
|
248
|
+
titleSize: number;
|
|
249
|
+
logoWidth: number;
|
|
250
|
+
accent: string;
|
|
251
|
+
muted: string;
|
|
252
|
+
band: string;
|
|
253
|
+
titleAlign: LayoutAlign;
|
|
254
|
+
}
|
|
255
|
+
/** The layouts every folder can use; a file in layouts/ overrides them by name. */
|
|
256
|
+
declare const BUILT_IN_LAYOUTS: {
|
|
257
|
+
readonly classic: {
|
|
258
|
+
readonly name: "classic";
|
|
259
|
+
readonly pageMargin: 40;
|
|
260
|
+
readonly bodySize: 9.5;
|
|
261
|
+
readonly titleSize: 22;
|
|
262
|
+
readonly logoWidth: 90;
|
|
263
|
+
readonly accent: "#1F3A5F";
|
|
264
|
+
readonly muted: "#666666";
|
|
265
|
+
readonly band: "#E4ECF5";
|
|
266
|
+
readonly titleAlign: LayoutAlign.Right;
|
|
267
|
+
};
|
|
268
|
+
readonly compact: {
|
|
269
|
+
readonly name: "compact";
|
|
270
|
+
readonly pageMargin: 28;
|
|
271
|
+
readonly bodySize: 8.5;
|
|
272
|
+
readonly titleSize: 16;
|
|
273
|
+
readonly logoWidth: 60;
|
|
274
|
+
readonly accent: "#2F4F4F";
|
|
275
|
+
readonly muted: "#5F5F5F";
|
|
276
|
+
readonly band: "#E8EFEE";
|
|
277
|
+
readonly titleAlign: LayoutAlign.Left;
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
/** Checks a parsed layout file; refuses unknown fields and anything but plain values. */
|
|
281
|
+
declare function parseLayout(raw: unknown): InvoiceLayout;
|
|
282
|
+
/** Finds a layout in the data folder, then the built-in set; an unknown name is refused and named. */
|
|
283
|
+
declare function resolveLayout(storage: InvoiceStorage, name: string): Promise<InvoiceLayout>;
|
|
284
|
+
/** The layout name in force: request, then client, then company, then the default. */
|
|
285
|
+
declare function selectLayoutName(request: InvoiceRequest, parties: Parties): string;
|
|
286
|
+
|
|
287
|
+
/** Fills {periodElAccusative} and {periodEn} in a client description. */
|
|
288
|
+
declare function expandDescription(description: string, period: string): string;
|
|
289
|
+
/** Turns a request into printable lines: its own lines, else amounts with the client description. */
|
|
290
|
+
declare function resolveLines(request: InvoiceRequest, client: Client): ResolvedLine[];
|
|
291
|
+
/** Sums lines in whole cents and adds VAT at the given rate. */
|
|
292
|
+
declare function computeTotals(lines: ResolvedLine[], ratePercent: number): Totals;
|
|
293
|
+
|
|
294
|
+
/** Creates an in-memory storage, for tests and previews. */
|
|
295
|
+
declare function createMemoryStorage(initial?: Record<string, string | Uint8Array>): InvoiceStorage;
|
|
296
|
+
|
|
297
|
+
/** Parses "2952" or "2952.50" into integer cents; returns null if malformed. */
|
|
298
|
+
declare function toCents(amount: string): number | null;
|
|
299
|
+
/** Formats cents in Greek style: 295200 becomes "2.952,00". */
|
|
300
|
+
declare function formatCents(cents: number): string;
|
|
301
|
+
/** Formats cents as a plain amount: -4000 becomes "-40.00". */
|
|
302
|
+
declare function centsToAmount(cents: number): string;
|
|
303
|
+
/** Computes VAT in whole cents, rounded half up. */
|
|
304
|
+
declare function vatCents(netCents: number, ratePercent: number): number;
|
|
305
|
+
|
|
306
|
+
/** Returns the next gap-free number for a year, such as 2030-002 or CN-2030-001. */
|
|
307
|
+
declare function nextNumber(storage: InvoiceStorage, year: string, kind?: InvoiceKind): Promise<string>;
|
|
308
|
+
/** Writes the record, refusing if that number was already issued. Issued records are never replaced. */
|
|
309
|
+
declare function claimInvoice(storage: InvoiceStorage, record: InvoiceRecord): Promise<string>;
|
|
310
|
+
|
|
311
|
+
/** Reads the request's company, client and VAT rule, refusing if any is missing. */
|
|
312
|
+
declare function loadParties(storage: InvoiceStorage, clientId: string): Promise<Parties>;
|
|
313
|
+
/** Checks a request and returns its parties; final mode also refuses blank required fields. */
|
|
314
|
+
declare function checkRequest(storage: InvoiceStorage, request: InvoiceRequest): Promise<Parties>;
|
|
315
|
+
|
|
316
|
+
/** The logo a PDF shows: an image data URL or SVG text. */
|
|
317
|
+
type PdfLogo = {
|
|
318
|
+
image: string;
|
|
319
|
+
} | {
|
|
320
|
+
svg: string;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
/** Reads the company logo for a PDF; PNG, JPG or SVG, refusing anything else or a missing file. */
|
|
324
|
+
declare function loadLogo(storage: InvoiceStorage, logo?: string): Promise<PdfLogo | undefined>;
|
|
325
|
+
/** Renders an invoice or credit note to PDF bytes with selectable Greek text. Works in browsers and Node. */
|
|
326
|
+
declare function renderInvoicePdf(storage: InvoiceStorage, input: RenderInput): Promise<Uint8Array>;
|
|
327
|
+
|
|
328
|
+
/** Period texts for a YYYY-MM period. */
|
|
329
|
+
declare function periodTexts(period: string): {
|
|
330
|
+
en: string;
|
|
331
|
+
el: string;
|
|
332
|
+
elAccusative: string;
|
|
333
|
+
};
|
|
334
|
+
/** Formats YYYY-MM-DD as DD/MM/YYYY. */
|
|
335
|
+
declare function displayDate(iso: string): string;
|
|
336
|
+
/** Adds days to a YYYY-MM-DD date. */
|
|
337
|
+
declare function addDays(iso: string, days: number): string;
|
|
338
|
+
|
|
339
|
+
/** The built-in VAT rules by id; a rule file in the data folder overrides them. Not tax advice. */
|
|
340
|
+
declare const BUILT_IN_RULES: ReadonlyMap<string, VatRule>;
|
|
341
|
+
|
|
342
|
+
/** One month of a schedule: a draft until number is set. */
|
|
343
|
+
interface ScheduleMonth {
|
|
344
|
+
period: string;
|
|
345
|
+
lines: InvoiceLine[];
|
|
346
|
+
number: string | null;
|
|
347
|
+
issuedOn: string | null;
|
|
348
|
+
}
|
|
349
|
+
/** A client's yearly schedule, stored in schedules/<id>.json. */
|
|
350
|
+
interface InvoiceSchedule {
|
|
351
|
+
id: string;
|
|
352
|
+
clientId: string;
|
|
353
|
+
year: string;
|
|
354
|
+
months: ScheduleMonth[];
|
|
355
|
+
}
|
|
356
|
+
/** What to prepare: a client and the amount each month starts with. */
|
|
357
|
+
interface PrepareScheduleRequest {
|
|
358
|
+
clientId: string;
|
|
359
|
+
year: string;
|
|
360
|
+
monthlyAmount: string;
|
|
361
|
+
}
|
|
362
|
+
/** The clock and renderer for issuing due months. */
|
|
363
|
+
interface IssueDueOptions {
|
|
364
|
+
today: string;
|
|
365
|
+
render?: PdfRender;
|
|
366
|
+
}
|
|
367
|
+
/** What issuing due months did; a failure stops the run and leaves the rest as drafts. */
|
|
368
|
+
interface IssueDueResult {
|
|
369
|
+
issued: {
|
|
370
|
+
period: string;
|
|
371
|
+
number: string;
|
|
372
|
+
}[];
|
|
373
|
+
failed: {
|
|
374
|
+
period: string;
|
|
375
|
+
errors: FieldError[];
|
|
376
|
+
} | null;
|
|
377
|
+
remaining: string[];
|
|
378
|
+
}
|
|
379
|
+
/** Prepares 12 drafts with no numbers from a client and a monthly amount; a second call for that year is refused. */
|
|
380
|
+
declare function prepareYearSchedule(storage: InvoiceStorage, request: PrepareScheduleRequest): Promise<InvoiceSchedule>;
|
|
381
|
+
/** Reads a schedule by id. */
|
|
382
|
+
declare function readSchedule(storage: InvoiceStorage, id: string): Promise<InvoiceSchedule>;
|
|
383
|
+
/** Changes a draft month's lines; an issued month is refused and named. */
|
|
384
|
+
declare function editScheduleMonth(storage: InvoiceStorage, id: string, period: string, lines: InvoiceLine[]): Promise<InvoiceSchedule>;
|
|
385
|
+
/** Issues finished months in order, each with its own number, dated today; stops at the first failure. */
|
|
386
|
+
declare function issueDueMonths(storage: InvoiceStorage, id: string, options: IssueDueOptions): Promise<IssueDueResult>;
|
|
387
|
+
|
|
388
|
+
/** True for an empty value or a placeholder such as CY[____]. */
|
|
389
|
+
declare function isBlank(value: unknown): boolean;
|
|
390
|
+
/** True for a real calendar date written as YYYY-MM-DD. */
|
|
391
|
+
declare function isRealDate(date: string): boolean;
|
|
392
|
+
/** Checks each line's amount; a description may stay blank until final. */
|
|
393
|
+
declare function validateLines(lines: InvoiceLine[]): FieldError[];
|
|
394
|
+
/** Checks the request shape; these errors apply to drafts too. */
|
|
395
|
+
declare function validateRequest(req: InvoiceRequest): FieldError[];
|
|
396
|
+
/** Lists required fields still blank; a final invoice is refused if any are. */
|
|
397
|
+
declare function missingForFinal(company: Company, client: Client, lines?: InvoiceLine[]): FieldError[];
|
|
398
|
+
|
|
399
|
+
export { BUILT_IN_LAYOUTS, BUILT_IN_RULES, type Client, type Company, type CreditNoteRequest, DEFAULT_LAYOUT, type FieldError, InvoiceError, InvoiceKind, type InvoiceLayout, type InvoiceLine, type InvoiceRecord, type InvoiceRequest, type InvoiceSchedule, type InvoiceStorage, type InvoiceView, type IssueDueOptions, type IssueDueResult, type IssueOptions, type IssuedInvoice, LayoutAlign, type MigrationResult, NUMBER_LOCK, type Parties, type PdfRender, type PrepareScheduleRequest, type RenderInput, type ResolvedLine, type ScheduleMonth, type ScheduleRef, StorageExistsError, type Totals, type VatRule, addDays, centsToAmount, checkRequest, claimInvoice, computeTotals, createMemoryStorage, displayDate, editScheduleMonth, ensureFormat, expandDescription, exportQuarterCsv, formatCents, invoiceDir, isBlank, isRealDate, issueCreditNote, issueDueMonths, issueInvoice, listIssued, loadLogo, loadParties, migrateLegacyFolder, missingForFinal, nextNumber, parseLayout, pdfPath, periodTexts, prepareYearSchedule, readInvoice, readSchedule, recordPath, renderDraftPdf, renderInvoicePdf, reprintInvoice, resolveLayout, resolveLines, selectLayoutName, toCents, validateLines, validateRequest, vatCents };
|