@things-factory/accounting 10.1.155 → 10.1.156
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-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/activities/activity-expenditure.d.ts +186 -0
- package/dist-server/activities/activity-expenditure.js +122 -0
- package/dist-server/activities/activity-expenditure.js.map +1 -0
- package/dist-server/activities/expenditure-activity-names.d.ts +4 -0
- package/dist-server/activities/expenditure-activity-names.js +13 -0
- package/dist-server/activities/expenditure-activity-names.js.map +1 -0
- package/dist-server/activities/index.d.ts +2 -1
- package/dist-server/activities/index.js +19 -12
- package/dist-server/activities/index.js.map +1 -1
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +1 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/service/account/account-match.d.ts +5 -3
- package/dist-server/service/account/account-match.js +7 -3
- package/dist-server/service/account/account-match.js.map +1 -1
- package/dist-server/service/bank-statement/bank-import.js +9 -3
- package/dist-server/service/bank-statement/bank-import.js.map +1 -1
- package/dist-server/service/bank-statement/bank-source.js +2 -1
- package/dist-server/service/bank-statement/bank-source.js.map +1 -1
- package/dist-server/service/counterparty-account/counterparty-account-query.d.ts +6 -0
- package/dist-server/service/counterparty-account/counterparty-account-query.js +48 -0
- package/dist-server/service/counterparty-account/counterparty-account-query.js.map +1 -0
- package/dist-server/service/counterparty-account/counterparty-account-rules.d.ts +46 -0
- package/dist-server/service/counterparty-account/counterparty-account-rules.js +76 -0
- package/dist-server/service/counterparty-account/counterparty-account-rules.js.map +1 -0
- package/dist-server/service/counterparty-account/counterparty-account-service.d.ts +22 -0
- package/dist-server/service/counterparty-account/counterparty-account-service.js +66 -0
- package/dist-server/service/counterparty-account/counterparty-account-service.js.map +1 -0
- package/dist-server/service/counterparty-account/counterparty-account-type.d.ts +9 -0
- package/dist-server/service/counterparty-account/counterparty-account-type.js +40 -0
- package/dist-server/service/counterparty-account/counterparty-account-type.js.map +1 -0
- package/dist-server/service/counterparty-account/index.d.ts +4 -0
- package/dist-server/service/counterparty-account/index.js +8 -0
- package/dist-server/service/counterparty-account/index.js.map +1 -0
- package/dist-server/service/expenditure-request/expenditure-payment-state.d.ts +52 -0
- package/dist-server/service/expenditure-request/expenditure-payment-state.js +100 -0
- package/dist-server/service/expenditure-request/expenditure-payment-state.js.map +1 -0
- package/dist-server/service/expenditure-request/expenditure-request-item.d.ts +41 -0
- package/dist-server/service/expenditure-request/expenditure-request-item.js +149 -0
- package/dist-server/service/expenditure-request/expenditure-request-item.js.map +1 -0
- package/dist-server/service/expenditure-request/expenditure-request-mutation.d.ts +18 -0
- package/dist-server/service/expenditure-request/expenditure-request-mutation.js +140 -0
- package/dist-server/service/expenditure-request/expenditure-request-mutation.js.map +1 -0
- package/dist-server/service/expenditure-request/expenditure-request-query.d.ts +46 -0
- package/dist-server/service/expenditure-request/expenditure-request-query.js +365 -0
- package/dist-server/service/expenditure-request/expenditure-request-query.js.map +1 -0
- package/dist-server/service/expenditure-request/expenditure-request-rules.d.ts +128 -0
- package/dist-server/service/expenditure-request/expenditure-request-rules.js +183 -0
- package/dist-server/service/expenditure-request/expenditure-request-rules.js.map +1 -0
- package/dist-server/service/expenditure-request/expenditure-request-service.d.ts +92 -0
- package/dist-server/service/expenditure-request/expenditure-request-service.js +488 -0
- package/dist-server/service/expenditure-request/expenditure-request-service.js.map +1 -0
- package/dist-server/service/expenditure-request/expenditure-request-type.d.ts +85 -0
- package/dist-server/service/expenditure-request/expenditure-request-type.js +312 -0
- package/dist-server/service/expenditure-request/expenditure-request-type.js.map +1 -0
- package/dist-server/service/expenditure-request/expenditure-request.d.ts +67 -0
- package/dist-server/service/expenditure-request/expenditure-request.js +228 -0
- package/dist-server/service/expenditure-request/expenditure-request.js.map +1 -0
- package/dist-server/service/expenditure-request/expenditure-target.d.ts +1 -0
- package/dist-server/service/expenditure-request/expenditure-target.js +55 -0
- package/dist-server/service/expenditure-request/expenditure-target.js.map +1 -0
- package/dist-server/service/expenditure-request/index.d.ts +8 -0
- package/dist-server/service/expenditure-request/index.js +12 -0
- package/dist-server/service/expenditure-request/index.js.map +1 -0
- package/dist-server/service/index.d.ts +9 -2
- package/dist-server/service/index.js +18 -3
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -6
- package/translations/en.json +58 -1
- package/translations/es.json +17 -1
- package/translations/ja.json +17 -1
- package/translations/ko.json +58 -1
- package/translations/ms.json +17 -1
- package/translations/zh.json +17 -1
- package/dist-client/activities/activity-expense-edit.d.ts +0 -14
- package/dist-client/activities/activity-expense-edit.js +0 -85
- package/dist-client/activities/activity-expense-edit.js.map +0 -1
- package/dist-client/activities/activity-expense-view.d.ts +0 -14
- package/dist-client/activities/activity-expense-view.js +0 -85
- package/dist-client/activities/activity-expense-view.js.map +0 -1
- package/dist-server/activities/activity-expense.d.ts +0 -58
- package/dist-server/activities/activity-expense.js +0 -150
- package/dist-server/activities/activity-expense.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bank-import.js","sourceRoot":"","sources":["../../../server/service/bank-statement/bank-import.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'crypto'\nimport { EntityManager, IsNull, Not } from 'typeorm'\n\nimport { Refusal } from '@things-factory/shell'\n\nimport { BankAccount } from '../bank-account/bank-account'\nimport { Transaction, TransactionCategory, TransactionSource } from '../transaction/transaction'\nimport { saveTransactions, TransactionInput } from '../transaction/transaction-service'\nimport { BankStatement } from './bank-statement'\nimport { parseBankWorkbook } from './bank-parser'\nimport { LedgerCandidate, placeHeldLines, reconcileBankLines } from './bank-reconcile'\nimport { BankHeldRow, BankImportResult, BankRowAssignment } from './bank-import-type'\n\nexport const BANK_FILE_LIMIT = 10 * 1024 * 1024\n\n/**\n * Bring a bank statement into the books. A line already in them (its fingerprint) is left; a line a typed transaction\n * already tells is given to that transaction - its bank facts replace the typed ones, the typed ones kept beside them\n * (`bankReconciliationBefore`); a line that could be more than one transaction is held for a person to place; the rest\n * are added. The lines are written through `saveTransactions`, so what a transaction paid is checked again and the\n * owners of its targets follow a changed day or amount. A preview writes nothing.\n */\nexport async function importBankStatement(\n tx: EntityManager,\n domain: { id: string },\n user: any,\n file: { data: Buffer; filename: string },\n preview: boolean,\n assignments: BankRowAssignment[] | null\n): Promise<BankImportResult> {\n const { data, filename } = file\n if (data.length > BANK_FILE_LIMIT) throw new Refusal('bank-file-too-large', {}, 'Upload a bank file smaller than 10MB')\n\n let parsed = parseBankWorkbook(data)\n const accounts = await tx.getRepository(BankAccount).find({ where: { domain: { id: domain.id }, active: true } })\n const digits = (value?: string | null) => (value ?? '').replace(/\\D/g, '')\n const accountFor = (currency: string) => {\n const found = accounts.filter(\n account => account.currency === currency && account.bankName === parsed.bankName && digits(account.accountNumber) === digits(parsed.accountNumber)\n )\n if (found.length !== 1)\n throw new Refusal('bank-account-not-registered', { bank: parsed.bankName, account: parsed.accountNumber, currency }, 'Register the statement account and currency first')\n return found[0]\n }\n /* Every moving line has its account before anything is written; a line that moved nothing needs none. */\n const moving = parsed.lines.filter(line => line.amount > 0)\n for (const line of moving) accountFor(line.currency)\n const timezoneOf = (currency: string) => accountFor(currency).timezone || 'Asia/Seoul'\n parsed = parseBankWorkbook(data, currency => (moving.some(line => line.currency === currency) ? timezoneOf(currency) : 'Asia/Seoul'))\n\n const statements = tx.getRepository(BankStatement)\n const fileHash = createHash('sha256').update(data).digest('hex')\n let statement = await statements.findOne({ where: { domain: { id: domain.id }, fileHash }, withDeleted: true })\n if (!statement && !preview)\n statement = await statements.save(\n statements.create({\n domain,\n creator: user,\n updater: user,\n fileHash,\n filename,\n bankName: parsed.bankName,\n accountNumber: parsed.accountNumber,\n rawRows: parsed.rawRows,\n timezones: Object.fromEntries([...new Set(moving.map(line => line.currency))].map(currency => [currency, timezoneOf(currency)])),\n periodStart: parsed.periodStart ?? undefined,\n periodEnd: parsed.periodEnd ?? undefined\n } as Partial<BankStatement>)\n )\n\n const transactions = tx.getRepository(Transaction)\n const ledger = await transactions.find({ where: { domain: { id: domain.id }, category: TransactionCategory.CONFIRMED } })\n const known = await transactions.find({ where: { domain: { id: domain.id }, bankFingerprint: Not(IsNull()) }, withDeleted: true, select: ['id', 'bankFingerprint'] })\n const fingerprints = new Set(known.map(row => row.bankFingerprint))\n const plan = reconcileBankLines(\n parsed.lines.filter(line => line.amount > 0 && !fingerprints.has(line.fingerprint)),\n ledger as LedgerCandidate[] /* confirmed: each has its day */\n )\n if (!placeHeldLines(plan, (assignments ?? []).map(one => ({ fingerprint: one.fingerprint, transactionId: one.transactionId }))))\n throw new Refusal('bank-assignment-invalid', {}, 'A held bank line can only be placed on one of its candidates, once')\n const byFingerprint = new Map(plan.map(item => [item.line.fingerprint, item]))\n\n const result: BankImportResult = { added: 0, existing: 0, updated: 0, review: 0, details: '', zeroEvents: 0, filename, accountNumber: parsed.accountNumber, held: [] as BankHeldRow[] }\n const details: string[] = []\n const inputs: TransactionInput[] = []\n for (const line of parsed.lines) {\n if (!line.amount) {\n result.zeroEvents++\n continue\n }\n if (fingerprints.has(line.fingerprint)) {\n result.existing++\n continue\n }\n const item = byFingerprint.get(line.fingerprint)!\n if (!item.match && item.candidates.length) {\n result.held.push({\n fingerprint: line.fingerprint,\n row: line.row,\n transactionDate: line.transactionDate,\n type: line.type,\n amount: line.amount,\n currency: line.currency,\n bankDescription: line.bankDescription ?? undefined,\n candidates: item.candidates.map(old => ({ id: old.id, transactionDate: old.transactionDate, description: old.description, amount: Number(old.amount) }))\n })\n result.review++\n details.push(`${line.row}: ${line.transactionDate} ${line.type} ${line.amount} ${line.currency} ? ${item.candidates.map(old => `${old.transactionDate} ${old.description ?? ''} [${old.id}]`).join(', ')}`)\n continue\n }\n if (item.match) result.updated++\n else result.added++\n if (preview) {\n if (item.match) details.push(`${line.row}: ${item.match.transactionDate} ${item.match.description ?? ''} → ${line.transactionDate} ${line.amount} ${line.currency}`)\n continue\n }\n\n const { raw, row, fingerprint, bankLocalTime, ...facts } = line\n const matched = item.match ? ledger.find(old => old.id === item.match!.id) : undefined\n inputs.push({\n ...(matched\n ? {\n id: matched.id,\n cuFlag: 'M',\n bankReconciliationBefore: {\n accountId: matched.bankAccountId,\n transactionDate: matched.transactionDate,\n transactionAt: matched.transactionAt,\n amount: matched.amount,\n balance: matched.balance,\n currency: matched.currency,\n type: matched.type,\n source: matched.source,\n reconciledAt: new Date().toISOString(),\n reconciledBy: user?.id\n }\n }\n : { cuFlag: '+', category: TransactionCategory.CONFIRMED }),\n ...(facts as any),\n transactionAt: line.transactionAt ?? null,\n bankAccount: { id: accountFor(line.currency).id },\n source: TransactionSource.BANK_STATEMENT,\n bankStatement: statement ? { id: statement.id } : null,\n sourceFile: filename,\n sourceRow: row,\n bankFingerprint: fingerprint,\n description: matched?.description ?? line.bankDescription ?? line.bankKind ?? null,\n counterpartyName: matched?.counterpartyName ?? line.bankCounterpartyName ?? null\n })\n }\n if (inputs.length) await saveTransactions(tx, domain, user, inputs)\n\n result.details = details.join('\\n')\n return result\n}\n"]}
|
|
1
|
+
{"version":3,"file":"bank-import.js","sourceRoot":"","sources":["../../../server/service/bank-statement/bank-import.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'crypto'\nimport { EntityManager, IsNull, Not } from 'typeorm'\n\nimport { accountKey } from '@things-factory/biz-base/counterparty-names'\nimport { Refusal } from '@things-factory/shell'\n\nimport { BankAccount } from '../bank-account/bank-account'\nimport { counterpartyOfAccount } from '../counterparty-account/counterparty-account-rules'\nimport { accountOwners } from '../counterparty-account/counterparty-account-service'\nimport { Transaction, TransactionCategory, TransactionSource } from '../transaction/transaction'\nimport { saveTransactions, TransactionInput } from '../transaction/transaction-service'\nimport { BankStatement } from './bank-statement'\nimport { parseBankWorkbook } from './bank-parser'\nimport { LedgerCandidate, placeHeldLines, reconcileBankLines } from './bank-reconcile'\nimport { BankHeldRow, BankImportResult, BankRowAssignment } from './bank-import-type'\n\nexport const BANK_FILE_LIMIT = 10 * 1024 * 1024\n\n/**\n * Bring a bank statement into the books. A line already in them (its fingerprint) is left; a line a typed transaction\n * already tells is given to that transaction - its bank facts replace the typed ones, the typed ones kept beside them\n * (`bankReconciliationBefore`); a line that could be more than one transaction is held for a person to place; the rest\n * are added. The lines are written through `saveTransactions`, so what a transaction paid is checked again and the\n * owners of its targets follow a changed day or amount. A preview writes nothing.\n */\nexport async function importBankStatement(\n tx: EntityManager,\n domain: { id: string },\n user: any,\n file: { data: Buffer; filename: string },\n preview: boolean,\n assignments: BankRowAssignment[] | null\n): Promise<BankImportResult> {\n const { data, filename } = file\n if (data.length > BANK_FILE_LIMIT) throw new Refusal('bank-file-too-large', {}, 'Upload a bank file smaller than 10MB')\n\n let parsed = parseBankWorkbook(data)\n const accounts = await tx.getRepository(BankAccount).find({ where: { domain: { id: domain.id }, active: true } })\n const accountFor = (currency: string) => {\n const found = accounts.filter(\n account => account.currency === currency && account.bankName === parsed.bankName && accountKey(account.accountNumber) === accountKey(parsed.accountNumber)\n )\n if (found.length !== 1)\n throw new Refusal('bank-account-not-registered', { bank: parsed.bankName, account: parsed.accountNumber, currency }, 'Register the statement account and currency first')\n return found[0]\n }\n /* Every moving line has its account before anything is written; a line that moved nothing needs none. */\n const moving = parsed.lines.filter(line => line.amount > 0)\n for (const line of moving) accountFor(line.currency)\n const timezoneOf = (currency: string) => accountFor(currency).timezone || 'Asia/Seoul'\n parsed = parseBankWorkbook(data, currency => (moving.some(line => line.currency === currency) ? timezoneOf(currency) : 'Asia/Seoul'))\n\n const statements = tx.getRepository(BankStatement)\n const fileHash = createHash('sha256').update(data).digest('hex')\n let statement = await statements.findOne({ where: { domain: { id: domain.id }, fileHash }, withDeleted: true })\n if (!statement && !preview)\n statement = await statements.save(\n statements.create({\n domain,\n creator: user,\n updater: user,\n fileHash,\n filename,\n bankName: parsed.bankName,\n accountNumber: parsed.accountNumber,\n rawRows: parsed.rawRows,\n timezones: Object.fromEntries([...new Set(moving.map(line => line.currency))].map(currency => [currency, timezoneOf(currency)])),\n periodStart: parsed.periodStart ?? undefined,\n periodEnd: parsed.periodEnd ?? undefined\n } as Partial<BankStatement>)\n )\n\n const transactions = tx.getRepository(Transaction)\n const ledger = await transactions.find({ where: { domain: { id: domain.id }, category: TransactionCategory.CONFIRMED } })\n const known = await transactions.find({ where: { domain: { id: domain.id }, bankFingerprint: Not(IsNull()) }, withDeleted: true, select: ['id', 'bankFingerprint'] })\n const fingerprints = new Set(known.map(row => row.bankFingerprint))\n const plan = reconcileBankLines(\n parsed.lines.filter(line => line.amount > 0 && !fingerprints.has(line.fingerprint)),\n ledger as LedgerCandidate[] /* confirmed: each has its day */\n )\n if (!placeHeldLines(plan, (assignments ?? []).map(one => ({ fingerprint: one.fingerprint, transactionId: one.transactionId }))))\n throw new Refusal('bank-assignment-invalid', {}, 'A held bank line can only be placed on one of its candidates, once')\n const byFingerprint = new Map(plan.map(item => [item.line.fingerprint, item]))\n\n /* A line paying an account a counterparty registered is that counterparty's (ADR-0134 §2-3). */\n const owners = await accountOwners(tx, domain)\n\n const result: BankImportResult = { added: 0, existing: 0, updated: 0, review: 0, details: '', zeroEvents: 0, filename, accountNumber: parsed.accountNumber, held: [] as BankHeldRow[] }\n const details: string[] = []\n const inputs: TransactionInput[] = []\n for (const line of parsed.lines) {\n if (!line.amount) {\n result.zeroEvents++\n continue\n }\n if (fingerprints.has(line.fingerprint)) {\n result.existing++\n continue\n }\n const item = byFingerprint.get(line.fingerprint)!\n if (!item.match && item.candidates.length) {\n result.held.push({\n fingerprint: line.fingerprint,\n row: line.row,\n transactionDate: line.transactionDate,\n type: line.type,\n amount: line.amount,\n currency: line.currency,\n bankDescription: line.bankDescription ?? undefined,\n candidates: item.candidates.map(old => ({ id: old.id, transactionDate: old.transactionDate, description: old.description, amount: Number(old.amount) }))\n })\n result.review++\n details.push(`${line.row}: ${line.transactionDate} ${line.type} ${line.amount} ${line.currency} ? ${item.candidates.map(old => `${old.transactionDate} ${old.description ?? ''} [${old.id}]`).join(', ')}`)\n continue\n }\n if (item.match) result.updated++\n else result.added++\n if (preview) {\n if (item.match) details.push(`${line.row}: ${item.match.transactionDate} ${item.match.description ?? ''} → ${line.transactionDate} ${line.amount} ${line.currency}`)\n continue\n }\n\n const { raw, row, fingerprint, bankLocalTime, ...facts } = line\n const matched = item.match ? ledger.find(old => old.id === item.match!.id) : undefined\n const payee = matched?.counterpartyId ? undefined : counterpartyOfAccount(line.bankCounterpartyAccount, owners)\n inputs.push({\n ...(matched\n ? {\n id: matched.id,\n cuFlag: 'M',\n bankReconciliationBefore: {\n accountId: matched.bankAccountId,\n transactionDate: matched.transactionDate,\n transactionAt: matched.transactionAt,\n amount: matched.amount,\n balance: matched.balance,\n currency: matched.currency,\n type: matched.type,\n source: matched.source,\n reconciledAt: new Date().toISOString(),\n reconciledBy: user?.id\n }\n }\n : { cuFlag: '+', category: TransactionCategory.CONFIRMED }),\n ...(facts as any),\n transactionAt: line.transactionAt ?? null,\n bankAccount: { id: accountFor(line.currency).id },\n source: TransactionSource.BANK_STATEMENT,\n bankStatement: statement ? { id: statement.id } : null,\n sourceFile: filename,\n sourceRow: row,\n bankFingerprint: fingerprint,\n description: matched?.description ?? line.bankDescription ?? line.bankKind ?? null,\n counterpartyName: matched?.counterpartyName ?? line.bankCounterpartyName ?? null,\n ...(payee ? { counterparty: { id: payee } } : {})\n })\n }\n if (inputs.length) await saveTransactions(tx, domain, user, inputs)\n\n result.details = details.join('\\n')\n return result\n}\n"]}
|
|
@@ -6,6 +6,7 @@ exports.bankFingerprint = bankFingerprint;
|
|
|
6
6
|
const tslib_1 = require("tslib");
|
|
7
7
|
const moment_timezone_1 = tslib_1.__importDefault(require("moment-timezone"));
|
|
8
8
|
const crypto_1 = require("crypto");
|
|
9
|
+
const counterparty_names_1 = require("@things-factory/biz-base/counterparty-names");
|
|
9
10
|
const shell_1 = require("@things-factory/shell");
|
|
10
11
|
/** Multipart headers may expose UTF-8 filename bytes as Latin-1 text. */
|
|
11
12
|
function bankFilename(value) {
|
|
@@ -47,7 +48,7 @@ function bankTimestamp(value, timezone = 'Asia/Seoul') {
|
|
|
47
48
|
*/
|
|
48
49
|
function bankFingerprint(line, occurrence = 0) {
|
|
49
50
|
return (0, crypto_1.createHash)('sha256').update(JSON.stringify([
|
|
50
|
-
|
|
51
|
+
(0, counterparty_names_1.accountKey)(line.accountNumber), line.currency, line.bankLocalTime,
|
|
51
52
|
line.type, line.amount.toFixed(2), line.balance.toFixed(2),
|
|
52
53
|
(line.bankDescription ?? '').normalize('NFKC').trim(), (line.bankKind ?? '').normalize('NFKC').trim(), occurrence
|
|
53
54
|
])).digest('hex');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bank-source.js","sourceRoot":"","sources":["../../../server/service/bank-statement/bank-source.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"bank-source.js","sourceRoot":"","sources":["../../../server/service/bank-statement/bank-source.ts"],"names":[],"mappings":"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","sourcesContent":["import moment from 'moment-timezone'\nimport { createHash } from 'crypto'\nimport { accountKey } from '@things-factory/biz-base/counterparty-names'\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 accountKey(line.accountNumber), 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"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Transaction } from '../transaction/transaction';
|
|
2
|
+
import { CounterpartyAccountSuggestion } from './counterparty-account-type';
|
|
3
|
+
export declare class CounterpartyAccountQuery {
|
|
4
|
+
counterpartyAccountSuggestions(counterpartyId: string, context: ResolverContext): Promise<CounterpartyAccountSuggestion[]>;
|
|
5
|
+
paidToUnregisteredAccount(line: Transaction): Promise<boolean>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CounterpartyAccountQuery = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const type_graphql_1 = require("type-graphql");
|
|
6
|
+
const shell_1 = require("@things-factory/shell");
|
|
7
|
+
const transaction_1 = require("../transaction/transaction");
|
|
8
|
+
const counterparty_account_rules_1 = require("./counterparty-account-rules");
|
|
9
|
+
const counterparty_account_service_1 = require("./counterparty-account-service");
|
|
10
|
+
const counterparty_account_type_1 = require("./counterparty-account-type");
|
|
11
|
+
let CounterpartyAccountQuery = class CounterpartyAccountQuery {
|
|
12
|
+
async counterpartyAccountSuggestions(counterpartyId, context) {
|
|
13
|
+
return await (0, counterparty_account_service_1.counterpartyAccountSuggestions)((0, shell_1.getRepository)(transaction_1.Transaction).manager, context.state.domain, counterpartyId);
|
|
14
|
+
}
|
|
15
|
+
async paidToUnregisteredAccount(line) {
|
|
16
|
+
if (!line.counterpartyId || !line.bankCounterpartyAccount)
|
|
17
|
+
return false;
|
|
18
|
+
const manager = (0, shell_1.getRepository)(transaction_1.Transaction).manager;
|
|
19
|
+
const domain = { id: line.domainId };
|
|
20
|
+
const registered = (await (0, counterparty_account_service_1.registeredAccountsOf)(manager, domain, [line.counterpartyId])).get(line.counterpartyId) ?? [];
|
|
21
|
+
if (!registered.length)
|
|
22
|
+
return false;
|
|
23
|
+
return (0, counterparty_account_rules_1.paidToUnregisteredAccount)(line.bankCounterpartyAccount, registered, (await (0, counterparty_account_service_1.accountOwners)(manager, domain)).own);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
exports.CounterpartyAccountQuery = CounterpartyAccountQuery;
|
|
27
|
+
tslib_1.__decorate([
|
|
28
|
+
(0, type_graphql_1.Directive)('@privilege(category: "accounting", privilege: "query", domainOwnerGranted: true)'),
|
|
29
|
+
(0, type_graphql_1.Query)(returns => [counterparty_account_type_1.CounterpartyAccountSuggestion], { description: 'The accounts the bank lines say a counterparty is paid into, not registered yet' }),
|
|
30
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('counterpartyId')),
|
|
31
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
32
|
+
tslib_1.__metadata("design:type", Function),
|
|
33
|
+
tslib_1.__metadata("design:paramtypes", [String, Object]),
|
|
34
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
35
|
+
], CounterpartyAccountQuery.prototype, "counterpartyAccountSuggestions", null);
|
|
36
|
+
tslib_1.__decorate([
|
|
37
|
+
(0, type_graphql_1.FieldResolver)(type => Boolean, {
|
|
38
|
+
description: 'The line paid an account its counterparty has not registered, though it has registered some (ADR-0134)'
|
|
39
|
+
}),
|
|
40
|
+
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
41
|
+
tslib_1.__metadata("design:type", Function),
|
|
42
|
+
tslib_1.__metadata("design:paramtypes", [transaction_1.Transaction]),
|
|
43
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
44
|
+
], CounterpartyAccountQuery.prototype, "paidToUnregisteredAccount", null);
|
|
45
|
+
exports.CounterpartyAccountQuery = CounterpartyAccountQuery = tslib_1.__decorate([
|
|
46
|
+
(0, type_graphql_1.Resolver)(transaction_1.Transaction)
|
|
47
|
+
], CounterpartyAccountQuery);
|
|
48
|
+
//# sourceMappingURL=counterparty-account-query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"counterparty-account-query.js","sourceRoot":"","sources":["../../../server/service/counterparty-account/counterparty-account-query.ts"],"names":[],"mappings":";;;;AAAA,+CAAwF;AAExF,iDAAqD;AAErD,4DAAwD;AACxD,6EAAwE;AACxE,iFAAoH;AACpH,2EAA2E;AAGpE,IAAM,wBAAwB,GAA9B,MAAM,wBAAwB;IAG7B,AAAN,KAAK,CAAC,8BAA8B,CAAwB,cAAsB,EAAS,OAAwB;QACjH,OAAO,MAAM,IAAA,6DAA8B,EAAC,IAAA,qBAAa,EAAC,yBAAW,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IACvH,CAAC;IAKK,AAAN,KAAK,CAAC,yBAAyB,CAAS,IAAiB;QACvD,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,IAAI,CAAC,uBAAuB;YAAE,OAAO,KAAK,CAAA;QACvE,MAAM,OAAO,GAAG,IAAA,qBAAa,EAAC,yBAAW,CAAC,CAAC,OAAO,CAAA;QAClD,MAAM,MAAM,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,QAAS,EAAE,CAAA;QACrC,MAAM,UAAU,GAAG,CAAC,MAAM,IAAA,mDAAoB,EAAC,OAAO,EAAE,MAAM,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAA;QACtH,IAAI,CAAC,UAAU,CAAC,MAAM;YAAE,OAAO,KAAK,CAAA;QACpC,OAAO,IAAA,sDAAyB,EAAC,IAAI,CAAC,uBAAuB,EAAE,UAAU,EAAE,CAAC,MAAM,IAAA,4CAAa,EAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACxH,CAAC;CACF,CAAA;AAlBY,4DAAwB;AAG7B;IAFL,IAAA,wBAAS,EAAC,kFAAkF,CAAC;IAC7F,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,yDAA6B,CAAC,EAAE,EAAE,WAAW,EAAE,iFAAiF,EAAE,CAAC;IAChH,mBAAA,IAAA,kBAAG,EAAC,gBAAgB,CAAC,CAAA;IAA0B,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;8EAEzF;AAKK;IAHL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE;QAC9B,WAAW,EAAE,wGAAwG;KACtH,CAAC;IAC+B,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAO,yBAAW;;yEAOxD;mCAjBU,wBAAwB;IADpC,IAAA,uBAAQ,EAAC,yBAAW,CAAC;GACT,wBAAwB,CAkBpC","sourcesContent":["import { Arg, Ctx, Directive, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { Transaction } from '../transaction/transaction'\nimport { paidToUnregisteredAccount } from './counterparty-account-rules'\nimport { accountOwners, counterpartyAccountSuggestions, registeredAccountsOf } from './counterparty-account-service'\nimport { CounterpartyAccountSuggestion } from './counterparty-account-type'\n\n@Resolver(Transaction)\nexport class CounterpartyAccountQuery {\n @Directive('@privilege(category: \"accounting\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => [CounterpartyAccountSuggestion], { description: 'The accounts the bank lines say a counterparty is paid into, not registered yet' })\n async counterpartyAccountSuggestions(@Arg('counterpartyId') counterpartyId: string, @Ctx() context: ResolverContext): Promise<CounterpartyAccountSuggestion[]> {\n return await counterpartyAccountSuggestions(getRepository(Transaction).manager, context.state.domain, counterpartyId)\n }\n\n @FieldResolver(type => Boolean, {\n description: 'The line paid an account its counterparty has not registered, though it has registered some (ADR-0134)'\n })\n async paidToUnregisteredAccount(@Root() line: Transaction): Promise<boolean> {\n if (!line.counterpartyId || !line.bankCounterpartyAccount) return false\n const manager = getRepository(Transaction).manager\n const domain = { id: line.domainId! }\n const registered = (await registeredAccountsOf(manager, domain, [line.counterpartyId])).get(line.counterpartyId) ?? []\n if (!registered.length) return false\n return paidToUnregisteredAccount(line.bankCounterpartyAccount, registered, (await accountOwners(manager, domain)).own)\n }\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the account number a bank wrote on a line says about whom the line paid (ADR-0134 §2-3, §2-4).
|
|
3
|
+
*
|
|
4
|
+
* Pure: the caller hands over the business's own account numbers and the registered counterparty accounts.
|
|
5
|
+
*/
|
|
6
|
+
import { type PartyForMatch } from '@things-factory/biz-base/counterparty-names';
|
|
7
|
+
export interface AccountOwners {
|
|
8
|
+
/** The business's own account numbers (`accountKey`) — a line to one of them is a transfer between its accounts. */
|
|
9
|
+
own: ReadonlySet<string>;
|
|
10
|
+
/** Each registered counterparty account number and the counterparties holding it. */
|
|
11
|
+
holders: ReadonlyMap<string, readonly string[]>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The counterparty a line paying the written account is linked to: none for one of the business's own accounts, and
|
|
15
|
+
* none unless exactly one counterparty holds the number. No link is made up.
|
|
16
|
+
*/
|
|
17
|
+
export declare function counterpartyOfAccount(written: string | null | undefined, owners: AccountOwners): string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* A line linked to a counterparty that paid an account the counterparty has not registered — when it has registered
|
|
20
|
+
* some. With none registered there is nothing to tell the line apart from.
|
|
21
|
+
*/
|
|
22
|
+
export declare function paidToUnregisteredAccount(written: string | null | undefined, registered: readonly string[], own?: ReadonlySet<string>): boolean;
|
|
23
|
+
export interface AccountLine {
|
|
24
|
+
bankCounterpartyAccount?: string | null;
|
|
25
|
+
bankCounterpartyBank?: string | null;
|
|
26
|
+
bankCounterpartyName?: string | null;
|
|
27
|
+
counterpartyId?: string | null;
|
|
28
|
+
transactionDate?: string | null;
|
|
29
|
+
}
|
|
30
|
+
export interface AccountSuggestion {
|
|
31
|
+
accountNumber: string;
|
|
32
|
+
bankName?: string;
|
|
33
|
+
/** The name the bank wrote for whom it paid, the one written most. */
|
|
34
|
+
writtenName?: string;
|
|
35
|
+
lineCount: number;
|
|
36
|
+
/** Of those, the lines already linked to the counterparty. */
|
|
37
|
+
linkedCount: number;
|
|
38
|
+
firstDate?: string;
|
|
39
|
+
lastDate?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The accounts the bank lines say a counterparty is paid into, for a person to confirm (ADR-0134 §2-4): the account
|
|
43
|
+
* numbers on lines linked to it, and on unlinked lines whose written name is its name or an alias and no one else's.
|
|
44
|
+
* A number already registered (to anyone) or one of the business's own is not suggested.
|
|
45
|
+
*/
|
|
46
|
+
export declare function accountSuggestions(counterpartyId: string, lines: readonly AccountLine[], parties: readonly PartyForMatch[], owners: AccountOwners): AccountSuggestion[];
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What the account number a bank wrote on a line says about whom the line paid (ADR-0134 §2-3, §2-4).
|
|
4
|
+
*
|
|
5
|
+
* Pure: the caller hands over the business's own account numbers and the registered counterparty accounts.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.counterpartyOfAccount = counterpartyOfAccount;
|
|
9
|
+
exports.paidToUnregisteredAccount = paidToUnregisteredAccount;
|
|
10
|
+
exports.accountSuggestions = accountSuggestions;
|
|
11
|
+
const counterparty_names_1 = require("@things-factory/biz-base/counterparty-names");
|
|
12
|
+
/**
|
|
13
|
+
* The counterparty a line paying the written account is linked to: none for one of the business's own accounts, and
|
|
14
|
+
* none unless exactly one counterparty holds the number. No link is made up.
|
|
15
|
+
*/
|
|
16
|
+
function counterpartyOfAccount(written, owners) {
|
|
17
|
+
const key = (0, counterparty_names_1.accountKey)(written);
|
|
18
|
+
if (!key || owners.own.has(key))
|
|
19
|
+
return undefined;
|
|
20
|
+
const holders = owners.holders.get(key) ?? [];
|
|
21
|
+
return holders.length === 1 ? holders[0] : undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A line linked to a counterparty that paid an account the counterparty has not registered — when it has registered
|
|
25
|
+
* some. With none registered there is nothing to tell the line apart from.
|
|
26
|
+
*/
|
|
27
|
+
function paidToUnregisteredAccount(written, registered, own = new Set()) {
|
|
28
|
+
const key = (0, counterparty_names_1.accountKey)(written);
|
|
29
|
+
if (!key || own.has(key) || !registered.length)
|
|
30
|
+
return false;
|
|
31
|
+
return !registered.some(one => (0, counterparty_names_1.accountKey)(one) === key);
|
|
32
|
+
}
|
|
33
|
+
const mostWritten = (values) => {
|
|
34
|
+
const counts = new Map();
|
|
35
|
+
for (const value of values)
|
|
36
|
+
if (value?.trim())
|
|
37
|
+
counts.set(value.trim(), (counts.get(value.trim()) ?? 0) + 1);
|
|
38
|
+
return [...counts].sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The accounts the bank lines say a counterparty is paid into, for a person to confirm (ADR-0134 §2-4): the account
|
|
42
|
+
* numbers on lines linked to it, and on unlinked lines whose written name is its name or an alias and no one else's.
|
|
43
|
+
* A number already registered (to anyone) or one of the business's own is not suggested.
|
|
44
|
+
*/
|
|
45
|
+
function accountSuggestions(counterpartyId, lines, parties, owners) {
|
|
46
|
+
const named = (0, counterparty_names_1.partyNamed)(parties);
|
|
47
|
+
const groups = new Map();
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
const key = (0, counterparty_names_1.accountKey)(line.bankCounterpartyAccount);
|
|
50
|
+
if (!key || owners.own.has(key) || owners.holders.has(key))
|
|
51
|
+
continue;
|
|
52
|
+
const linked = line.counterpartyId === counterpartyId;
|
|
53
|
+
if (!linked && (line.counterpartyId || named(line.bankCounterpartyName)?.id !== counterpartyId))
|
|
54
|
+
continue;
|
|
55
|
+
const group = groups.get(key) ?? { lines: [], linked: 0 };
|
|
56
|
+
group.lines.push(line);
|
|
57
|
+
if (linked)
|
|
58
|
+
group.linked++;
|
|
59
|
+
groups.set(key, group);
|
|
60
|
+
}
|
|
61
|
+
return [...groups]
|
|
62
|
+
.map(([accountNumber, group]) => {
|
|
63
|
+
const days = group.lines.map(one => one.transactionDate).filter(Boolean).sort();
|
|
64
|
+
return {
|
|
65
|
+
accountNumber,
|
|
66
|
+
bankName: mostWritten(group.lines.map(one => one.bankCounterpartyBank)),
|
|
67
|
+
writtenName: mostWritten(group.lines.map(one => one.bankCounterpartyName)),
|
|
68
|
+
lineCount: group.lines.length,
|
|
69
|
+
linkedCount: group.linked,
|
|
70
|
+
firstDate: days[0],
|
|
71
|
+
lastDate: days[days.length - 1]
|
|
72
|
+
};
|
|
73
|
+
})
|
|
74
|
+
.sort((a, b) => b.lineCount - a.lineCount || (b.lastDate ?? '').localeCompare(a.lastDate ?? ''));
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=counterparty-account-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"counterparty-account-rules.js","sourceRoot":"","sources":["../../../server/service/counterparty-account/counterparty-account-rules.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the account number a bank wrote on a line says about whom the line paid (ADR-0134 §2-3, §2-4).\n *\n * Pure: the caller hands over the business's own account numbers and the registered counterparty accounts.\n */\n\nimport { accountKey, partyNamed, type PartyForMatch } from '@things-factory/biz-base/counterparty-names'\n\nexport interface AccountOwners {\n /** The business's own account numbers (`accountKey`) — a line to one of them is a transfer between its accounts. */\n own: ReadonlySet<string>\n /** Each registered counterparty account number and the counterparties holding it. */\n holders: ReadonlyMap<string, readonly string[]>\n}\n\n/**\n * The counterparty a line paying the written account is linked to: none for one of the business's own accounts, and\n * none unless exactly one counterparty holds the number. No link is made up.\n */\nexport function counterpartyOfAccount(written: string | null | undefined, owners: AccountOwners): string | undefined {\n const key = accountKey(written)\n if (!key || owners.own.has(key)) return undefined\n const holders = owners.holders.get(key) ?? []\n return holders.length === 1 ? holders[0] : undefined\n}\n\n/**\n * A line linked to a counterparty that paid an account the counterparty has not registered — when it has registered\n * some. With none registered there is nothing to tell the line apart from.\n */\nexport function paidToUnregisteredAccount(written: string | null | undefined, registered: readonly string[], own: ReadonlySet<string> = new Set()): boolean {\n const key = accountKey(written)\n if (!key || own.has(key) || !registered.length) return false\n return !registered.some(one => accountKey(one) === key)\n}\n\nexport interface AccountLine {\n bankCounterpartyAccount?: string | null\n bankCounterpartyBank?: string | null\n bankCounterpartyName?: string | null\n counterpartyId?: string | null\n transactionDate?: string | null\n}\n\nexport interface AccountSuggestion {\n accountNumber: string\n bankName?: string\n /** The name the bank wrote for whom it paid, the one written most. */\n writtenName?: string\n lineCount: number\n /** Of those, the lines already linked to the counterparty. */\n linkedCount: number\n firstDate?: string\n lastDate?: string\n}\n\nconst mostWritten = (values: (string | null | undefined)[]) => {\n const counts = new Map<string, number>()\n for (const value of values) if (value?.trim()) counts.set(value.trim(), (counts.get(value.trim()) ?? 0) + 1)\n return [...counts].sort((a, b) => b[1] - a[1])[0]?.[0]\n}\n\n/**\n * The accounts the bank lines say a counterparty is paid into, for a person to confirm (ADR-0134 §2-4): the account\n * numbers on lines linked to it, and on unlinked lines whose written name is its name or an alias and no one else's.\n * A number already registered (to anyone) or one of the business's own is not suggested.\n */\nexport function accountSuggestions(\n counterpartyId: string,\n lines: readonly AccountLine[],\n parties: readonly PartyForMatch[],\n owners: AccountOwners\n): AccountSuggestion[] {\n const named = partyNamed(parties)\n const groups = new Map<string, { lines: AccountLine[]; linked: number }>()\n for (const line of lines) {\n const key = accountKey(line.bankCounterpartyAccount)\n if (!key || owners.own.has(key) || owners.holders.has(key)) continue\n const linked = line.counterpartyId === counterpartyId\n if (!linked && (line.counterpartyId || named(line.bankCounterpartyName)?.id !== counterpartyId)) continue\n const group = groups.get(key) ?? { lines: [], linked: 0 }\n group.lines.push(line)\n if (linked) group.linked++\n groups.set(key, group)\n }\n return [...groups]\n .map(([accountNumber, group]) => {\n const days = group.lines.map(one => one.transactionDate).filter(Boolean).sort() as string[]\n return {\n accountNumber,\n bankName: mostWritten(group.lines.map(one => one.bankCounterpartyBank)),\n writtenName: mostWritten(group.lines.map(one => one.bankCounterpartyName)),\n lineCount: group.lines.length,\n linkedCount: group.linked,\n firstDate: days[0],\n lastDate: days[days.length - 1]\n }\n })\n .sort((a, b) => b.lineCount - a.lineCount || (b.lastDate ?? '').localeCompare(a.lastDate ?? ''))\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EntityManager } from 'typeorm';
|
|
2
|
+
import { AccountOwners, AccountSuggestion } from './counterparty-account-rules';
|
|
3
|
+
/** The business's own account numbers and the registered counterparty accounts of a domain. */
|
|
4
|
+
export declare function accountOwners(tx: EntityManager, domain: {
|
|
5
|
+
id: string;
|
|
6
|
+
}): Promise<AccountOwners>;
|
|
7
|
+
/**
|
|
8
|
+
* Link the bank lines with no counterparty yet to the counterparty their written account is registered to (ADR-0134
|
|
9
|
+
* §2-3) — those of `keys` only, when given. A linked line is not changed. Through `saveTransactions`, which keeps the
|
|
10
|
+
* line's history. Returns how many were linked.
|
|
11
|
+
*/
|
|
12
|
+
export declare function linkLinesByAccount(tx: EntityManager, domain: {
|
|
13
|
+
id: string;
|
|
14
|
+
}, user: any, keys?: readonly string[]): Promise<number>;
|
|
15
|
+
/** The accounts the bank lines say a counterparty is paid into, not registered yet (`accountSuggestions`). */
|
|
16
|
+
export declare function counterpartyAccountSuggestions(tx: EntityManager, domain: {
|
|
17
|
+
id: string;
|
|
18
|
+
}, counterpartyId: string): Promise<AccountSuggestion[]>;
|
|
19
|
+
/** The registered account numbers of each of these counterparties. */
|
|
20
|
+
export declare function registeredAccountsOf(tx: EntityManager, domain: {
|
|
21
|
+
id: string;
|
|
22
|
+
}, counterpartyIds: readonly string[]): Promise<Map<string, string[]>>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.accountOwners = accountOwners;
|
|
4
|
+
exports.linkLinesByAccount = linkLinesByAccount;
|
|
5
|
+
exports.counterpartyAccountSuggestions = counterpartyAccountSuggestions;
|
|
6
|
+
exports.registeredAccountsOf = registeredAccountsOf;
|
|
7
|
+
const typeorm_1 = require("typeorm");
|
|
8
|
+
const biz_base_1 = require("@things-factory/biz-base");
|
|
9
|
+
const bank_account_1 = require("../bank-account/bank-account");
|
|
10
|
+
const transaction_1 = require("../transaction/transaction");
|
|
11
|
+
const transaction_service_1 = require("../transaction/transaction-service");
|
|
12
|
+
const counterparty_account_rules_1 = require("./counterparty-account-rules");
|
|
13
|
+
/** The business's own account numbers and the registered counterparty accounts of a domain. */
|
|
14
|
+
async function accountOwners(tx, domain) {
|
|
15
|
+
const own = (await tx.getRepository(bank_account_1.BankAccount).find({ where: { domain: { id: domain.id } }, select: ['id', 'accountNumber'] }))
|
|
16
|
+
.map(one => (0, biz_base_1.accountKey)(one.accountNumber))
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
const holders = new Map();
|
|
19
|
+
for (const one of await tx.getRepository(biz_base_1.CounterpartyBankAccount).find({ where: { domain: { id: domain.id } } }))
|
|
20
|
+
holders.set(one.accountNumber, [...(holders.get(one.accountNumber) ?? []), one.counterpartyId]);
|
|
21
|
+
return { own: new Set(own), holders };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Link the bank lines with no counterparty yet to the counterparty their written account is registered to (ADR-0134
|
|
25
|
+
* §2-3) — those of `keys` only, when given. A linked line is not changed. Through `saveTransactions`, which keeps the
|
|
26
|
+
* line's history. Returns how many were linked.
|
|
27
|
+
*/
|
|
28
|
+
async function linkLinesByAccount(tx, domain, user, keys) {
|
|
29
|
+
const owners = await accountOwners(tx, domain);
|
|
30
|
+
const wanted = keys ? new Set(keys.map(biz_base_1.accountKey).filter(Boolean)) : undefined;
|
|
31
|
+
const lines = await tx.getRepository(transaction_1.Transaction).find({
|
|
32
|
+
where: { domain: { id: domain.id }, counterparty: (0, typeorm_1.IsNull)(), bankCounterpartyAccount: (0, typeorm_1.Not)((0, typeorm_1.IsNull)()) },
|
|
33
|
+
select: ['id', 'bankCounterpartyAccount']
|
|
34
|
+
});
|
|
35
|
+
const linked = lines.flatMap(line => {
|
|
36
|
+
if (wanted && !wanted.has((0, biz_base_1.accountKey)(line.bankCounterpartyAccount)))
|
|
37
|
+
return [];
|
|
38
|
+
const counterpartyId = (0, counterparty_account_rules_1.counterpartyOfAccount)(line.bankCounterpartyAccount, owners);
|
|
39
|
+
return counterpartyId ? [{ id: line.id, cuFlag: 'M', counterparty: { id: counterpartyId } }] : [];
|
|
40
|
+
});
|
|
41
|
+
if (linked.length)
|
|
42
|
+
await (0, transaction_service_1.saveTransactions)(tx, domain, user, linked);
|
|
43
|
+
return linked.length;
|
|
44
|
+
}
|
|
45
|
+
/** The accounts the bank lines say a counterparty is paid into, not registered yet (`accountSuggestions`). */
|
|
46
|
+
async function counterpartyAccountSuggestions(tx, domain, counterpartyId) {
|
|
47
|
+
const owners = await accountOwners(tx, domain);
|
|
48
|
+
const parties = await tx.getRepository(biz_base_1.Counterparty).find({ where: { domain: { id: domain.id } }, select: ['id', 'name', 'aliases'] });
|
|
49
|
+
const lines = await tx.getRepository(transaction_1.Transaction).find({ where: { domain: { id: domain.id }, bankCounterpartyAccount: (0, typeorm_1.Not)((0, typeorm_1.IsNull)()) } });
|
|
50
|
+
return (0, counterparty_account_rules_1.accountSuggestions)(counterpartyId, lines, parties, owners);
|
|
51
|
+
}
|
|
52
|
+
/** The registered account numbers of each of these counterparties. */
|
|
53
|
+
async function registeredAccountsOf(tx, domain, counterpartyIds) {
|
|
54
|
+
const out = new Map();
|
|
55
|
+
if (!counterpartyIds.length)
|
|
56
|
+
return out;
|
|
57
|
+
const accounts = await tx.getRepository(biz_base_1.CounterpartyBankAccount).find({ where: { domain: { id: domain.id }, counterparty: { id: (0, typeorm_1.In)([...counterpartyIds]) } } });
|
|
58
|
+
for (const one of accounts)
|
|
59
|
+
out.set(one.counterpartyId, [...(out.get(one.counterpartyId) ?? []), one.accountNumber]);
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
/* A registered account links the lines already paying it. */
|
|
63
|
+
(0, biz_base_1.onCounterpartyBankAccountsSaved)(async (tx, domain, saved, user) => {
|
|
64
|
+
await linkLinesByAccount(tx, domain, user, saved.map(one => one.accountNumber));
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=counterparty-account-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"counterparty-account-service.js","sourceRoot":"","sources":["../../../server/service/counterparty-account/counterparty-account-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, In, IsNull, Not } from 'typeorm'\n\nimport { accountKey, Counterparty, CounterpartyBankAccount, onCounterpartyBankAccountsSaved } from '@things-factory/biz-base'\n\nimport { BankAccount } from '../bank-account/bank-account'\nimport { Transaction } from '../transaction/transaction'\nimport { saveTransactions, TransactionInput } from '../transaction/transaction-service'\nimport { AccountOwners, accountSuggestions, AccountSuggestion, counterpartyOfAccount } from './counterparty-account-rules'\n\n/** The business's own account numbers and the registered counterparty accounts of a domain. */\nexport async function accountOwners(tx: EntityManager, domain: { id: string }): Promise<AccountOwners> {\n const own = (await tx.getRepository(BankAccount).find({ where: { domain: { id: domain.id } }, select: ['id', 'accountNumber'] }))\n .map(one => accountKey(one.accountNumber))\n .filter(Boolean)\n const holders = new Map<string, string[]>()\n for (const one of await tx.getRepository(CounterpartyBankAccount).find({ where: { domain: { id: domain.id } } }))\n holders.set(one.accountNumber, [...(holders.get(one.accountNumber) ?? []), one.counterpartyId!])\n return { own: new Set(own), holders }\n}\n\n/**\n * Link the bank lines with no counterparty yet to the counterparty their written account is registered to (ADR-0134\n * §2-3) — those of `keys` only, when given. A linked line is not changed. Through `saveTransactions`, which keeps the\n * line's history. Returns how many were linked.\n */\nexport async function linkLinesByAccount(tx: EntityManager, domain: { id: string }, user: any, keys?: readonly string[]): Promise<number> {\n const owners = await accountOwners(tx, domain)\n const wanted = keys ? new Set(keys.map(accountKey).filter(Boolean)) : undefined\n const lines = await tx.getRepository(Transaction).find({\n where: { domain: { id: domain.id }, counterparty: IsNull(), bankCounterpartyAccount: Not(IsNull()) } as any,\n select: ['id', 'bankCounterpartyAccount']\n })\n const linked = lines.flatMap(line => {\n if (wanted && !wanted.has(accountKey(line.bankCounterpartyAccount))) return []\n const counterpartyId = counterpartyOfAccount(line.bankCounterpartyAccount, owners)\n return counterpartyId ? [{ id: line.id, cuFlag: 'M', counterparty: { id: counterpartyId } }] : []\n })\n if (linked.length) await saveTransactions(tx, domain, user, linked as TransactionInput[])\n return linked.length\n}\n\n/** The accounts the bank lines say a counterparty is paid into, not registered yet (`accountSuggestions`). */\nexport async function counterpartyAccountSuggestions(tx: EntityManager, domain: { id: string }, counterpartyId: string): Promise<AccountSuggestion[]> {\n const owners = await accountOwners(tx, domain)\n const parties = await tx.getRepository(Counterparty).find({ where: { domain: { id: domain.id } }, select: ['id', 'name', 'aliases'] })\n const lines = await tx.getRepository(Transaction).find({ where: { domain: { id: domain.id }, bankCounterpartyAccount: Not(IsNull()) } as any })\n return accountSuggestions(counterpartyId, lines, parties, owners)\n}\n\n/** The registered account numbers of each of these counterparties. */\nexport async function registeredAccountsOf(tx: EntityManager, domain: { id: string }, counterpartyIds: readonly string[]): Promise<Map<string, string[]>> {\n const out = new Map<string, string[]>()\n if (!counterpartyIds.length) return out\n const accounts = await tx.getRepository(CounterpartyBankAccount).find({ where: { domain: { id: domain.id }, counterparty: { id: In([...counterpartyIds]) } } })\n for (const one of accounts) out.set(one.counterpartyId!, [...(out.get(one.counterpartyId!) ?? []), one.accountNumber])\n return out\n}\n\n/* A registered account links the lines already paying it. */\nonCounterpartyBankAccountsSaved(async (tx, domain, saved, user) => {\n await linkLinesByAccount(tx, domain, user, saved.map(one => one.accountNumber))\n})\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CounterpartyAccountSuggestion = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const type_graphql_1 = require("type-graphql");
|
|
6
|
+
let CounterpartyAccountSuggestion = class CounterpartyAccountSuggestion {
|
|
7
|
+
};
|
|
8
|
+
exports.CounterpartyAccountSuggestion = CounterpartyAccountSuggestion;
|
|
9
|
+
tslib_1.__decorate([
|
|
10
|
+
(0, type_graphql_1.Field)({ description: 'The account number, its digits' }),
|
|
11
|
+
tslib_1.__metadata("design:type", String)
|
|
12
|
+
], CounterpartyAccountSuggestion.prototype, "accountNumber", void 0);
|
|
13
|
+
tslib_1.__decorate([
|
|
14
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'The bank the lines wrote' }),
|
|
15
|
+
tslib_1.__metadata("design:type", String)
|
|
16
|
+
], CounterpartyAccountSuggestion.prototype, "bankName", void 0);
|
|
17
|
+
tslib_1.__decorate([
|
|
18
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'The name the bank wrote for whom it paid, the one written most' }),
|
|
19
|
+
tslib_1.__metadata("design:type", String)
|
|
20
|
+
], CounterpartyAccountSuggestion.prototype, "writtenName", void 0);
|
|
21
|
+
tslib_1.__decorate([
|
|
22
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'How many bank lines paid it' }),
|
|
23
|
+
tslib_1.__metadata("design:type", Number)
|
|
24
|
+
], CounterpartyAccountSuggestion.prototype, "lineCount", void 0);
|
|
25
|
+
tslib_1.__decorate([
|
|
26
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Of those, how many are already linked to the counterparty' }),
|
|
27
|
+
tslib_1.__metadata("design:type", Number)
|
|
28
|
+
], CounterpartyAccountSuggestion.prototype, "linkedCount", void 0);
|
|
29
|
+
tslib_1.__decorate([
|
|
30
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'YYYY-MM-DD - the first line' }),
|
|
31
|
+
tslib_1.__metadata("design:type", String)
|
|
32
|
+
], CounterpartyAccountSuggestion.prototype, "firstDate", void 0);
|
|
33
|
+
tslib_1.__decorate([
|
|
34
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'YYYY-MM-DD - the last line' }),
|
|
35
|
+
tslib_1.__metadata("design:type", String)
|
|
36
|
+
], CounterpartyAccountSuggestion.prototype, "lastDate", void 0);
|
|
37
|
+
exports.CounterpartyAccountSuggestion = CounterpartyAccountSuggestion = tslib_1.__decorate([
|
|
38
|
+
(0, type_graphql_1.ObjectType)({ description: 'An account the bank lines say a counterparty is paid into, not registered yet - for a person to confirm' })
|
|
39
|
+
], CounterpartyAccountSuggestion);
|
|
40
|
+
//# sourceMappingURL=counterparty-account-type.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"counterparty-account-type.js","sourceRoot":"","sources":["../../../server/service/counterparty-account/counterparty-account-type.ts"],"names":[],"mappings":";;;;AAAA,+CAAqD;AAG9C,IAAM,6BAA6B,GAAnC,MAAM,6BAA6B;CAQzC,CAAA;AARY,sEAA6B;AACkB;IAAzD,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,gCAAgC,EAAE,CAAC;;oEAAsB;AACX;IAAnE,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,0BAA0B,EAAE,CAAC;;+DAAkB;AACqB;IAAzG,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,gEAAgE,EAAE,CAAC;;kEAAqB;AAC1D;IAAnE,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,WAAW,EAAE,6BAA6B,EAAE,CAAC;;gEAAkB;AACa;IAAjG,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,WAAW,EAAE,2DAA2D,EAAE,CAAC;;kEAAoB;AAC9C;IAAtE,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,6BAA6B,EAAE,CAAC;;gEAAmB;AACnB;IAArE,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,4BAA4B,EAAE,CAAC;;+DAAkB;wCAP5E,6BAA6B;IADzC,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,yGAAyG,EAAE,CAAC;GAC1H,6BAA6B,CAQzC","sourcesContent":["import { Field, Int, ObjectType } from 'type-graphql'\n\n@ObjectType({ description: 'An account the bank lines say a counterparty is paid into, not registered yet - for a person to confirm' })\nexport class CounterpartyAccountSuggestion {\n @Field({ description: 'The account number, its digits' }) accountNumber: string\n @Field({ nullable: true, description: 'The bank the lines wrote' }) bankName?: string\n @Field({ nullable: true, description: 'The name the bank wrote for whom it paid, the one written most' }) writtenName?: string\n @Field(type => Int, { description: 'How many bank lines paid it' }) lineCount: number\n @Field(type => Int, { description: 'Of those, how many are already linked to the counterparty' }) linkedCount: number\n @Field({ nullable: true, description: 'YYYY-MM-DD - the first line' }) firstDate?: string\n @Field({ nullable: true, description: 'YYYY-MM-DD - the last line' }) lastDate?: string\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.subscribers = exports.resolvers = exports.entities = void 0;
|
|
4
|
+
const counterparty_account_query_1 = require("./counterparty-account-query");
|
|
5
|
+
exports.entities = [];
|
|
6
|
+
exports.resolvers = [counterparty_account_query_1.CounterpartyAccountQuery];
|
|
7
|
+
exports.subscribers = [];
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/counterparty-account/index.ts"],"names":[],"mappings":";;;AAAA,6EAAuE;AAE1D,QAAA,QAAQ,GAAG,EAAE,CAAA;AACb,QAAA,SAAS,GAAG,CAAC,qDAAwB,CAAC,CAAA;AACtC,QAAA,WAAW,GAAG,EAAE,CAAA","sourcesContent":["import { CounterpartyAccountQuery } from './counterparty-account-query'\n\nexport const entities = []\nexport const resolvers = [CounterpartyAccountQuery]\nexport const subscribers = []\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { EntityManager } from 'typeorm';
|
|
2
|
+
import { type RequestLine } from './expenditure-request-rules';
|
|
3
|
+
/** The payment target kind of an expenditure request - the money paid on it directly, with no document between. */
|
|
4
|
+
export declare const EXPENDITURE_TARGET = "ExpenditureRequest";
|
|
5
|
+
export declare enum ExpenditurePaymentState {
|
|
6
|
+
/** Only corporate-card lines - the company already paid. */
|
|
7
|
+
NOTHING_TO_PAY = "NOTHING_TO_PAY",
|
|
8
|
+
UNPAID = "UNPAID",
|
|
9
|
+
PARTLY_PAID = "PARTLY_PAID",
|
|
10
|
+
PAID = "PAID"
|
|
11
|
+
}
|
|
12
|
+
export interface PaidPart {
|
|
13
|
+
amount: number;
|
|
14
|
+
transactionId: string;
|
|
15
|
+
/** The line was matched to the bank statement (it carries the statement's fingerprint). */
|
|
16
|
+
reconciled: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface PaymentState {
|
|
19
|
+
state: ExpenditurePaymentState;
|
|
20
|
+
payable: number;
|
|
21
|
+
paid: number;
|
|
22
|
+
outstanding: number;
|
|
23
|
+
/** What is paid on the request directly - the payable part not cited from a tax invoice. */
|
|
24
|
+
settleable: number;
|
|
25
|
+
settled: number;
|
|
26
|
+
/** Every bank line that paid it was matched to the bank statement. */
|
|
27
|
+
reconciled: boolean;
|
|
28
|
+
transactionIds: string[];
|
|
29
|
+
/** What is still to pay of each tax invoice a payable line cites - a reader that counts the invoice elsewhere takes it off. */
|
|
30
|
+
cited: {
|
|
31
|
+
taxInvoiceId: string;
|
|
32
|
+
outstanding: number;
|
|
33
|
+
}[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* How far a request is paid (ADR-0135 §2-4, §2-5). Pure.
|
|
37
|
+
*
|
|
38
|
+
* A payable line that cites a purchase tax invoice is paid when that invoice is - the bank line pays the invoice, and
|
|
39
|
+
* the request reads it (ADR-0122 §3-3: one path for one fact). The rest of the request is paid by the payments on the
|
|
40
|
+
* request itself.
|
|
41
|
+
*/
|
|
42
|
+
export declare function paymentStateFrom(lines: readonly RequestLine[], settlements: readonly PaidPart[], invoicePayments: ReadonlyMap<string, readonly PaidPart[]>): PaymentState;
|
|
43
|
+
/**
|
|
44
|
+
* **The one reader of whether requests were paid and reconciled** - the payment queue, the forecast of what is to go
|
|
45
|
+
* out, the AI and every 360 read it from here, never each on their own.
|
|
46
|
+
*/
|
|
47
|
+
export declare function paymentStatesOf(tx: EntityManager, domain: {
|
|
48
|
+
id: string;
|
|
49
|
+
}, requests: readonly {
|
|
50
|
+
id: string;
|
|
51
|
+
items?: readonly RequestLine[] | null;
|
|
52
|
+
}[]): Promise<Map<string, PaymentState>>;
|