@ekwo-ai/core 0.4.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 +76 -0
- package/dist/books/backend.d.ts +118 -0
- package/dist/books/backend.d.ts.map +1 -0
- package/dist/books/backend.js +69 -0
- package/dist/books/backend.js.map +1 -0
- package/dist/books/columns.d.ts +68 -0
- package/dist/books/columns.d.ts.map +1 -0
- package/dist/books/columns.js +466 -0
- package/dist/books/columns.js.map +1 -0
- package/dist/books/contacts.d.ts +43 -0
- package/dist/books/contacts.d.ts.map +1 -0
- package/dist/books/contacts.js +67 -0
- package/dist/books/contacts.js.map +1 -0
- package/dist/books/documents.d.ts +87 -0
- package/dist/books/documents.d.ts.map +1 -0
- package/dist/books/documents.js +413 -0
- package/dist/books/documents.js.map +1 -0
- package/dist/books/format.d.ts +21 -0
- package/dist/books/format.d.ts.map +1 -0
- package/dist/books/format.js +49 -0
- package/dist/books/format.js.map +1 -0
- package/dist/books/index.d.ts +18 -0
- package/dist/books/index.d.ts.map +1 -0
- package/dist/books/index.js +18 -0
- package/dist/books/index.js.map +1 -0
- package/dist/books/payments.d.ts +57 -0
- package/dist/books/payments.d.ts.map +1 -0
- package/dist/books/payments.js +255 -0
- package/dist/books/payments.js.map +1 -0
- package/dist/books/rounding.d.ts +25 -0
- package/dist/books/rounding.d.ts.map +1 -0
- package/dist/books/rounding.js +35 -0
- package/dist/books/rounding.js.map +1 -0
- package/dist/books/shared.d.ts +33 -0
- package/dist/books/shared.d.ts.map +1 -0
- package/dist/books/shared.js +98 -0
- package/dist/books/shared.js.map +1 -0
- package/dist/client.d.ts +73 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +138 -0
- package/dist/client.js.map +1 -0
- package/dist/fec.d.ts +14 -0
- package/dist/fec.d.ts.map +1 -0
- package/dist/fec.js +13 -0
- package/dist/fec.js.map +1 -0
- package/dist/identity.d.ts +46 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +62 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/refusal.d.ts +27 -0
- package/dist/refusal.d.ts.map +1 -0
- package/dist/refusal.js +38 -0
- package/dist/refusal.js.map +1 -0
- package/dist/schema.d.ts +23 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +42 -0
- package/dist/schema.js.map +1 -0
- package/dist/types.d.ts +466 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +170 -0
- package/dist/types.js.map +1 -0
- package/package.json +34 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/books/documents.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documents: drafts and their lines, and the one call that books them.
|
|
3
|
+
*
|
|
4
|
+
* One rule holds everywhere here: the ledger is written by the schema, never
|
|
5
|
+
* by this package. `post_document` carries the accounting rules — balance,
|
|
6
|
+
* numbering, period locks, the counterpart that is the difference of
|
|
7
|
+
* everything else — and this file calls it. Nothing here inserts an `entries`
|
|
8
|
+
* or an `entry_lines` row. Direct inserts are for the objects a person types:
|
|
9
|
+
* draft documents and their lines.
|
|
10
|
+
*/
|
|
11
|
+
import { BooksError } from './backend.js';
|
|
12
|
+
import * as columns from './columns.js';
|
|
13
|
+
import { amountIn, moneyFields } from './format.js';
|
|
14
|
+
import { companyCurrency, createdBefore, idsByCode, namesOf, only, onlyVisible } from './shared.js';
|
|
15
|
+
export const DOC_TYPES = [
|
|
16
|
+
'sale_invoice',
|
|
17
|
+
'sale_credit_note',
|
|
18
|
+
'sale_quote',
|
|
19
|
+
'purchase_invoice',
|
|
20
|
+
'purchase_credit_note',
|
|
21
|
+
'purchase_order',
|
|
22
|
+
];
|
|
23
|
+
export const DOCUMENT_STATES = ['draft', 'posted', 'cancelled'];
|
|
24
|
+
export const PAYMENT_STATES = ['not_paid', 'partially_paid', 'paid', 'overpaid', 'reversed'];
|
|
25
|
+
export async function createDocument(backend, args) {
|
|
26
|
+
const resolved = await resolveLineInputs(backend, args.company_id, args.doc_type, args.lines);
|
|
27
|
+
// Created before under this reference: the answer is that document. A
|
|
28
|
+
// creation is two inserts, the header and then the lines, so a connection
|
|
29
|
+
// that dropped between them left a draft with none — which is finished here
|
|
30
|
+
// rather than handed back empty.
|
|
31
|
+
const before = await createdBefore(backend, 'documents', args.company_id, args.client_ref, ['id', 'state']);
|
|
32
|
+
if (before !== undefined) {
|
|
33
|
+
const id = before['id'];
|
|
34
|
+
const held = await backend.select({
|
|
35
|
+
table: 'document_lines',
|
|
36
|
+
columns: ['id'],
|
|
37
|
+
where: [{ column: 'document_id', op: 'eq', value: id }],
|
|
38
|
+
limit: 1,
|
|
39
|
+
});
|
|
40
|
+
if (held.length === 0 && before['state'] === 'draft')
|
|
41
|
+
await insertLines(backend, args.company_id, id, resolved);
|
|
42
|
+
return { ...(await getDocument(backend, { document_id: id })), replayed: true };
|
|
43
|
+
}
|
|
44
|
+
const currency = args.currency_code ?? (await companyCurrency(backend, args.company_id));
|
|
45
|
+
const document = only(await backend.insert('documents', [
|
|
46
|
+
{
|
|
47
|
+
company_id: args.company_id,
|
|
48
|
+
doc_type: args.doc_type,
|
|
49
|
+
contact_id: args.contact_id,
|
|
50
|
+
document_date: args.document_date,
|
|
51
|
+
due_date: args.due_date ?? null,
|
|
52
|
+
accounting_date: args.accounting_date ?? null,
|
|
53
|
+
number: args.number ?? null,
|
|
54
|
+
supplier_reference: args.supplier_reference ?? null,
|
|
55
|
+
currency_code: currency,
|
|
56
|
+
journal_id: args.journal_id ?? null,
|
|
57
|
+
payment_reference: args.payment_reference ?? null,
|
|
58
|
+
client_ref: args.client_ref ?? null,
|
|
59
|
+
},
|
|
60
|
+
], ['id']), 'the document could not be created');
|
|
61
|
+
await insertLines(backend, args.company_id, document['id'], resolved);
|
|
62
|
+
return getDocument(backend, { document_id: document['id'] });
|
|
63
|
+
}
|
|
64
|
+
function isSale(docType) {
|
|
65
|
+
return docType.startsWith('sale_');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Turns the lines a model wrote into the rows the schema takes.
|
|
69
|
+
*
|
|
70
|
+
* Two things happen here and nowhere else. Codes become ids — one query per
|
|
71
|
+
* table, so twenty lines are three round trips and not sixty. And a product
|
|
72
|
+
* fills in what the line left out: the text, the description, the unit, the
|
|
73
|
+
* price and the tax. Those are *pre-fills*, in the sense an accounting
|
|
74
|
+
* package has always meant: the line is what is invoiced, and everything the
|
|
75
|
+
* caller gave wins over everything the catalogue says.
|
|
76
|
+
*
|
|
77
|
+
* The account is deliberately not resolved here. The database does it, in
|
|
78
|
+
* `resolve_line_account`, because a product line with no account is refused
|
|
79
|
+
* by a check constraint — so a null can only mean "resolve it", and every
|
|
80
|
+
* client has to get the same answer. A null tax is the opposite: it means no
|
|
81
|
+
* tax at all, which is why it is filled from the product only when the line
|
|
82
|
+
* named none.
|
|
83
|
+
*/
|
|
84
|
+
async function resolveLineInputs(backend, company, docType, lines) {
|
|
85
|
+
const [accounts, taxes, products] = await Promise.all([
|
|
86
|
+
idsByCode(backend, 'accounts', company, lines.map((line) => line.account_code).filter((code) => typeof code === 'string')),
|
|
87
|
+
idsByCode(backend, 'taxes', company, lines.map((line) => line.tax_code).filter((code) => typeof code === 'string')),
|
|
88
|
+
productsFor(backend, company, lines),
|
|
89
|
+
]);
|
|
90
|
+
const sale = isSale(docType);
|
|
91
|
+
return lines.map((line, index) => {
|
|
92
|
+
const productId = line.product_id ?? (line.product_code === undefined ? null : products.byCode.get(line.product_code)?.['id']);
|
|
93
|
+
const product = productId === null || productId === undefined ? undefined : products.byId.get(productId);
|
|
94
|
+
if (productId !== null && productId !== undefined && product === undefined) {
|
|
95
|
+
throw new BooksError(`unknown_product: no product ${productId} in this company. search_products says what exists; a product of another company is invisible here, not merely refused.`);
|
|
96
|
+
}
|
|
97
|
+
const name = line.name ?? product?.['name'];
|
|
98
|
+
if (name === undefined) {
|
|
99
|
+
throw new BooksError(`missing_line_name: line ${index + 1} has no name and no product to take one from. A line has to say what is being billed.`);
|
|
100
|
+
}
|
|
101
|
+
const catalogPrice = product?.[sale ? 'sale_price' : 'purchase_price'];
|
|
102
|
+
const unitPrice = line.unit_price ?? (typeof catalogPrice === 'string' ? catalogPrice : undefined);
|
|
103
|
+
if (unitPrice === undefined) {
|
|
104
|
+
throw new BooksError(`missing_unit_price: line ${index + 1} ("${name}") has no price, and ${product === undefined
|
|
105
|
+
? 'no product to take one from'
|
|
106
|
+
: `product ${String(product['code'])} has no ${sale ? 'sale' : 'purchase'} price`}.`);
|
|
107
|
+
}
|
|
108
|
+
const productTax = product?.[sale ? 'sale_tax_id' : 'purchase_tax_id'];
|
|
109
|
+
return {
|
|
110
|
+
product_id: productId ?? null,
|
|
111
|
+
name,
|
|
112
|
+
description: line.description ?? (product?.['description'] ?? null),
|
|
113
|
+
unit_code: line.unit_code ?? (product?.['unit_code'] ?? 'C62'),
|
|
114
|
+
unit_price: amountIn(unitPrice),
|
|
115
|
+
quantity: amountIn(line.quantity ?? 1),
|
|
116
|
+
discount_percent: amountIn(line.discount_percent ?? 0),
|
|
117
|
+
account_id: line.account_id ?? (line.account_code === undefined ? null : (accounts.get(line.account_code) ?? null)),
|
|
118
|
+
tax_id: line.tax_id ??
|
|
119
|
+
(line.tax_code === undefined
|
|
120
|
+
? (typeof productTax === 'string' ? productTax : null)
|
|
121
|
+
: (taxes.get(line.tax_code) ?? null)),
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** The products named by a set of lines, by id and by code, in one query each. */
|
|
126
|
+
async function productsFor(backend, company, lines) {
|
|
127
|
+
const ids = [...new Set(lines.map((line) => line.product_id).filter((id) => typeof id === 'string'))];
|
|
128
|
+
const codes = [
|
|
129
|
+
...new Set(lines.map((line) => line.product_code).filter((code) => typeof code === 'string')),
|
|
130
|
+
];
|
|
131
|
+
if (ids.length === 0 && codes.length === 0)
|
|
132
|
+
return { byId: new Map(), byCode: new Map() };
|
|
133
|
+
const where = [{ column: 'company_id', op: 'eq', value: company }];
|
|
134
|
+
const rows = ids.length > 0 && codes.length > 0
|
|
135
|
+
? [
|
|
136
|
+
...(await backend.select({
|
|
137
|
+
table: 'products',
|
|
138
|
+
columns: columns.PRODUCT,
|
|
139
|
+
where: [...where, { column: 'id', op: 'in', value: ids }],
|
|
140
|
+
})),
|
|
141
|
+
...(await backend.select({
|
|
142
|
+
table: 'products',
|
|
143
|
+
columns: columns.PRODUCT,
|
|
144
|
+
where: [...where, { column: 'code', op: 'in', value: codes }],
|
|
145
|
+
})),
|
|
146
|
+
]
|
|
147
|
+
: await backend.select({
|
|
148
|
+
table: 'products',
|
|
149
|
+
columns: columns.PRODUCT,
|
|
150
|
+
where: [
|
|
151
|
+
...where,
|
|
152
|
+
ids.length > 0
|
|
153
|
+
? { column: 'id', op: 'in', value: ids }
|
|
154
|
+
: { column: 'code', op: 'in', value: codes },
|
|
155
|
+
],
|
|
156
|
+
});
|
|
157
|
+
const byId = new Map(rows.map((row) => [row['id'], row]));
|
|
158
|
+
const byCode = new Map(rows.map((row) => [row['code'], row]));
|
|
159
|
+
for (const code of codes) {
|
|
160
|
+
if (!byCode.has(code)) {
|
|
161
|
+
throw new BooksError(`unknown_product_code: no product "${code}" in this company. search_products says what exists, and create_product adds one.`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { byId, byCode };
|
|
165
|
+
}
|
|
166
|
+
async function insertLines(backend, company, documentId, lines, after = 0) {
|
|
167
|
+
const rows = lines.map((line, index) => ({
|
|
168
|
+
document_id: documentId,
|
|
169
|
+
company_id: company,
|
|
170
|
+
sequence: after + (index + 1) * 10,
|
|
171
|
+
line_type: 'product',
|
|
172
|
+
...line,
|
|
173
|
+
}));
|
|
174
|
+
await backend.insert('document_lines', rows, ['id']);
|
|
175
|
+
}
|
|
176
|
+
/** The draft a line may be added to or replaced on, or the refusal. */
|
|
177
|
+
async function draftOf(backend, documentId) {
|
|
178
|
+
const document = only(await backend.select({
|
|
179
|
+
table: 'documents',
|
|
180
|
+
columns: ['id', 'company_id', 'state', 'doc_type'],
|
|
181
|
+
where: [{ column: 'id', op: 'eq', value: documentId }],
|
|
182
|
+
}), `document ${documentId}`);
|
|
183
|
+
if (document['state'] !== 'draft') {
|
|
184
|
+
throw new BooksError(`document_not_draft: document ${documentId} is ${String(document['state'])}. A posted document is not edited; correct it with a credit note.`);
|
|
185
|
+
}
|
|
186
|
+
return document;
|
|
187
|
+
}
|
|
188
|
+
/** One more line at the end of a draft. The lines already there are untouched. */
|
|
189
|
+
export async function addDocumentLine(backend, args) {
|
|
190
|
+
const document = await draftOf(backend, args.document_id);
|
|
191
|
+
const company = document['company_id'];
|
|
192
|
+
const [resolved] = await resolveLineInputs(backend, company, document['doc_type'], [args.line]);
|
|
193
|
+
const last = await backend.select({
|
|
194
|
+
table: 'document_lines',
|
|
195
|
+
columns: ['sequence'],
|
|
196
|
+
where: [{ column: 'document_id', op: 'eq', value: args.document_id }],
|
|
197
|
+
order: [{ column: 'sequence', ascending: false }],
|
|
198
|
+
limit: 1,
|
|
199
|
+
});
|
|
200
|
+
await insertLines(backend, company, args.document_id, [resolved], last[0]?.sequence ?? 0);
|
|
201
|
+
return getDocument(backend, { document_id: args.document_id });
|
|
202
|
+
}
|
|
203
|
+
export async function updateDocumentLines(backend, args) {
|
|
204
|
+
const document = await draftOf(backend, args.document_id);
|
|
205
|
+
const company = document['company_id'];
|
|
206
|
+
const resolved = await resolveLineInputs(backend, company, document['doc_type'], args.lines);
|
|
207
|
+
await backend.remove('document_lines', [{ column: 'document_id', op: 'eq', value: args.document_id }]);
|
|
208
|
+
await insertLines(backend, company, args.document_id, resolved);
|
|
209
|
+
return getDocument(backend, { document_id: args.document_id });
|
|
210
|
+
}
|
|
211
|
+
export async function postDocument(backend, args) {
|
|
212
|
+
if (args.dry_run === true) {
|
|
213
|
+
// The database posts for real and takes it back, so what comes out is
|
|
214
|
+
// what posting would write and a refusal is the one posting would give.
|
|
215
|
+
const rehearsed = only(await backend.rpc('rehearse_post_document', { p_document_id: args.document_id }), `document ${args.document_id} produced no entry`);
|
|
216
|
+
return {
|
|
217
|
+
dry_run: true,
|
|
218
|
+
entry: rehearsed['entry'] ?? null,
|
|
219
|
+
entry_lines: rehearsed['entry_lines'] ?? [],
|
|
220
|
+
note: 'Nothing was written. The number is the one the entry would take now; somebody posting first takes it instead.',
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const entries = await backend.rpc('post_document', { p_document_id: args.document_id });
|
|
224
|
+
const entry = only(moneyFields(entries, ['total_debit', 'total_credit']), `document ${args.document_id} produced no entry`);
|
|
225
|
+
const lines = await backend.select({
|
|
226
|
+
table: 'entry_lines',
|
|
227
|
+
columns: columns.ENTRY_LINE,
|
|
228
|
+
where: [{ column: 'entry_id', op: 'eq', value: entry['id'] }],
|
|
229
|
+
order: [{ column: 'sequence' }],
|
|
230
|
+
});
|
|
231
|
+
const accounts = await backend.select({
|
|
232
|
+
table: 'accounts',
|
|
233
|
+
columns: ['id', 'code', 'name'],
|
|
234
|
+
where: [
|
|
235
|
+
{
|
|
236
|
+
column: 'id',
|
|
237
|
+
op: 'in',
|
|
238
|
+
value: lines.map((line) => line['account_id']).filter((id) => typeof id === 'string'),
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
});
|
|
242
|
+
const byId = new Map(accounts.map((account) => [account.id, account]));
|
|
243
|
+
return {
|
|
244
|
+
entry,
|
|
245
|
+
entry_lines: lines.map((line) => ({ ...line, account: byId.get(line['account_id']) ?? null })),
|
|
246
|
+
note: 'The document is posted and carries the entry number. Nothing here can be unposted; a mistake is corrected with a credit note.',
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
export async function listDocuments(backend, args) {
|
|
250
|
+
const where = [{ column: 'company_id', op: 'eq', value: args.company_id }];
|
|
251
|
+
if (args.doc_type !== undefined)
|
|
252
|
+
where.push({ column: 'doc_type', op: 'eq', value: args.doc_type });
|
|
253
|
+
if (args.state !== undefined)
|
|
254
|
+
where.push({ column: 'state', op: 'eq', value: args.state });
|
|
255
|
+
if (args.unpaid === true) {
|
|
256
|
+
if (args.state === undefined)
|
|
257
|
+
where.push({ column: 'state', op: 'eq', value: 'posted' });
|
|
258
|
+
where.push({ column: 'payment_state', op: 'in', value: ['not_paid', 'partially_paid'] });
|
|
259
|
+
}
|
|
260
|
+
if (args.payment_state !== undefined)
|
|
261
|
+
where.push({ column: 'payment_state', op: 'eq', value: args.payment_state });
|
|
262
|
+
if (args.contact_id !== undefined)
|
|
263
|
+
where.push({ column: 'contact_id', op: 'eq', value: args.contact_id });
|
|
264
|
+
if (args.from !== undefined)
|
|
265
|
+
where.push({ column: 'document_date', op: 'gte', value: args.from });
|
|
266
|
+
if (args.to !== undefined)
|
|
267
|
+
where.push({ column: 'document_date', op: 'lte', value: args.to });
|
|
268
|
+
const documents = await backend.select({
|
|
269
|
+
table: 'documents',
|
|
270
|
+
columns: columns.DOCUMENT,
|
|
271
|
+
where,
|
|
272
|
+
order: [{ column: 'document_date', ascending: false }],
|
|
273
|
+
limit: args.limit ?? 50,
|
|
274
|
+
});
|
|
275
|
+
const names = await namesOf(backend, 'contacts', documents.map((doc) => doc['contact_id']));
|
|
276
|
+
return {
|
|
277
|
+
documents: documents.map((doc) => ({
|
|
278
|
+
...doc,
|
|
279
|
+
contact_name: names[doc['contact_id']] ?? null,
|
|
280
|
+
})),
|
|
281
|
+
count: documents.length,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
export async function getDocument(backend, args) {
|
|
285
|
+
const document = onlyVisible(await backend.select({
|
|
286
|
+
table: 'documents',
|
|
287
|
+
columns: columns.DOCUMENT,
|
|
288
|
+
where: [{ column: 'id', op: 'eq', value: args.document_id }],
|
|
289
|
+
}), `document ${args.document_id}`);
|
|
290
|
+
const lines = await backend.select({
|
|
291
|
+
table: 'document_lines',
|
|
292
|
+
columns: columns.DOCUMENT_LINE,
|
|
293
|
+
where: [{ column: 'document_id', op: 'eq', value: args.document_id }],
|
|
294
|
+
order: [{ column: 'sequence' }],
|
|
295
|
+
});
|
|
296
|
+
const [contact, accounts, taxes] = await Promise.all([
|
|
297
|
+
namesOf(backend, 'contacts', [document['contact_id']]),
|
|
298
|
+
backend.select({
|
|
299
|
+
table: 'accounts',
|
|
300
|
+
columns: ['id', 'code', 'name'],
|
|
301
|
+
where: [
|
|
302
|
+
{
|
|
303
|
+
column: 'id',
|
|
304
|
+
op: 'in',
|
|
305
|
+
value: lines.map((line) => line['account_id']).filter((id) => typeof id === 'string'),
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
}),
|
|
309
|
+
backend.select({
|
|
310
|
+
table: 'taxes',
|
|
311
|
+
columns: ['id', 'code', 'amount::text'],
|
|
312
|
+
where: [
|
|
313
|
+
{
|
|
314
|
+
column: 'id',
|
|
315
|
+
op: 'in',
|
|
316
|
+
value: lines.map((line) => line['tax_id']).filter((id) => typeof id === 'string'),
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
}),
|
|
320
|
+
]);
|
|
321
|
+
const accountById = new Map(accounts.map((account) => [account.id, account]));
|
|
322
|
+
const taxById = new Map(taxes.map((tax) => [tax.id, tax]));
|
|
323
|
+
const entryId = document['entry_id'];
|
|
324
|
+
let entry = null;
|
|
325
|
+
let entryLines = [];
|
|
326
|
+
if (typeof entryId === 'string') {
|
|
327
|
+
entry = (await backend.select({
|
|
328
|
+
table: 'entries',
|
|
329
|
+
columns: columns.ENTRY,
|
|
330
|
+
where: [{ column: 'id', op: 'eq', value: entryId }],
|
|
331
|
+
}))[0];
|
|
332
|
+
entryLines = await backend.select({
|
|
333
|
+
table: 'entry_lines',
|
|
334
|
+
columns: columns.ENTRY_LINE,
|
|
335
|
+
where: [{ column: 'entry_id', op: 'eq', value: entryId }],
|
|
336
|
+
order: [{ column: 'sequence' }],
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
const ledgerAccounts = await namesOf(backend, 'accounts', entryLines.map((line) => line['account_id']));
|
|
340
|
+
// What the law of the document's country asks of it. The mentions come from
|
|
341
|
+
// the view, which already decided which of them apply from the treatments of
|
|
342
|
+
// the taxes on the lines; the rules come from the country model, reached
|
|
343
|
+
// through the company's fiscal country — the country whose VAT applies, not
|
|
344
|
+
// the address. Both are null-tolerant: a country that has said nothing
|
|
345
|
+
// returns nothing, and the caller is never handed another country's answer.
|
|
346
|
+
const [mentions, headers] = await Promise.all([
|
|
347
|
+
backend.select({
|
|
348
|
+
table: 'document_legal_mentions',
|
|
349
|
+
columns: columns.DOCUMENT_LEGAL_MENTION,
|
|
350
|
+
where: [{ column: 'document_id', op: 'eq', value: args.document_id }],
|
|
351
|
+
order: [{ column: 'sequence' }],
|
|
352
|
+
}),
|
|
353
|
+
backend.select({
|
|
354
|
+
table: 'document_header',
|
|
355
|
+
columns: columns.DOCUMENT_HEADER,
|
|
356
|
+
where: [{ column: 'document_id', op: 'eq', value: args.document_id }],
|
|
357
|
+
}),
|
|
358
|
+
]);
|
|
359
|
+
const header = headers[0] ?? null;
|
|
360
|
+
// The country rules used to be fetched here, company then country_defaults,
|
|
361
|
+
// which is what `document_header` now does in one read. They keep their own
|
|
362
|
+
// key in the answer because that is what a caller asks for by name — but
|
|
363
|
+
// they are a slice of the header and not a second query.
|
|
364
|
+
const countryRules = header === null
|
|
365
|
+
? null
|
|
366
|
+
: Object.fromEntries(columns.COUNTRY_DOCUMENT_RULES.map((column) => {
|
|
367
|
+
const name = column.split('::')[0];
|
|
368
|
+
return [name, header[name] ?? null];
|
|
369
|
+
}));
|
|
370
|
+
return {
|
|
371
|
+
document: { ...document, contact_name: contact[document['contact_id']] ?? null },
|
|
372
|
+
lines: lines.map((line) => ({
|
|
373
|
+
...line,
|
|
374
|
+
account: accountById.get(line['account_id']) ?? null,
|
|
375
|
+
tax: taxById.get(line['tax_id']) ?? null,
|
|
376
|
+
})),
|
|
377
|
+
header,
|
|
378
|
+
legal_mentions: mentions,
|
|
379
|
+
country_rules: countryRules,
|
|
380
|
+
entry,
|
|
381
|
+
entry_lines: entryLines.map((line) => ({
|
|
382
|
+
...line,
|
|
383
|
+
account_name: ledgerAccounts[line['account_id']] ?? null,
|
|
384
|
+
})),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
388
|
+
/**
|
|
389
|
+
* The document a person means: its id, its number, or the reference its
|
|
390
|
+
* creator gave it. A draft has no number yet, which is what the third is for.
|
|
391
|
+
*/
|
|
392
|
+
export async function resolveDocument(backend, company, wanted) {
|
|
393
|
+
if (UUID.test(wanted))
|
|
394
|
+
return wanted.toLowerCase();
|
|
395
|
+
for (const column of ['number', 'client_ref']) {
|
|
396
|
+
const found = await backend.select({
|
|
397
|
+
table: 'documents',
|
|
398
|
+
columns: ['id'],
|
|
399
|
+
where: [
|
|
400
|
+
{ column: 'company_id', op: 'eq', value: company },
|
|
401
|
+
{ column, op: 'eq', value: wanted },
|
|
402
|
+
],
|
|
403
|
+
limit: 2,
|
|
404
|
+
});
|
|
405
|
+
if (found.length > 1) {
|
|
406
|
+
throw new BooksError(`ambiguous_document: ${found.length} documents of this company carry the ${column} ${wanted}. Name one by its id.`);
|
|
407
|
+
}
|
|
408
|
+
if (found[0] !== undefined)
|
|
409
|
+
return found[0].id;
|
|
410
|
+
}
|
|
411
|
+
throw new BooksError(`unknown_document: no document of this company has the number or the reference ${wanted}.`);
|
|
412
|
+
}
|
|
413
|
+
//# sourceMappingURL=documents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"documents.js","sourceRoot":"","sources":["../../src/books/documents.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amounts as decimal strings, everywhere, in both directions.
|
|
3
|
+
*
|
|
4
|
+
* Postgres holds `numeric` exactly and hands it over as text; a table read
|
|
5
|
+
* asks for `amount::text` so that is what arrives. The one place a float can
|
|
6
|
+
* appear is a function result crossing PostgREST as JSON, where `1210.00`
|
|
7
|
+
* becomes a number before any code of ours sees it. `money()` puts it back to
|
|
8
|
+
* two decimals, which is exact for every amount a ledger holds, and a model
|
|
9
|
+
* reading `"1210.00"` never has to wonder whether `1210.0000000001` was the
|
|
10
|
+
* VAT rounding or the transport.
|
|
11
|
+
*/
|
|
12
|
+
import type { Decimal } from '../types.js';
|
|
13
|
+
/** A numeric, as the decimal string the ledger holds. */
|
|
14
|
+
export declare function money(value: unknown): Decimal | null;
|
|
15
|
+
/** The same, for a quantity or a rate, where two decimals would be a lie. */
|
|
16
|
+
export declare function decimal(value: unknown): Decimal | null;
|
|
17
|
+
/** Applies `money()` to the named keys of every row. */
|
|
18
|
+
export declare function moneyFields<T extends Record<string, unknown>>(rows: T[], keys: string[]): T[];
|
|
19
|
+
/** An amount on its way into the database: `"1210.00"`, never 1210.0000001. */
|
|
20
|
+
export declare function amountIn(value: string | number): string;
|
|
21
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/books/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3C,yDAAyD;AACzD,wBAAgB,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAKpD;AAED,6EAA6E;AAC7E,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAItD;AAED,wDAAwD;AACxD,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAQ7F;AAED,+EAA+E;AAC/E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAMvD"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amounts as decimal strings, everywhere, in both directions.
|
|
3
|
+
*
|
|
4
|
+
* Postgres holds `numeric` exactly and hands it over as text; a table read
|
|
5
|
+
* asks for `amount::text` so that is what arrives. The one place a float can
|
|
6
|
+
* appear is a function result crossing PostgREST as JSON, where `1210.00`
|
|
7
|
+
* becomes a number before any code of ours sees it. `money()` puts it back to
|
|
8
|
+
* two decimals, which is exact for every amount a ledger holds, and a model
|
|
9
|
+
* reading `"1210.00"` never has to wonder whether `1210.0000000001` was the
|
|
10
|
+
* VAT rounding or the transport.
|
|
11
|
+
*/
|
|
12
|
+
import { BooksError } from './backend.js';
|
|
13
|
+
import { roundCurrency } from './rounding.js';
|
|
14
|
+
/** A numeric, as the decimal string the ledger holds. */
|
|
15
|
+
export function money(value) {
|
|
16
|
+
if (value === null || value === undefined)
|
|
17
|
+
return null;
|
|
18
|
+
if (typeof value === 'number')
|
|
19
|
+
return roundCurrency(value, 2).toFixed(2);
|
|
20
|
+
const text = String(value);
|
|
21
|
+
return text.length === 0 ? null : text;
|
|
22
|
+
}
|
|
23
|
+
/** The same, for a quantity or a rate, where two decimals would be a lie. */
|
|
24
|
+
export function decimal(value) {
|
|
25
|
+
if (value === null || value === undefined)
|
|
26
|
+
return null;
|
|
27
|
+
const text = String(value);
|
|
28
|
+
return text.length === 0 ? null : text;
|
|
29
|
+
}
|
|
30
|
+
/** Applies `money()` to the named keys of every row. */
|
|
31
|
+
export function moneyFields(rows, keys) {
|
|
32
|
+
return rows.map((row) => {
|
|
33
|
+
const out = { ...row };
|
|
34
|
+
for (const key of keys) {
|
|
35
|
+
if (key in out)
|
|
36
|
+
out[key] = money(out[key]);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** An amount on its way into the database: `"1210.00"`, never 1210.0000001. */
|
|
42
|
+
export function amountIn(value) {
|
|
43
|
+
const text = typeof value === 'number' ? roundCurrency(value, 2).toFixed(2) : value.trim();
|
|
44
|
+
if (!/^-?\d+(\.\d+)?$/.test(text)) {
|
|
45
|
+
throw new BooksError(`bad_amount: ${String(value)} is not a decimal amount`);
|
|
46
|
+
}
|
|
47
|
+
return text;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/books/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAI9C,yDAAyD;AACzD,MAAM,UAAU,KAAK,CAAC,KAAc;IAClC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,aAAa,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,WAAW,CAAoC,IAAS,EAAE,IAAc;IACtF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACtB,MAAM,GAAG,GAA4B,EAAE,GAAG,GAAG,EAAE,CAAC;QAChD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,GAAG,IAAI,GAAG;gBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,GAAQ,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,QAAQ,CAAC,KAAsB;IAC7C,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAC3F,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,UAAU,CAAC,eAAe,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeping the books, once, for every surface.
|
|
3
|
+
*
|
|
4
|
+
* The MCP server and the command line call these functions and bring their
|
|
5
|
+
* own `Backend`. No accounting rule is in here — the schema holds them — and
|
|
6
|
+
* what is here is what both surfaces would otherwise each write: codes turned
|
|
7
|
+
* into ids, a product filling in a line, which open items a payment is
|
|
8
|
+
* offered to.
|
|
9
|
+
*/
|
|
10
|
+
export * from './backend.js';
|
|
11
|
+
export * as columns from './columns.js';
|
|
12
|
+
export * from './format.js';
|
|
13
|
+
export { roundCurrency } from './rounding.js';
|
|
14
|
+
export * from './shared.js';
|
|
15
|
+
export * from './contacts.js';
|
|
16
|
+
export * from './documents.js';
|
|
17
|
+
export * from './payments.js';
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeping the books, once, for every surface.
|
|
3
|
+
*
|
|
4
|
+
* The MCP server and the command line call these functions and bring their
|
|
5
|
+
* own `Backend`. No accounting rule is in here — the schema holds them — and
|
|
6
|
+
* what is here is what both surfaces would otherwise each write: codes turned
|
|
7
|
+
* into ids, a product filling in a line, which open items a payment is
|
|
8
|
+
* offered to.
|
|
9
|
+
*/
|
|
10
|
+
export * from './backend.js';
|
|
11
|
+
export * as columns from './columns.js';
|
|
12
|
+
export * from './format.js';
|
|
13
|
+
export { roundCurrency } from './rounding.js';
|
|
14
|
+
export * from './shared.js';
|
|
15
|
+
export * from './contacts.js';
|
|
16
|
+
export * from './documents.js';
|
|
17
|
+
export * from './payments.js';
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payments and matching.
|
|
3
|
+
*
|
|
4
|
+
* `post_payment`, `reconcile` and `unreconcile` carry the rules and this file
|
|
5
|
+
* calls them. The one thing decided here is which open items a payment is
|
|
6
|
+
* offered to, oldest first — never how much is owed, which the ledger says.
|
|
7
|
+
*/
|
|
8
|
+
import { type Backend } from './backend.js';
|
|
9
|
+
export interface RecordPaymentArgs {
|
|
10
|
+
company_id: string;
|
|
11
|
+
/** Left out when `document_id` is given: the open item says which way the money goes. */
|
|
12
|
+
direction?: 'inbound' | 'outbound' | undefined;
|
|
13
|
+
amount: string | number;
|
|
14
|
+
payment_date: string;
|
|
15
|
+
contact_id?: string | undefined;
|
|
16
|
+
journal_id?: string | undefined;
|
|
17
|
+
journal_code?: string | undefined;
|
|
18
|
+
bank_account_id?: string | undefined;
|
|
19
|
+
reference?: string | undefined;
|
|
20
|
+
memo?: string | undefined;
|
|
21
|
+
currency_code?: string | undefined;
|
|
22
|
+
exchange_rate?: string | number | undefined;
|
|
23
|
+
match_open_items?: boolean | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* The document this money pays. It names the contact and the direction —
|
|
26
|
+
* read off what is still open on it, as the ledger holds it — and the
|
|
27
|
+
* matching is offered to that document alone.
|
|
28
|
+
*/
|
|
29
|
+
document_id?: string | undefined;
|
|
30
|
+
/** Your own reference. Recording twice under one reference records once. */
|
|
31
|
+
client_ref?: string | undefined;
|
|
32
|
+
}
|
|
33
|
+
export interface SettleFromStatementArgs {
|
|
34
|
+
transaction_id: string;
|
|
35
|
+
/** The document the statement line pays: its open items are the lines settled. */
|
|
36
|
+
document_id?: string | undefined;
|
|
37
|
+
/** Or the open items themselves, by ledger line. */
|
|
38
|
+
line_ids?: string[] | undefined;
|
|
39
|
+
}
|
|
40
|
+
export interface ReconcileArgs {
|
|
41
|
+
line_a: string;
|
|
42
|
+
line_b: string;
|
|
43
|
+
amount?: string | number | undefined;
|
|
44
|
+
}
|
|
45
|
+
export interface UnreconcileArgs {
|
|
46
|
+
reconciliation_id: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function recordPayment(backend: Backend, args: RecordPaymentArgs): Promise<unknown>;
|
|
49
|
+
export declare function reconcile(backend: Backend, args: ReconcileArgs): Promise<unknown>;
|
|
50
|
+
export declare function unreconcile(backend: Backend, args: UnreconcileArgs): Promise<unknown>;
|
|
51
|
+
/**
|
|
52
|
+
* A statement line pays a document: `settle_from_statement()` books the
|
|
53
|
+
* payment the line is and matches it against the open items named. Naming a
|
|
54
|
+
* document names what is open on it; nothing here chooses between candidates.
|
|
55
|
+
*/
|
|
56
|
+
export declare function settleFromStatement(backend: Backend, args: SettleFromStatementArgs): Promise<unknown>;
|
|
57
|
+
//# sourceMappingURL=payments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payments.d.ts","sourceRoot":"","sources":["../../src/books/payments.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAc,KAAK,OAAO,EAAyB,MAAM,cAAc,CAAC;AAK/E,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,yFAAyF;IACzF,SAAS,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;IAC/C,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC5C,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CACjC;AAqBD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,MAAM,WAAW,eAAe;IAC9B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,OAAO,CAAC,CA+HlB;AAgHD,wBAAsB,SAAS,CAC7B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,OAAO,CAAC,CAMlB;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,OAAO,CAAC,CA2BlB"}
|