@things-factory/accounting 10.1.138 → 10.1.140
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/dist-server/service/account/index.d.ts +1 -1
- package/dist-server/service/accounting-category/index.d.ts +1 -1
- package/dist-server/service/accounting-document/index.d.ts +1 -1
- package/dist-server/service/bank-account/index.d.ts +1 -1
- package/dist-server/service/bank-statement/bank-import-mutation.d.ts +5 -0
- package/dist-server/service/bank-statement/bank-import-mutation.js +43 -0
- package/dist-server/service/bank-statement/bank-import-mutation.js.map +1 -0
- package/dist-server/service/bank-statement/bank-import-type.d.ts +31 -0
- package/dist-server/service/bank-statement/bank-import-type.js +122 -0
- package/dist-server/service/bank-statement/bank-import-type.js.map +1 -0
- package/dist-server/service/bank-statement/bank-import.d.ts +16 -0
- package/dist-server/service/bank-statement/bank-import.js +141 -0
- package/dist-server/service/bank-statement/bank-import.js.map +1 -0
- package/dist-server/service/bank-statement/bank-parser.d.ts +35 -0
- package/dist-server/service/bank-statement/bank-parser.js +89 -0
- package/dist-server/service/bank-statement/bank-parser.js.map +1 -0
- package/dist-server/service/bank-statement/bank-reconcile.d.ts +35 -0
- package/dist-server/service/bank-statement/bank-reconcile.js +71 -0
- package/dist-server/service/bank-statement/bank-reconcile.js.map +1 -0
- package/dist-server/service/bank-statement/bank-source.d.ts +25 -0
- package/dist-server/service/bank-statement/bank-source.js +55 -0
- package/dist-server/service/bank-statement/bank-source.js.map +1 -0
- package/dist-server/service/bank-statement/index.d.ts +2 -1
- package/dist-server/service/bank-statement/index.js +2 -1
- package/dist-server/service/bank-statement/index.js.map +1 -1
- package/dist-server/service/financial-institution/index.d.ts +1 -1
- package/dist-server/service/financial-statement/index.d.ts +2 -2
- package/dist-server/service/fiscal-month/index.d.ts +1 -1
- package/dist-server/service/fiscal-quarter/index.d.ts +1 -1
- package/dist-server/service/fiscal-year/index.d.ts +1 -1
- package/dist-server/service/index.d.ts +7 -2
- package/dist-server/service/index.js +5 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/payment/payment-service.d.ts +3 -1
- package/dist-server/service/payment/payment-service.js +5 -2
- package/dist-server/service/payment/payment-service.js.map +1 -1
- package/dist-server/service/transaction/transaction-service.d.ts +5 -0
- package/dist-server/service/transaction/transaction-service.js +22 -0
- package/dist-server/service/transaction/transaction-service.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -3
- package/translations/en.json +15 -1
- package/translations/ko.json +15 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BankLine } from './bank-parser';
|
|
2
|
+
export interface LedgerCandidate {
|
|
3
|
+
id: string;
|
|
4
|
+
currency: string;
|
|
5
|
+
type: string;
|
|
6
|
+
amount: number;
|
|
7
|
+
transactionDate: string;
|
|
8
|
+
balance?: number;
|
|
9
|
+
source?: string;
|
|
10
|
+
bankFingerprint?: string;
|
|
11
|
+
bankAccountId?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
counterpartyName?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Resolve the entire statement before writing: one ledger row cannot be consumed twice. */
|
|
16
|
+
export declare function reconcileBankLines(lines: BankLine[], ledger: LedgerCandidate[]): {
|
|
17
|
+
line: BankLine;
|
|
18
|
+
candidates: LedgerCandidate[];
|
|
19
|
+
match: LedgerCandidate;
|
|
20
|
+
}[];
|
|
21
|
+
/**
|
|
22
|
+
* Place the held lines a person decided: on one of the line's own candidates (the row it is), or nowhere (a new
|
|
23
|
+
* transaction). No ledger row is taken twice, counting the ones the statement matched by itself. False when a
|
|
24
|
+
* decision names a line that was not held, or a row it may not take — then nothing is placed.
|
|
25
|
+
*/
|
|
26
|
+
export declare function placeHeldLines(plan: {
|
|
27
|
+
line: {
|
|
28
|
+
fingerprint: string;
|
|
29
|
+
};
|
|
30
|
+
candidates: LedgerCandidate[];
|
|
31
|
+
match?: LedgerCandidate;
|
|
32
|
+
}[], decisions: readonly {
|
|
33
|
+
fingerprint: string;
|
|
34
|
+
transactionId?: string | null;
|
|
35
|
+
}[]): boolean;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reconcileBankLines = reconcileBankLines;
|
|
4
|
+
exports.placeHeldLines = placeHeldLines;
|
|
5
|
+
const cents = (value) => Math.round(Number(value) * 100);
|
|
6
|
+
const day = (value) => Date.parse(value + 'T00:00:00Z') / 86400000;
|
|
7
|
+
const normalized = (value) => (value ?? '').normalize('NFKC').replace(/[^\p{L}\p{N}]/gu, '').toLowerCase();
|
|
8
|
+
/** Resolve the entire statement before writing: one ledger row cannot be consumed twice. */
|
|
9
|
+
function reconcileBankLines(lines, ledger) {
|
|
10
|
+
const candidates = lines.map(line => {
|
|
11
|
+
const possible = ledger.filter(old => !old.bankFingerprint && old.source !== 'BANK_STATEMENT' &&
|
|
12
|
+
old.currency === line.currency && old.type === line.type && cents(old.amount) === cents(line.amount));
|
|
13
|
+
const exact = possible.filter(old => old.transactionDate === line.transactionDate);
|
|
14
|
+
if (exact.length)
|
|
15
|
+
return exact;
|
|
16
|
+
// Date correction requires independent evidence, never amount alone.
|
|
17
|
+
return possible.filter(old => Math.abs(day(old.transactionDate) - day(line.transactionDate)) <= 7 && ((old.balance != null && cents(old.balance) === cents(line.balance)) ||
|
|
18
|
+
[old.description, old.counterpartyName].some(value => normalized(value).length >= 4 &&
|
|
19
|
+
[line.bankDescription, line.bankCounterpartyName].some(bank => normalized(bank) === normalized(value)))));
|
|
20
|
+
});
|
|
21
|
+
const uses = new Map();
|
|
22
|
+
for (const group of candidates)
|
|
23
|
+
for (const old of group)
|
|
24
|
+
uses.set(old.id, (uses.get(old.id) ?? 0) + 1);
|
|
25
|
+
return lines.map((line, index) => {
|
|
26
|
+
let group = candidates[index];
|
|
27
|
+
let uncertain = false;
|
|
28
|
+
if (!group.length) {
|
|
29
|
+
group = ledger.filter(old => !old.bankFingerprint && old.source !== 'BANK_STATEMENT' && old.currency === line.currency &&
|
|
30
|
+
old.type === line.type && cents(old.amount) === cents(line.amount) && Math.abs(day(old.transactionDate) - day(line.transactionDate)) <= 7);
|
|
31
|
+
uncertain = group.length > 0;
|
|
32
|
+
}
|
|
33
|
+
// A ledger total may represent multiple bank movements. Keep all its constituent rows pending.
|
|
34
|
+
if (!group.length) {
|
|
35
|
+
const sameDay = lines.filter(other => other.transactionDate === line.transactionDate && other.currency === line.currency && other.type === line.type);
|
|
36
|
+
group = ledger.filter(old => !old.bankFingerprint && old.source !== 'BANK_STATEMENT' && old.transactionDate === line.transactionDate &&
|
|
37
|
+
old.currency === line.currency && old.type === line.type && cents(old.amount) > cents(line.amount) &&
|
|
38
|
+
(cents(old.amount) === sameDay.reduce((sum, other) => sum + cents(other.amount), 0) ||
|
|
39
|
+
sameDay.some(other => other !== line && cents(other.amount) + cents(line.amount) === cents(old.amount))));
|
|
40
|
+
}
|
|
41
|
+
return { line, candidates: group, match: !uncertain && group.length === 1 && uses.get(group[0].id) === 1 ? group[0] : undefined };
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Place the held lines a person decided: on one of the line's own candidates (the row it is), or nowhere (a new
|
|
46
|
+
* transaction). No ledger row is taken twice, counting the ones the statement matched by itself. False when a
|
|
47
|
+
* decision names a line that was not held, or a row it may not take — then nothing is placed.
|
|
48
|
+
*/
|
|
49
|
+
function placeHeldLines(plan, decisions) {
|
|
50
|
+
const byLine = new Map(plan.map(item => [item.line.fingerprint, item]));
|
|
51
|
+
const taken = new Set(plan.filter(item => item.match).map(item => item.match.id));
|
|
52
|
+
const placed = [];
|
|
53
|
+
for (const decision of decisions) {
|
|
54
|
+
const item = byLine.get(decision.fingerprint);
|
|
55
|
+
if (!item || item.match || !item.candidates.length || placed.some(([one]) => one === item))
|
|
56
|
+
return false;
|
|
57
|
+
const id = decision.transactionId ?? null;
|
|
58
|
+
const row = id ? item.candidates.find(old => old.id === id) : undefined;
|
|
59
|
+
if (id && (!row || taken.has(id)))
|
|
60
|
+
return false;
|
|
61
|
+
if (row)
|
|
62
|
+
taken.add(row.id);
|
|
63
|
+
placed.push([item, row]);
|
|
64
|
+
}
|
|
65
|
+
for (const [item, row] of placed) {
|
|
66
|
+
item.match = row;
|
|
67
|
+
item.candidates = [];
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=bank-reconcile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bank-reconcile.js","sourceRoot":"","sources":["../../../server/service/bank-statement/bank-reconcile.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BankLine } from './bank-parser'\n\nexport interface LedgerCandidate {\n id: string; currency: string; type: string; amount: number; transactionDate: string\n balance?: number; source?: string; bankFingerprint?: string; bankAccountId?: string\n description?: string; counterpartyName?: string\n}\nconst cents = (value: number) => Math.round(Number(value) * 100)\nconst day = (value: string) => Date.parse(value + 'T00:00:00Z') / 86400000\nconst normalized = (value?: string | null) => (value ?? '').normalize('NFKC').replace(/[^\\p{L}\\p{N}]/gu, '').toLowerCase()\n\n/** Resolve the entire statement before writing: one ledger row cannot be consumed twice. */\nexport function reconcileBankLines(lines: BankLine[], ledger: LedgerCandidate[]) {\n const candidates = lines.map(line => {\n const possible = ledger.filter(old => !old.bankFingerprint && old.source !== 'BANK_STATEMENT' &&\n old.currency === line.currency && old.type === line.type && cents(old.amount) === cents(line.amount))\n const exact = possible.filter(old => old.transactionDate === line.transactionDate)\n if (exact.length) return exact\n // Date correction requires independent evidence, never amount alone.\n return possible.filter(old => Math.abs(day(old.transactionDate) - day(line.transactionDate)) <= 7 && (\n (old.balance != null && cents(old.balance) === cents(line.balance)) ||\n [old.description, old.counterpartyName].some(value => normalized(value).length >= 4 &&\n [line.bankDescription, line.bankCounterpartyName].some(bank => normalized(bank) === normalized(value)))\n ))\n })\n const uses = new Map<string, number>()\n for (const group of candidates) for (const old of group) uses.set(old.id, (uses.get(old.id) ?? 0) + 1)\n return lines.map((line, index) => {\n let group = candidates[index]\n let uncertain = false\n if (!group.length) {\n group = ledger.filter(old => !old.bankFingerprint && old.source !== 'BANK_STATEMENT' && old.currency === line.currency &&\n old.type === line.type && cents(old.amount) === cents(line.amount) && Math.abs(day(old.transactionDate) - day(line.transactionDate)) <= 7)\n uncertain = group.length > 0\n }\n // A ledger total may represent multiple bank movements. Keep all its constituent rows pending.\n if (!group.length) {\n const sameDay = lines.filter(other => other.transactionDate === line.transactionDate && other.currency === line.currency && other.type === line.type)\n group = ledger.filter(old => !old.bankFingerprint && old.source !== 'BANK_STATEMENT' && old.transactionDate === line.transactionDate &&\n old.currency === line.currency && old.type === line.type && cents(old.amount) > cents(line.amount) &&\n (cents(old.amount) === sameDay.reduce((sum, other) => sum + cents(other.amount), 0) ||\n sameDay.some(other => other !== line && cents(other.amount) + cents(line.amount) === cents(old.amount))))\n }\n return { line, candidates: group, match: !uncertain && group.length === 1 && uses.get(group[0].id) === 1 ? group[0] : undefined }\n })\n}\n\n/**\n * Place the held lines a person decided: on one of the line's own candidates (the row it is), or nowhere (a new\n * transaction). No ledger row is taken twice, counting the ones the statement matched by itself. False when a\n * decision names a line that was not held, or a row it may not take — then nothing is placed.\n */\nexport function placeHeldLines(\n plan: { line: { fingerprint: string }; candidates: LedgerCandidate[]; match?: LedgerCandidate }[],\n decisions: readonly { fingerprint: string; transactionId?: string | null }[]\n): boolean {\n const byLine = new Map(plan.map(item => [item.line.fingerprint, item]))\n const taken = new Set(plan.filter(item => item.match).map(item => item.match!.id))\n const placed: [typeof plan[number], LedgerCandidate | undefined][] = []\n for (const decision of decisions) {\n const item = byLine.get(decision.fingerprint)\n if (!item || item.match || !item.candidates.length || placed.some(([one]) => one === item)) return false\n const id = decision.transactionId ?? null\n const row = id ? item.candidates.find(old => old.id === id) : undefined\n if (id && (!row || taken.has(id))) return false\n if (row) taken.add(row.id)\n placed.push([item, row])\n }\n for (const [item, row] of placed) {\n item.match = row\n item.candidates = []\n }\n return true\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Multipart headers may expose UTF-8 filename bytes as Latin-1 text. */
|
|
2
|
+
export declare function bankFilename(value: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Dates stay in the bank's local calendar; an unknown time is never invented. `bankLocalTime` is the time as the bank
|
|
5
|
+
* printed it — what identifies the line, whatever timezone the account is later given.
|
|
6
|
+
*/
|
|
7
|
+
export declare function bankTimestamp(value: string, timezone?: string): {
|
|
8
|
+
transactionDate: string;
|
|
9
|
+
transactionAt: string | null;
|
|
10
|
+
bankLocalTime: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* File names and export row numbers change between overlapping statements; the account's timezone may change too.
|
|
14
|
+
* The line is known by what the bank printed: its local time string, not the instant it was read as (ADR-0120).
|
|
15
|
+
*/
|
|
16
|
+
export declare function bankFingerprint(line: {
|
|
17
|
+
accountNumber: string;
|
|
18
|
+
currency: string;
|
|
19
|
+
bankLocalTime: string;
|
|
20
|
+
type: string;
|
|
21
|
+
amount: number;
|
|
22
|
+
balance: number;
|
|
23
|
+
bankDescription: string | null;
|
|
24
|
+
bankKind: string | null;
|
|
25
|
+
}, occurrence?: number): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bankFilename = bankFilename;
|
|
4
|
+
exports.bankTimestamp = bankTimestamp;
|
|
5
|
+
exports.bankFingerprint = bankFingerprint;
|
|
6
|
+
const tslib_1 = require("tslib");
|
|
7
|
+
const moment_timezone_1 = tslib_1.__importDefault(require("moment-timezone"));
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
9
|
+
const shell_1 = require("@things-factory/shell");
|
|
10
|
+
/** Multipart headers may expose UTF-8 filename bytes as Latin-1 text. */
|
|
11
|
+
function bankFilename(value) {
|
|
12
|
+
if (/[^\x00-\xff]/.test(value))
|
|
13
|
+
return value.normalize('NFC');
|
|
14
|
+
const decoded = Buffer.from(value, 'latin1').toString('utf8');
|
|
15
|
+
return (decoded.includes('\ufffd') ? value : decoded).normalize('NFC');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Dates stay in the bank's local calendar; an unknown time is never invented. `bankLocalTime` is the time as the bank
|
|
19
|
+
* printed it — what identifies the line, whatever timezone the account is later given.
|
|
20
|
+
*/
|
|
21
|
+
function bankTimestamp(value, timezone = 'Asia/Seoul') {
|
|
22
|
+
const normalized = value.trim().replace(/\./g, '-').replace(/[()]/g, '').replace(/\s+/g, ' ');
|
|
23
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})(?: (\d{2}):(\d{2}):(\d{2}))?$/.exec(normalized);
|
|
24
|
+
if (!match)
|
|
25
|
+
throw new shell_1.Refusal('bank-invalid-time', { value }, 'Check the bank transaction timestamp');
|
|
26
|
+
const [, y, m, d, h, min, sec] = match;
|
|
27
|
+
const date = new Date(Date.UTC(+y, +m - 1, +d));
|
|
28
|
+
if (date.getUTCFullYear() !== +y || date.getUTCMonth() !== +m - 1 || date.getUTCDate() !== +d || (h !== undefined && (+h > 23 || +min > 59 || +sec > 59)))
|
|
29
|
+
throw new shell_1.Refusal('bank-invalid-time', { value }, 'Check the bank transaction timestamp');
|
|
30
|
+
if (!moment_timezone_1.default.tz.zone(timezone))
|
|
31
|
+
throw new shell_1.Refusal('bank-invalid-time', { value: timezone }, 'Invalid bank account time zone');
|
|
32
|
+
let transactionAt = null;
|
|
33
|
+
if (h !== undefined) {
|
|
34
|
+
const local = `${y}-${m}-${d} ${h}:${min}:${sec}`;
|
|
35
|
+
const instant = moment_timezone_1.default.tz(local, 'YYYY-MM-DD HH:mm:ss', true, timezone);
|
|
36
|
+
if (instant.format('YYYY-MM-DD HH:mm:ss') !== local)
|
|
37
|
+
throw new shell_1.Refusal('bank-invalid-time', { value }, 'Nonexistent local bank time');
|
|
38
|
+
if ([-120, -60, 60, 120].some(minutes => instant.clone().add(minutes, 'minutes').format('YYYY-MM-DD HH:mm:ss') === local))
|
|
39
|
+
throw new shell_1.Refusal('bank-invalid-time', { value }, 'Ambiguous local bank time');
|
|
40
|
+
transactionAt = instant.format('YYYY-MM-DDTHH:mm:ssZ');
|
|
41
|
+
}
|
|
42
|
+
return { transactionDate: `${y}-${m}-${d}`, transactionAt, bankLocalTime: h === undefined ? `${y}-${m}-${d}` : `${y}-${m}-${d} ${h}:${min}:${sec}` };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* File names and export row numbers change between overlapping statements; the account's timezone may change too.
|
|
46
|
+
* The line is known by what the bank printed: its local time string, not the instant it was read as (ADR-0120).
|
|
47
|
+
*/
|
|
48
|
+
function bankFingerprint(line, occurrence = 0) {
|
|
49
|
+
return (0, crypto_1.createHash)('sha256').update(JSON.stringify([
|
|
50
|
+
line.accountNumber.replace(/\D/g, ''), line.currency, line.bankLocalTime,
|
|
51
|
+
line.type, line.amount.toFixed(2), line.balance.toFixed(2),
|
|
52
|
+
(line.bankDescription ?? '').normalize('NFKC').trim(), (line.bankKind ?? '').normalize('NFKC').trim(), occurrence
|
|
53
|
+
])).digest('hex');
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=bank-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bank-source.js","sourceRoot":"","sources":["../../../server/service/bank-statement/bank-source.ts"],"names":[],"mappings":";;AAKA,oCAIC;AAMD,sCAkBC;AAMD,0CAMC;;AA7CD,8EAAoC;AACpC,mCAAmC;AACnC,iDAA+C;AAE/C,yEAAyE;AACzE,SAAgB,YAAY,CAAC,KAAa;IACxC,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;IAC7D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC7D,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;AACxE,CAAC;AAED;;;GAGG;AACH,SAAgB,aAAa,CAAC,KAAa,EAAE,QAAQ,GAAG,YAAY;IAClE,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC7F,MAAM,KAAK,GAAG,wDAAwD,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACvF,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,eAAO,CAAC,mBAAmB,EAAE,EAAE,KAAK,EAAE,EAAE,sCAAsC,CAAC,CAAA;IACrG,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,KAAK,CAAA;IACtC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/C,IAAI,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC;QACvJ,MAAM,IAAI,eAAO,CAAC,mBAAmB,EAAE,EAAE,KAAK,EAAE,EAAE,sCAAsC,CAAC,CAAA;IAC3F,IAAI,CAAC,yBAAM,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,eAAO,CAAC,mBAAmB,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,gCAAgC,CAAC,CAAA;IAC5H,IAAI,aAAa,GAAkB,IAAI,CAAA;IACvC,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,EAAE,CAAA;QACjD,MAAM,OAAO,GAAG,yBAAM,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAA;QACvE,IAAI,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAC,KAAK,KAAK;YAAE,MAAM,IAAI,eAAO,CAAC,mBAAmB,EAAE,EAAE,KAAK,EAAE,EAAE,6BAA6B,CAAC,CAAA;QACrI,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,qBAAqB,CAAC,KAAK,KAAK,CAAC;YAAE,MAAM,IAAI,eAAO,CAAC,mBAAmB,EAAE,EAAE,KAAK,EAAE,EAAE,2BAA2B,CAAC,CAAA;QACzM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAA;IACxD,CAAC;IACD,OAAO,EAAE,eAAe,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,EAAE,EAAE,CAAA;AACtJ,CAAC;AAED;;;GAGG;AACH,SAAgB,eAAe,CAAC,IAAgL,EAAE,UAAU,GAAG,CAAC;IAC9N,OAAO,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;QAChD,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa;QACxE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QAC1D,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,UAAU;KAClH,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACnB,CAAC","sourcesContent":["import moment from 'moment-timezone'\nimport { createHash } from 'crypto'\nimport { Refusal } from '@things-factory/shell'\n\n/** Multipart headers may expose UTF-8 filename bytes as Latin-1 text. */\nexport function bankFilename(value: string): string {\n if (/[^\\x00-\\xff]/.test(value)) return value.normalize('NFC')\n const decoded = Buffer.from(value, 'latin1').toString('utf8')\n return (decoded.includes('\\ufffd') ? value : decoded).normalize('NFC')\n}\n\n/**\n * Dates stay in the bank's local calendar; an unknown time is never invented. `bankLocalTime` is the time as the bank\n * printed it — what identifies the line, whatever timezone the account is later given.\n */\nexport function bankTimestamp(value: string, timezone = 'Asia/Seoul'): { transactionDate: string; transactionAt: string | null; bankLocalTime: string } {\n const normalized = value.trim().replace(/\\./g, '-').replace(/[()]/g, '').replace(/\\s+/g, ' ')\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})(?: (\\d{2}):(\\d{2}):(\\d{2}))?$/.exec(normalized)\n if (!match) throw new Refusal('bank-invalid-time', { value }, 'Check the bank transaction timestamp')\n const [, y, m, d, h, min, sec] = match\n const date = new Date(Date.UTC(+y, +m - 1, +d))\n if (date.getUTCFullYear() !== +y || date.getUTCMonth() !== +m - 1 || date.getUTCDate() !== +d || (h !== undefined && (+h > 23 || +min > 59 || +sec > 59)))\n throw new Refusal('bank-invalid-time', { value }, 'Check the bank transaction timestamp')\n if (!moment.tz.zone(timezone)) throw new Refusal('bank-invalid-time', { value: timezone }, 'Invalid bank account time zone')\n let transactionAt: string | null = null\n if (h !== undefined) {\n const local = `${y}-${m}-${d} ${h}:${min}:${sec}`\n const instant = moment.tz(local, 'YYYY-MM-DD HH:mm:ss', true, timezone)\n if (instant.format('YYYY-MM-DD HH:mm:ss') !== local) throw new Refusal('bank-invalid-time', { value }, 'Nonexistent local bank time')\n if ([-120, -60, 60, 120].some(minutes => instant.clone().add(minutes, 'minutes').format('YYYY-MM-DD HH:mm:ss') === local)) throw new Refusal('bank-invalid-time', { value }, 'Ambiguous local bank time')\n transactionAt = instant.format('YYYY-MM-DDTHH:mm:ssZ')\n }\n return { transactionDate: `${y}-${m}-${d}`, transactionAt, bankLocalTime: h === undefined ? `${y}-${m}-${d}` : `${y}-${m}-${d} ${h}:${min}:${sec}` }\n}\n\n/**\n * File names and export row numbers change between overlapping statements; the account's timezone may change too.\n * The line is known by what the bank printed: its local time string, not the instant it was read as (ADR-0120).\n */\nexport function bankFingerprint(line: { accountNumber: string; currency: string; bankLocalTime: string; type: string; amount: number; balance: number; bankDescription: string | null; bankKind: string | null }, occurrence = 0): string {\n return createHash('sha256').update(JSON.stringify([\n line.accountNumber.replace(/\\D/g, ''), line.currency, line.bankLocalTime,\n line.type, line.amount.toFixed(2), line.balance.toFixed(2),\n (line.bankDescription ?? '').normalize('NFKC').trim(), (line.bankKind ?? '').normalize('NFKC').trim(), occurrence\n ])).digest('hex')\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BankStatement } from './bank-statement';
|
|
2
2
|
import { BankStatementQuery } from './bank-statement-query';
|
|
3
|
+
import { BankImportMutation } from './bank-import-mutation';
|
|
3
4
|
export declare const entities: (typeof BankStatement)[];
|
|
4
|
-
export declare const resolvers: (typeof BankStatementQuery)[];
|
|
5
|
+
export declare const resolvers: (typeof BankImportMutation | typeof BankStatementQuery)[];
|
|
5
6
|
export declare const subscribers: any[];
|
|
@@ -3,7 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.subscribers = exports.resolvers = exports.entities = void 0;
|
|
4
4
|
const bank_statement_1 = require("./bank-statement");
|
|
5
5
|
const bank_statement_query_1 = require("./bank-statement-query");
|
|
6
|
+
const bank_import_mutation_1 = require("./bank-import-mutation");
|
|
6
7
|
exports.entities = [bank_statement_1.BankStatement];
|
|
7
|
-
exports.resolvers = [bank_statement_query_1.BankStatementQuery];
|
|
8
|
+
exports.resolvers = [bank_statement_query_1.BankStatementQuery, bank_import_mutation_1.BankImportMutation];
|
|
8
9
|
exports.subscribers = [];
|
|
9
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/bank-statement/index.ts"],"names":[],"mappings":";;;AAAA,qDAAgD;AAChD,iEAA2D;AAE9C,QAAA,QAAQ,GAAG,CAAC,8BAAa,CAAC,CAAA;AAC1B,QAAA,SAAS,GAAG,CAAC,yCAAkB,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/bank-statement/index.ts"],"names":[],"mappings":";;;AAAA,qDAAgD;AAChD,iEAA2D;AAC3D,iEAA2D;AAE9C,QAAA,QAAQ,GAAG,CAAC,8BAAa,CAAC,CAAA;AAC1B,QAAA,SAAS,GAAG,CAAC,yCAAkB,EAAE,yCAAkB,CAAC,CAAA;AACpD,QAAA,WAAW,GAAG,EAAE,CAAA","sourcesContent":["import { BankStatement } from './bank-statement'\nimport { BankStatementQuery } from './bank-statement-query'\nimport { BankImportMutation } from './bank-import-mutation'\n\nexport const entities = [BankStatement]\nexport const resolvers = [BankStatementQuery, BankImportMutation]\nexport const subscribers = []\n"]}
|
|
@@ -2,5 +2,5 @@ import { FinancialInstitution } from './financial-institution';
|
|
|
2
2
|
import { FinancialInstitutionQuery } from './financial-institution-query';
|
|
3
3
|
import { FinancialInstitutionMutation } from './financial-institution-mutation';
|
|
4
4
|
export declare const entities: (typeof FinancialInstitution)[];
|
|
5
|
-
export declare const resolvers: (typeof
|
|
5
|
+
export declare const resolvers: (typeof FinancialInstitutionMutation | typeof FinancialInstitutionQuery)[];
|
|
6
6
|
export declare const subscribers: any[];
|
|
@@ -4,6 +4,6 @@ import { FinancialStatementHistory } from './financial-statement-history';
|
|
|
4
4
|
import { FinancialStatementLineQuery, FinancialStatementQuery } from './financial-statement-query';
|
|
5
5
|
import { FinancialStatementMutation } from './financial-statement-mutation';
|
|
6
6
|
import { FinancialStatementHistoryEntitySubscriber } from './event-subscriber';
|
|
7
|
-
export declare const entities: (typeof
|
|
8
|
-
export declare const resolvers: (typeof
|
|
7
|
+
export declare const entities: (typeof FinancialStatement | typeof FinancialStatementHistory | typeof FinancialStatementLine)[];
|
|
8
|
+
export declare const resolvers: (typeof FinancialStatementMutation | typeof FinancialStatementQuery | typeof FinancialStatementLineQuery)[];
|
|
9
9
|
export declare const subscribers: (typeof FinancialStatementHistoryEntitySubscriber)[];
|
|
@@ -2,5 +2,5 @@ import { FiscalMonth } from './fiscal-month';
|
|
|
2
2
|
import { FiscalMonthQuery } from './fiscal-month-query';
|
|
3
3
|
import { FiscalMonthMutation } from './fiscal-month-mutation';
|
|
4
4
|
export declare const entities: (typeof FiscalMonth)[];
|
|
5
|
-
export declare const resolvers: (typeof
|
|
5
|
+
export declare const resolvers: (typeof FiscalMonthMutation | typeof FiscalMonthQuery)[];
|
|
6
6
|
export declare const subscribers: any[];
|
|
@@ -2,5 +2,5 @@ import { FiscalQuarter } from './fiscal-quarter';
|
|
|
2
2
|
import { FiscalQuarterQuery } from './fiscal-quarter-query';
|
|
3
3
|
import { FiscalQuarterMutation } from './fiscal-quarter-mutation';
|
|
4
4
|
export declare const entities: (typeof FiscalQuarter)[];
|
|
5
|
-
export declare const resolvers: (typeof
|
|
5
|
+
export declare const resolvers: (typeof FiscalQuarterMutation | typeof FiscalQuarterQuery)[];
|
|
6
6
|
export declare const subscribers: any[];
|
|
@@ -2,5 +2,5 @@ import { FiscalYear } from './fiscal-year';
|
|
|
2
2
|
import { FiscalYearQuery } from './fiscal-year-query';
|
|
3
3
|
import { FiscalYearMutation } from './fiscal-year-mutation';
|
|
4
4
|
export declare const entities: (typeof FiscalYear)[];
|
|
5
|
-
export declare const resolvers: (typeof
|
|
5
|
+
export declare const resolvers: (typeof FiscalYearMutation | typeof FiscalYearQuery)[];
|
|
6
6
|
export declare const subscribers: any[];
|
|
@@ -21,8 +21,13 @@ export * from './financial-statement/financial-statement-line';
|
|
|
21
21
|
export * from './financial-statement/financial-statement-service';
|
|
22
22
|
export * from './recurring-cash-item/recurring-schedule';
|
|
23
23
|
export * from './recurring-cash-item/recurring-cash-item-service';
|
|
24
|
-
export
|
|
24
|
+
export * from './bank-statement/bank-source';
|
|
25
|
+
export * from './bank-statement/bank-parser';
|
|
26
|
+
export * from './bank-statement/bank-reconcile';
|
|
27
|
+
export * from './bank-statement/bank-import-type';
|
|
28
|
+
export * from './bank-statement/bank-import';
|
|
29
|
+
export declare const entities: (typeof import("./fiscal-year/fiscal-year").FiscalYear | typeof import("./accounting-category/accounting-category").AccountingCategory | typeof import("./account/account").Account | typeof import("./financial-institution/financial-institution").FinancialInstitution | typeof import("./transaction/transaction").Transaction | typeof import("./accounting-document/accounting-document").AccountingDocument | typeof import("./bank-account/bank-account").BankAccount | typeof import("./bank-statement/bank-statement").BankStatement | typeof import("./financial-statement/financial-statement").FinancialStatement | typeof import("./financial-statement/financial-statement-history").FinancialStatementHistory | typeof import("./financial-statement/financial-statement-line").FinancialStatementLine | typeof import("./transaction/transaction-history").TransactionHistory | typeof import("./payment/payment").Payment | typeof import("./recurring-cash-item/recurring-cash-item").RecurringCashItem)[];
|
|
25
30
|
export declare const subscribers: any[];
|
|
26
31
|
export declare const schema: {
|
|
27
|
-
resolverClasses: (typeof import("./
|
|
32
|
+
resolverClasses: (typeof import("./account/account-mutation").AccountMutation | typeof import("./account/account-query").AccountQuery | typeof import("./accounting-category/accounting-category-mutation").AccountingCategoryMutation | typeof import("./accounting-category/accounting-category-query").AccountingCategoryQuery | typeof import("./accounting-document/accounting-document-mutation").AccountingDocumentMutation | typeof import("./accounting-document/accounting-document-query").AccountingDocumentQuery | typeof import("./bank-account/bank-account-mutation").BankAccountMutation | typeof import("./bank-account/bank-account-query").BankAccountQuery | typeof import("./bank-statement/bank-import-mutation").BankImportMutation | typeof import("./bank-statement/bank-statement-query").BankStatementQuery | typeof import("./financial-institution/financial-institution-mutation").FinancialInstitutionMutation | typeof import("./financial-institution/financial-institution-query").FinancialInstitutionQuery | typeof import("./financial-statement/financial-statement-mutation").FinancialStatementMutation | typeof import("./financial-statement/financial-statement-query").FinancialStatementQuery | typeof import("./financial-statement/financial-statement-query").FinancialStatementLineQuery | typeof import("./fiscal-month/fiscal-month-mutation").FiscalMonthMutation | typeof import("./fiscal-month/fiscal-month-query").FiscalMonthQuery | typeof import("./fiscal-quarter/fiscal-quarter-mutation").FiscalQuarterMutation | typeof import("./fiscal-quarter/fiscal-quarter-query").FiscalQuarterQuery | typeof import("./fiscal-year/fiscal-year-mutation").FiscalYearMutation | typeof import("./fiscal-year/fiscal-year-query").FiscalYearQuery | typeof import("./transaction/transaction-query").TransactionQuery | typeof import("./transaction/transaction-mutation").TransactionMutation | typeof import("./payment/payment-query").PaymentQuery | typeof import("./payment/payment-mutation").PaymentMutation | typeof import("./recurring-cash-item/recurring-cash-item-query").RecurringCashItemQuery | typeof import("./recurring-cash-item/recurring-cash-item-mutation").RecurringCashItemMutation)[];
|
|
28
33
|
};
|
|
@@ -27,6 +27,11 @@ tslib_1.__exportStar(require("./financial-statement/financial-statement-line"),
|
|
|
27
27
|
tslib_1.__exportStar(require("./financial-statement/financial-statement-service"), exports);
|
|
28
28
|
tslib_1.__exportStar(require("./recurring-cash-item/recurring-schedule"), exports);
|
|
29
29
|
tslib_1.__exportStar(require("./recurring-cash-item/recurring-cash-item-service"), exports);
|
|
30
|
+
tslib_1.__exportStar(require("./bank-statement/bank-source"), exports);
|
|
31
|
+
tslib_1.__exportStar(require("./bank-statement/bank-parser"), exports);
|
|
32
|
+
tslib_1.__exportStar(require("./bank-statement/bank-reconcile"), exports);
|
|
33
|
+
tslib_1.__exportStar(require("./bank-statement/bank-import-type"), exports);
|
|
34
|
+
tslib_1.__exportStar(require("./bank-statement/bank-import"), exports);
|
|
30
35
|
/* IMPORT ENTITIES AND RESOLVERS */
|
|
31
36
|
const financial_statement_1 = require("./financial-statement");
|
|
32
37
|
const accounting_category_1 = require("./accounting-category");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,2DAAgC;AAChC,oFAAyD;AACzD,oFAAyD;AACzD,oEAAyC;AACzC,sEAA2C;AAC3C,0EAA+C;AAC/C,4DAAiC;AACjC,4DAAiC;AACjC,oFAAyD;AACzD,oFAAyD;AACzD,wFAA6D;AAC7D,sEAA2C;AAC3C,0EAA+C;AAC/C,oEAAyC;AAEzC,qBAAqB;AACrB,2EAAgD;AAChD,4EAAiD;AACjD,8EAAmD;AACnD,oEAAyC;AACzC,oEAAyC;AACzC,yFAA8D;AAC9D,4FAAiE;AACjE,mFAAwD;AACxD,4FAAiE;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,2DAAgC;AAChC,oFAAyD;AACzD,oFAAyD;AACzD,oEAAyC;AACzC,sEAA2C;AAC3C,0EAA+C;AAC/C,4DAAiC;AACjC,4DAAiC;AACjC,oFAAyD;AACzD,oFAAyD;AACzD,wFAA6D;AAC7D,sEAA2C;AAC3C,0EAA+C;AAC/C,oEAAyC;AAEzC,qBAAqB;AACrB,2EAAgD;AAChD,4EAAiD;AACjD,8EAAmD;AACnD,oEAAyC;AACzC,oEAAyC;AACzC,yFAA8D;AAC9D,4FAAiE;AACjE,mFAAwD;AACxD,4FAAiE;AACjE,uEAA4C;AAC5C,uEAA4C;AAC5C,0EAA+C;AAC/C,4EAAiD;AACjD,uEAA4C;AAE5C,mCAAmC;AACnC,+DAI8B;AAC9B,+DAI8B;AAC9B,+CAIsB;AACtB,iDAIuB;AACvB,qDAIyB;AACzB,uCAIkB;AAClB,uCAIkB;AAClB,+DAI8B;AAC9B,+DAI8B;AAC9B,mEAIgC;AAChC,iDAIuB;AACvB,qDAIyB;AACzB,+CAIsB;AAET,QAAA,QAAQ,GAAG;IACtB,GAAG,8BAA0B;IAC7B,GAAG,8BAA0B;IAC7B,GAAG,sBAAmB;IACtB,GAAG,uBAAmB;IACtB,GAAG,yBAAqB;IACxB,GAAG,kBAAe;IAClB,GAAG,kBAAe;IAClB,GAAG,8BAA0B;IAC7B,GAAG,8BAAyB;IAC5B,GAAG,gCAA4B;IAC/B,GAAG,uBAAmB;IACtB,GAAG,yBAAqB;IACxB,GAAG,sBAAkB;CACtB,CAAA;AAEY,QAAA,WAAW,GAAG;IACzB,GAAG,iCAA6B;IAChC,GAAG,iCAA6B;IAChC,GAAG,yBAAsB;IACzB,GAAG,0BAAsB;IACzB,GAAG,4BAAwB;IAC3B,GAAG,qBAAkB;IACrB,GAAG,qBAAkB;IACrB,GAAG,iCAA6B;IAChC,GAAG,iCAA4B;IAC/B,GAAG,mCAA+B;IAClC,GAAG,0BAAsB;IACzB,GAAG,4BAAwB;IAC3B,GAAG,yBAAqB;CACzB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,GAAG,+BAA2B;QAC9B,GAAG,+BAA2B;QAC9B,GAAG,uBAAoB;QACvB,GAAG,wBAAoB;QACvB,GAAG,0BAAsB;QACzB,GAAG,mBAAgB;QACnB,GAAG,mBAAgB;QACnB,GAAG,+BAA2B;QAC9B,GAAG,+BAA0B;QAC7B,GAAG,iCAA6B;QAChC,GAAG,wBAAoB;QACvB,GAAG,0BAAsB;QACzB,GAAG,uBAAmB;KACvB;CACF,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './cash-flow-type'\nexport * from './financial-statement/financial-statement'\nexport * from './accounting-category/accounting-category'\nexport * from './transaction/transaction'\nexport * from './bank-account/bank-account'\nexport * from './bank-statement/bank-statement'\nexport * from './payment/payment'\nexport * from './account/account'\nexport * from './accounting-document/accounting-document'\nexport * from './recurring-cash-item/recurring-cash-item'\nexport * from './financial-institution/financial-institution'\nexport * from './fiscal-month/fiscal-month'\nexport * from './fiscal-quarter/fiscal-quarter'\nexport * from './fiscal-year/fiscal-year'\n\n/* EXPORT SERVICES */\nexport * from './transaction/transaction-period'\nexport * from './transaction/transaction-service'\nexport * from './bank-account/bank-account-service'\nexport * from './payment/payment-targets'\nexport * from './payment/payment-service'\nexport * from './financial-statement/financial-statement-line'\nexport * from './financial-statement/financial-statement-service'\nexport * from './recurring-cash-item/recurring-schedule'\nexport * from './recurring-cash-item/recurring-cash-item-service'\nexport * from './bank-statement/bank-source'\nexport * from './bank-statement/bank-parser'\nexport * from './bank-statement/bank-reconcile'\nexport * from './bank-statement/bank-import-type'\nexport * from './bank-statement/bank-import'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport {\n entities as FinancialStatementEntities,\n resolvers as FinancialStatementResolvers,\n subscribers as FinancialStatementSubscribers\n} from './financial-statement'\nimport {\n entities as AccountingCategoryEntities,\n resolvers as AccountingCategoryResolvers,\n subscribers as AccountingCategorySubscribers\n} from './accounting-category'\nimport {\n entities as TransactionEntities,\n resolvers as TransactionResolvers,\n subscribers as TransactionSubscribers\n} from './transaction'\nimport {\n entities as BankAccountEntities,\n resolvers as BankAccountResolvers,\n subscribers as BankAccountSubscribers\n} from './bank-account'\nimport {\n entities as BankStatementEntities,\n resolvers as BankStatementResolvers,\n subscribers as BankStatementSubscribers\n} from './bank-statement'\nimport {\n entities as PaymentEntities,\n resolvers as PaymentResolvers,\n subscribers as PaymentSubscribers\n} from './payment'\nimport {\n entities as AccountEntities,\n resolvers as AccountResolvers,\n subscribers as AccountSubscribers\n} from './account'\nimport {\n entities as AccountingDocumentEntities,\n resolvers as AccountingDocumentResolvers,\n subscribers as AccountingDocumentSubscribers\n} from './accounting-document'\nimport {\n entities as RecurringCashItemEntities,\n resolvers as RecurringCashItemResolvers,\n subscribers as RecurringCashItemSubscribers\n} from './recurring-cash-item'\nimport {\n entities as FinancialInstitutionEntities,\n resolvers as FinancialInstitutionResolvers,\n subscribers as FinancialInstitutionSubscribers\n} from './financial-institution'\nimport {\n entities as FiscalMonthEntities,\n resolvers as FiscalMonthResolvers,\n subscribers as FiscalMonthSubscribers\n} from './fiscal-month'\nimport {\n entities as FiscalQuarterEntities,\n resolvers as FiscalQuarterResolvers,\n subscribers as FiscalQuarterSubscribers\n} from './fiscal-quarter'\nimport {\n entities as FiscalYearEntities,\n resolvers as FiscalYearResolvers,\n subscribers as FiscalYearSubscribers\n} from './fiscal-year'\n\nexport const entities = [\n ...FinancialStatementEntities,\n ...AccountingCategoryEntities,\n ...TransactionEntities,\n ...BankAccountEntities,\n ...BankStatementEntities,\n ...PaymentEntities,\n ...AccountEntities,\n ...AccountingDocumentEntities,\n ...RecurringCashItemEntities,\n ...FinancialInstitutionEntities,\n ...FiscalMonthEntities,\n ...FiscalQuarterEntities,\n ...FiscalYearEntities\n]\n\nexport const subscribers = [\n ...FinancialStatementSubscribers,\n ...AccountingCategorySubscribers,\n ...TransactionSubscribers,\n ...BankAccountSubscribers,\n ...BankStatementSubscribers,\n ...PaymentSubscribers,\n ...AccountSubscribers,\n ...AccountingDocumentSubscribers,\n ...RecurringCashItemSubscribers,\n ...FinancialInstitutionSubscribers,\n ...FiscalMonthSubscribers,\n ...FiscalQuarterSubscribers,\n ...FiscalYearSubscribers\n]\n\nexport const schema = {\n resolverClasses: [\n ...FinancialStatementResolvers,\n ...AccountingCategoryResolvers,\n ...TransactionResolvers,\n ...BankAccountResolvers,\n ...BankStatementResolvers,\n ...PaymentResolvers,\n ...AccountResolvers,\n ...AccountingDocumentResolvers,\n ...RecurringCashItemResolvers,\n ...FinancialInstitutionResolvers,\n ...FiscalMonthResolvers,\n ...FiscalQuarterResolvers,\n ...FiscalYearResolvers\n ]\n}\n"]}
|
|
@@ -2,7 +2,9 @@ import { EntityManager } from 'typeorm';
|
|
|
2
2
|
import { Payment } from './payment';
|
|
3
3
|
import { PaymentPatch } from './payment-type';
|
|
4
4
|
/**
|
|
5
|
-
* Within one purpose a transaction pays no more than itself
|
|
5
|
+
* Within one purpose a transaction pays no more than itself - and for evidence, within each kind of document: the
|
|
6
|
+
* same money is evidenced in full by a tax invoice and by a commercial invoice alike, each telling it its own way.
|
|
7
|
+
* Run after its payments or its amount change.
|
|
6
8
|
*/
|
|
7
9
|
export declare function checkPaymentsWithin(tx: EntityManager, domain: {
|
|
8
10
|
id: string;
|
|
@@ -10,7 +10,9 @@ const payment_targets_1 = require("./payment-targets");
|
|
|
10
10
|
/** Amounts are decimals; a sum within half a cent of the transaction is the transaction. */
|
|
11
11
|
const CENT = 0.005;
|
|
12
12
|
/**
|
|
13
|
-
* Within one purpose a transaction pays no more than itself
|
|
13
|
+
* Within one purpose a transaction pays no more than itself - and for evidence, within each kind of document: the
|
|
14
|
+
* same money is evidenced in full by a tax invoice and by a commercial invoice alike, each telling it its own way.
|
|
15
|
+
* Run after its payments or its amount change.
|
|
14
16
|
*/
|
|
15
17
|
async function checkPaymentsWithin(tx, domain, transactionIds) {
|
|
16
18
|
const ids = [...new Set(transactionIds)];
|
|
@@ -24,7 +26,8 @@ async function checkPaymentsWithin(tx, domain, transactionIds) {
|
|
|
24
26
|
.find({ where: { domain: { id: domain.id }, transaction: { id: (0, typeorm_1.In)(ids) } }, relations: ['transaction'] });
|
|
25
27
|
const paid = new Map();
|
|
26
28
|
for (const payment of payments) {
|
|
27
|
-
const
|
|
29
|
+
const layer = payment.purpose === payment_1.PaymentPurpose.EVIDENCE ? `${payment.purpose}:${payment.targetType}` : payment.purpose;
|
|
30
|
+
const key = `${payment.transaction.id}|${layer}`;
|
|
28
31
|
paid.set(key, (paid.get(key) ?? 0) + Number(payment.amount));
|
|
29
32
|
}
|
|
30
33
|
for (const transaction of transactions) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payment-service.js","sourceRoot":"","sources":["../../../server/service/payment/payment-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, In } from 'typeorm'\n\nimport { Refusal } from '@things-factory/shell'\n\nimport { Transaction } from '../transaction/transaction'\nimport { Payment, PaymentMethod } from './payment'\nimport { paymentTargetOf } from './payment-targets'\nimport { PaymentPatch } from './payment-type'\n\n/** Amounts are decimals; a sum within half a cent of the transaction is the transaction. */\nconst CENT = 0.005\n\n/**\n * Within one purpose a transaction pays no more than itself. Run after its payments or its amount change.\n */\nexport async function checkPaymentsWithin(\n tx: EntityManager,\n domain: { id: string },\n transactionIds: string[]\n): Promise<void> {\n const ids = [...new Set(transactionIds)]\n if (!ids.length) return\n\n const transactions = await tx\n .getRepository(Transaction)\n .find({ where: { domain: { id: domain.id }, id: In(ids) }, select: ['id', 'amount'] })\n const payments = await tx\n .getRepository(Payment)\n .find({ where: { domain: { id: domain.id }, transaction: { id: In(ids) } }, relations: ['transaction'] })\n const paid = new Map<string, number>()\n for (const payment of payments) {\n const key = `${payment.transaction.id}|${payment.purpose}`\n paid.set(key, (paid.get(key) ?? 0) + Number(payment.amount))\n }\n for (const transaction of transactions) {\n for (const [key, sum] of paid) {\n const [id, purpose] = key.split('|')\n if (id !== transaction.id) continue\n const amount = Math.abs(Number(transaction.amount))\n if (sum > amount + CENT) {\n throw new Refusal(\n 'payment-over-transaction',\n { purpose, paid: sum, amount },\n `${purpose}: ${sum} put on a transaction of ${amount}`\n )\n }\n }\n }\n}\n\n/** A patch as the row it writes: the target kind known, the amount more than zero, the purpose the kind's. */\nfunction rowOf(patch: PaymentPatch, isNew: boolean): Partial<Payment> & { transactionId?: string } {\n const row: Record<string, any> = {}\n const transactionId = patch.transaction?.id\n if (isNew && !transactionId)\n throw new Refusal('payment-transaction-required', {}, 'a payment needs the transaction it is a part of')\n if (transactionId) row.transactionId = transactionId\n\n if (patch.targetType !== undefined || isNew) {\n const target = paymentTargetOf(String(patch.targetType ?? ''))\n if (!target)\n throw new Refusal(\n 'payment-target-unknown',\n { targetType: patch.targetType },\n `'${patch.targetType}' is not a kind of target any module registered`\n )\n row.targetType = patch.targetType\n row.purpose = target.purpose\n }\n if (patch.targetId !== undefined || isNew) {\n if (!patch.targetId) throw new Refusal('payment-target-required', {}, 'a payment needs what it paid')\n row.targetId = patch.targetId\n }\n if (patch.amount !== undefined || isNew) {\n const amount = Number(patch.amount)\n if (!(amount > 0)) throw new Refusal('payment-amount-not-positive', {}, 'amount: must be more than zero')\n row.amount = amount\n }\n if (patch.targetAmount !== undefined) {\n const targetAmount = patch.targetAmount === null ? null : Number(patch.targetAmount)\n if (targetAmount !== null && !(targetAmount > 0))\n throw new Refusal('payment-amount-not-positive', {}, 'targetAmount: must be more than zero')\n row.targetAmount = targetAmount\n }\n if (patch.method !== undefined) row.method = patch.method ?? PaymentMethod.PERSON\n if (patch.basis !== undefined) row.basis = patch.basis || null\n if (patch.notes !== undefined) row.notes = patch.notes || null\n return row\n}\n\n/**\n * Apply rows to a domain's payments: `+` (or no id) adds, `M` changes, `-` deletes (softly). Then the general rule\n * (§`checkPaymentsWithin`) and each touched kind's own rules run, in the same database transaction.\n */\nexport async function savePayments(\n tx: EntityManager,\n domain: { id: string },\n user: any,\n patches: PaymentPatch[]\n): Promise<Payment[]> {\n const repo = tx.getRepository(Payment)\n const transactions = tx.getRepository(Transaction)\n const touched = new Map<string, { targetIds: Set<string>; transactionIds: Set<string> }>()\n const touch = (payment: { targetType: string; targetId: string; transactionId?: string }) => {\n const kind = touched.get(payment.targetType) ?? { targetIds: new Set<string>(), transactionIds: new Set<string>() }\n kind.targetIds.add(payment.targetId)\n if (payment.transactionId) kind.transactionIds.add(payment.transactionId)\n touched.set(payment.targetType, kind)\n }\n\n const deletes = patches.filter(one => one.cuFlag === '-' && one.id).map(one => one.id!)\n if (deletes.length) {\n const owned = await repo.find({ where: { domain: { id: domain.id }, id: In(deletes) }, relations: ['transaction'] })\n if (owned.length !== new Set(deletes).size)\n throw new Refusal('payment-not-found', {}, 'a payment was not found - reload and try again')\n for (const one of owned) touch({ ...one, transactionId: one.transaction.id })\n await repo.softDelete({ id: In(deletes) })\n }\n\n const saved: Payment[] = []\n for (const patch of patches.filter(one => one.cuFlag !== '-')) {\n const isNew = !patch.id || patch.cuFlag === '+'\n const { transactionId, ...row } = rowOf(patch, isNew)\n const current = isNew\n ? undefined\n : await repo.findOne({ where: { domain: { id: domain.id }, id: patch.id }, relations: ['transaction'] })\n if (!isNew && !current) throw new Refusal('payment-not-found', {}, 'a payment was not found - reload and try again')\n\n const transactionOf = transactionId ?? current!.transaction.id\n const transaction = await transactions.findOne({ where: { domain: { id: domain.id }, id: transactionOf } })\n if (!transaction)\n throw new Refusal('payment-transaction-not-found', {}, 'the transaction was not found - reload and try again')\n\n const targetType = row.targetType ?? current!.targetType\n const targetId = row.targetId ?? current!.targetId\n const twin = await repo.findOne({\n where: { domain: { id: domain.id }, transaction: { id: transaction.id }, targetType, targetId }\n })\n if (twin && twin.id !== current?.id)\n throw new Refusal(\n 'payment-twice',\n { targetType, targetId },\n 'the transaction already pays this - change that payment instead'\n )\n\n if (current) touch({ ...current, transactionId: current.transaction.id })\n const written = await repo.save({\n ...(current ?? { domain, creator: user }),\n ...row,\n transaction,\n updater: user\n } as Payment)\n touch({ targetType: written.targetType, targetId: written.targetId, transactionId: transaction.id })\n saved.push(written)\n }\n\n await checkPaymentsWithin(\n tx,\n domain,\n [...touched.values()].flatMap(kind => [...kind.transactionIds])\n )\n const touchedOf = (kind: { targetIds: Set<string>; transactionIds: Set<string> }) => ({\n targetIds: [...kind.targetIds],\n transactionIds: [...kind.transactionIds]\n })\n for (const [targetType, kind] of touched) await paymentTargetOf(targetType)?.validate?.(tx, domain, touchedOf(kind))\n for (const [targetType, kind] of touched) await paymentTargetOf(targetType)?.onSaved?.(tx, domain, touchedOf(kind))\n\n return saved\n}\n"]}
|
|
1
|
+
{"version":3,"file":"payment-service.js","sourceRoot":"","sources":["../../../server/service/payment/payment-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, In } from 'typeorm'\n\nimport { Refusal } from '@things-factory/shell'\n\nimport { Transaction } from '../transaction/transaction'\nimport { Payment, PaymentMethod, PaymentPurpose } from './payment'\nimport { paymentTargetOf } from './payment-targets'\nimport { PaymentPatch } from './payment-type'\n\n/** Amounts are decimals; a sum within half a cent of the transaction is the transaction. */\nconst CENT = 0.005\n\n/**\n * Within one purpose a transaction pays no more than itself - and for evidence, within each kind of document: the\n * same money is evidenced in full by a tax invoice and by a commercial invoice alike, each telling it its own way.\n * Run after its payments or its amount change.\n */\nexport async function checkPaymentsWithin(\n tx: EntityManager,\n domain: { id: string },\n transactionIds: string[]\n): Promise<void> {\n const ids = [...new Set(transactionIds)]\n if (!ids.length) return\n\n const transactions = await tx\n .getRepository(Transaction)\n .find({ where: { domain: { id: domain.id }, id: In(ids) }, select: ['id', 'amount'] })\n const payments = await tx\n .getRepository(Payment)\n .find({ where: { domain: { id: domain.id }, transaction: { id: In(ids) } }, relations: ['transaction'] })\n const paid = new Map<string, number>()\n for (const payment of payments) {\n const layer = payment.purpose === PaymentPurpose.EVIDENCE ? `${payment.purpose}:${payment.targetType}` : payment.purpose\n const key = `${payment.transaction.id}|${layer}`\n paid.set(key, (paid.get(key) ?? 0) + Number(payment.amount))\n }\n for (const transaction of transactions) {\n for (const [key, sum] of paid) {\n const [id, purpose] = key.split('|')\n if (id !== transaction.id) continue\n const amount = Math.abs(Number(transaction.amount))\n if (sum > amount + CENT) {\n throw new Refusal(\n 'payment-over-transaction',\n { purpose, paid: sum, amount },\n `${purpose}: ${sum} put on a transaction of ${amount}`\n )\n }\n }\n }\n}\n\n/** A patch as the row it writes: the target kind known, the amount more than zero, the purpose the kind's. */\nfunction rowOf(patch: PaymentPatch, isNew: boolean): Partial<Payment> & { transactionId?: string } {\n const row: Record<string, any> = {}\n const transactionId = patch.transaction?.id\n if (isNew && !transactionId)\n throw new Refusal('payment-transaction-required', {}, 'a payment needs the transaction it is a part of')\n if (transactionId) row.transactionId = transactionId\n\n if (patch.targetType !== undefined || isNew) {\n const target = paymentTargetOf(String(patch.targetType ?? ''))\n if (!target)\n throw new Refusal(\n 'payment-target-unknown',\n { targetType: patch.targetType },\n `'${patch.targetType}' is not a kind of target any module registered`\n )\n row.targetType = patch.targetType\n row.purpose = target.purpose\n }\n if (patch.targetId !== undefined || isNew) {\n if (!patch.targetId) throw new Refusal('payment-target-required', {}, 'a payment needs what it paid')\n row.targetId = patch.targetId\n }\n if (patch.amount !== undefined || isNew) {\n const amount = Number(patch.amount)\n if (!(amount > 0)) throw new Refusal('payment-amount-not-positive', {}, 'amount: must be more than zero')\n row.amount = amount\n }\n if (patch.targetAmount !== undefined) {\n const targetAmount = patch.targetAmount === null ? null : Number(patch.targetAmount)\n if (targetAmount !== null && !(targetAmount > 0))\n throw new Refusal('payment-amount-not-positive', {}, 'targetAmount: must be more than zero')\n row.targetAmount = targetAmount\n }\n if (patch.method !== undefined) row.method = patch.method ?? PaymentMethod.PERSON\n if (patch.basis !== undefined) row.basis = patch.basis || null\n if (patch.notes !== undefined) row.notes = patch.notes || null\n return row\n}\n\n/**\n * Apply rows to a domain's payments: `+` (or no id) adds, `M` changes, `-` deletes (softly). Then the general rule\n * (§`checkPaymentsWithin`) and each touched kind's own rules run, in the same database transaction.\n */\nexport async function savePayments(\n tx: EntityManager,\n domain: { id: string },\n user: any,\n patches: PaymentPatch[]\n): Promise<Payment[]> {\n const repo = tx.getRepository(Payment)\n const transactions = tx.getRepository(Transaction)\n const touched = new Map<string, { targetIds: Set<string>; transactionIds: Set<string> }>()\n const touch = (payment: { targetType: string; targetId: string; transactionId?: string }) => {\n const kind = touched.get(payment.targetType) ?? { targetIds: new Set<string>(), transactionIds: new Set<string>() }\n kind.targetIds.add(payment.targetId)\n if (payment.transactionId) kind.transactionIds.add(payment.transactionId)\n touched.set(payment.targetType, kind)\n }\n\n const deletes = patches.filter(one => one.cuFlag === '-' && one.id).map(one => one.id!)\n if (deletes.length) {\n const owned = await repo.find({ where: { domain: { id: domain.id }, id: In(deletes) }, relations: ['transaction'] })\n if (owned.length !== new Set(deletes).size)\n throw new Refusal('payment-not-found', {}, 'a payment was not found - reload and try again')\n for (const one of owned) touch({ ...one, transactionId: one.transaction.id })\n await repo.softDelete({ id: In(deletes) })\n }\n\n const saved: Payment[] = []\n for (const patch of patches.filter(one => one.cuFlag !== '-')) {\n const isNew = !patch.id || patch.cuFlag === '+'\n const { transactionId, ...row } = rowOf(patch, isNew)\n const current = isNew\n ? undefined\n : await repo.findOne({ where: { domain: { id: domain.id }, id: patch.id }, relations: ['transaction'] })\n if (!isNew && !current) throw new Refusal('payment-not-found', {}, 'a payment was not found - reload and try again')\n\n const transactionOf = transactionId ?? current!.transaction.id\n const transaction = await transactions.findOne({ where: { domain: { id: domain.id }, id: transactionOf } })\n if (!transaction)\n throw new Refusal('payment-transaction-not-found', {}, 'the transaction was not found - reload and try again')\n\n const targetType = row.targetType ?? current!.targetType\n const targetId = row.targetId ?? current!.targetId\n const twin = await repo.findOne({\n where: { domain: { id: domain.id }, transaction: { id: transaction.id }, targetType, targetId }\n })\n if (twin && twin.id !== current?.id)\n throw new Refusal(\n 'payment-twice',\n { targetType, targetId },\n 'the transaction already pays this - change that payment instead'\n )\n\n if (current) touch({ ...current, transactionId: current.transaction.id })\n const written = await repo.save({\n ...(current ?? { domain, creator: user }),\n ...row,\n transaction,\n updater: user\n } as Payment)\n touch({ targetType: written.targetType, targetId: written.targetId, transactionId: transaction.id })\n saved.push(written)\n }\n\n await checkPaymentsWithin(\n tx,\n domain,\n [...touched.values()].flatMap(kind => [...kind.transactionIds])\n )\n const touchedOf = (kind: { targetIds: Set<string>; transactionIds: Set<string> }) => ({\n targetIds: [...kind.targetIds],\n transactionIds: [...kind.transactionIds]\n })\n for (const [targetType, kind] of touched) await paymentTargetOf(targetType)?.validate?.(tx, domain, touchedOf(kind))\n for (const [targetType, kind] of touched) await paymentTargetOf(targetType)?.onSaved?.(tx, domain, touchedOf(kind))\n\n return saved\n}\n"]}
|
|
@@ -20,10 +20,15 @@ export type TransactionInput = Partial<Omit<Transaction, 'bankAccount' | 'accoun
|
|
|
20
20
|
id: string;
|
|
21
21
|
} | null;
|
|
22
22
|
};
|
|
23
|
+
/** A line that came from a bank statement keeps the statement's facts (§`BANK_FACTS`). */
|
|
24
|
+
export declare function protectBankFacts(current: Partial<Transaction> & {
|
|
25
|
+
bankAccountId?: string;
|
|
26
|
+
}, patch: Record<string, any>): void;
|
|
23
27
|
/**
|
|
24
28
|
* Apply rows to a domain's transactions: `+` (or no id) adds, `M` changes, `-` deletes (softly - a financial row
|
|
25
29
|
* that was deleted stays readable). A transaction that pays something, or is paid, is unlinked before it is
|
|
26
30
|
* deleted; when its money changes, its payments are checked again - the general rule and each target kind's own.
|
|
31
|
+
* A line from a bank statement keeps the statement's facts (§`protectBankFacts`).
|
|
27
32
|
*/
|
|
28
33
|
export declare function saveTransactions(tx: EntityManager, domain: {
|
|
29
34
|
id: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.protectBankFacts = protectBankFacts;
|
|
3
4
|
exports.saveTransactions = saveTransactions;
|
|
4
5
|
const typeorm_1 = require("typeorm");
|
|
5
6
|
const shell_1 = require("@things-factory/shell");
|
|
@@ -10,6 +11,24 @@ const payment_service_1 = require("../payment/payment-service");
|
|
|
10
11
|
const payment_targets_1 = require("../payment/payment-targets");
|
|
11
12
|
const transaction_1 = require("./transaction");
|
|
12
13
|
const transaction_period_1 = require("./transaction-period");
|
|
14
|
+
/** What a bank statement says of a line - changed only by a statement; what a person writes beside them stays open. */
|
|
15
|
+
const BANK_FACTS = ['transactionDate', 'type', 'amount', 'balance', 'currency', 'bankAccount'];
|
|
16
|
+
/** A line that came from a bank statement keeps the statement's facts (§`BANK_FACTS`). */
|
|
17
|
+
function protectBankFacts(current, patch) {
|
|
18
|
+
if (current.source !== transaction_1.TransactionSource.BANK_STATEMENT)
|
|
19
|
+
return;
|
|
20
|
+
for (const field of BANK_FACTS) {
|
|
21
|
+
if (!(field in patch))
|
|
22
|
+
continue;
|
|
23
|
+
const before = field === 'bankAccount' ? current.bankAccountId ?? current.bankAccount?.id : current[field];
|
|
24
|
+
const after = field === 'bankAccount' ? patch.bankAccount?.id : patch[field];
|
|
25
|
+
const equal = ['amount', 'balance'].includes(field)
|
|
26
|
+
? (before == null && after == null) || (before != null && after != null && Number(before) === Number(after))
|
|
27
|
+
: before === after;
|
|
28
|
+
if (!equal)
|
|
29
|
+
throw new shell_1.Refusal('bank-facts-locked', {}, 'Bank facts can only be updated from a statement; annotations remain editable');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
13
32
|
/** What its payments depend on - they are checked again, and their owners follow, when one of these changes. */
|
|
14
33
|
const FACTS = ['amount', 'type', 'currency', 'transactionDate'];
|
|
15
34
|
/**
|
|
@@ -73,6 +92,7 @@ async function paymentsOn(tx, domain, ids) {
|
|
|
73
92
|
* Apply rows to a domain's transactions: `+` (or no id) adds, `M` changes, `-` deletes (softly - a financial row
|
|
74
93
|
* that was deleted stays readable). A transaction that pays something, or is paid, is unlinked before it is
|
|
75
94
|
* deleted; when its money changes, its payments are checked again - the general rule and each target kind's own.
|
|
95
|
+
* A line from a bank statement keeps the statement's facts (§`protectBankFacts`).
|
|
76
96
|
*/
|
|
77
97
|
async function saveTransactions(tx, domain, user, inputs) {
|
|
78
98
|
const repo = tx.getRepository(transaction_1.Transaction);
|
|
@@ -92,6 +112,8 @@ async function saveTransactions(tx, domain, user, inputs) {
|
|
|
92
112
|
const current = isNew ? undefined : await repo.findOne({ where: { domain: { id: domain.id }, id: input.id } });
|
|
93
113
|
if (!isNew && !current)
|
|
94
114
|
throw new shell_1.Refusal('transaction-not-found', {}, 'a transaction was not found - reload and try again');
|
|
115
|
+
if (current)
|
|
116
|
+
protectBankFacts(current, input);
|
|
95
117
|
const row = rowOf(input, current);
|
|
96
118
|
if (row.bankAccount?.id && !('currency' in input)) {
|
|
97
119
|
const account = await tx
|