@things-factory/accounting 10.1.154 → 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 +10 -9
- 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
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.lockedRequest = exports.RECEIPT_REF_TYPE = void 0;
|
|
4
|
+
exports.ownRequest = ownRequest;
|
|
5
|
+
exports.saveRequest = saveRequest;
|
|
6
|
+
exports.submitRequest = submitRequest;
|
|
7
|
+
exports.withdrawRequest = withdrawRequest;
|
|
8
|
+
exports.deleteRequest = deleteRequest;
|
|
9
|
+
exports.addLine = addLine;
|
|
10
|
+
exports.replaceReceipt = replaceReceipt;
|
|
11
|
+
exports.approvedWithStates = approvedWithStates;
|
|
12
|
+
exports.driftOf = driftOf;
|
|
13
|
+
exports.payRequests = payRequests;
|
|
14
|
+
exports.payWithLine = payWithLine;
|
|
15
|
+
const typeorm_1 = require("typeorm");
|
|
16
|
+
const shell_1 = require("@things-factory/shell");
|
|
17
|
+
const biz_base_1 = require("@things-factory/biz-base");
|
|
18
|
+
const organization_1 = require("@things-factory/organization");
|
|
19
|
+
const tax_invoice_1 = require("@things-factory/tax-invoice");
|
|
20
|
+
const worklist_1 = require("@things-factory/worklist");
|
|
21
|
+
const attachment_base_1 = require("@things-factory/attachment-base");
|
|
22
|
+
const expenditure_activity_names_1 = require("../../activities/expenditure-activity-names");
|
|
23
|
+
const account_1 = require("../account/account");
|
|
24
|
+
const account_match_1 = require("../account/account-match");
|
|
25
|
+
const bank_account_1 = require("../bank-account/bank-account");
|
|
26
|
+
const cash_flow_type_1 = require("../cash-flow-type");
|
|
27
|
+
const payment_1 = require("../payment/payment");
|
|
28
|
+
const document_targets_1 = require("../payment/document-targets");
|
|
29
|
+
const payment_service_1 = require("../payment/payment-service");
|
|
30
|
+
const transaction_1 = require("../transaction/transaction");
|
|
31
|
+
const transaction_service_1 = require("../transaction/transaction-service");
|
|
32
|
+
const expenditure_request_1 = require("./expenditure-request");
|
|
33
|
+
const expenditure_request_item_1 = require("./expenditure-request-item");
|
|
34
|
+
const expenditure_payment_state_1 = require("./expenditure-payment-state");
|
|
35
|
+
const expenditure_request_rules_1 = require("./expenditure-request-rules");
|
|
36
|
+
exports.RECEIPT_REF_TYPE = 'ExpenditureRequestItem';
|
|
37
|
+
const won = (value) => `${Math.round(value || 0).toLocaleString()}원`;
|
|
38
|
+
const KINDS = Object.values(expenditure_request_1.ExpenditureKind);
|
|
39
|
+
const METHODS = Object.values(expenditure_request_item_1.ExpenditurePaymentMethod);
|
|
40
|
+
/** A request somebody is judging (or that is decided) - it changes only after it is withdrawn. */
|
|
41
|
+
const lockedRequest = (status) => new shell_1.Refusal('expenditure-locked', { status: String(status).toLowerCase() }, `a ${String(status).toLowerCase()} request cannot be changed - withdraw it first`);
|
|
42
|
+
exports.lockedRequest = lockedRequest;
|
|
43
|
+
/** The request of this domain, refusing one that is not the caller's own. */
|
|
44
|
+
async function ownRequest(tx, id, domain, user) {
|
|
45
|
+
const request = await tx.getRepository(expenditure_request_1.ExpenditureRequest).findOne({ where: { id, domain: { id: domain.id } }, relations: ['requester'] });
|
|
46
|
+
if (!request)
|
|
47
|
+
throw new shell_1.Refusal('expenditure-not-found', {}, `expenditure request '${id}' was not found`);
|
|
48
|
+
if (request.requester?.id !== user.id)
|
|
49
|
+
throw new shell_1.Refusal('expenditure-not-yours', {}, 'only the requester can change this request');
|
|
50
|
+
return request;
|
|
51
|
+
}
|
|
52
|
+
async function employeeOf(tx, domain, user) {
|
|
53
|
+
return await tx.getRepository(organization_1.Employee).findOne({ where: { domain: { id: domain.id }, user: { id: user.id } }, relations: ['department'] });
|
|
54
|
+
}
|
|
55
|
+
async function linesOf(tx, requestId) {
|
|
56
|
+
return await tx.getRepository(expenditure_request_item_1.ExpenditureRequestItem).find({ where: { request: { id: requestId } }, order: { spentDate: 'ASC', createdAt: 'ASC' } });
|
|
57
|
+
}
|
|
58
|
+
async function refreshTotals(tx, request) {
|
|
59
|
+
const totals = (0, expenditure_request_rules_1.totalsOf)(await linesOf(tx, request.id));
|
|
60
|
+
request.totalAmount = totals.total;
|
|
61
|
+
request.payableAmount = totals.payable;
|
|
62
|
+
return await tx.getRepository(expenditure_request_1.ExpenditureRequest).save(request);
|
|
63
|
+
}
|
|
64
|
+
async function inDomain(tx, entity, domain, ref, field) {
|
|
65
|
+
if (!ref?.id)
|
|
66
|
+
return null;
|
|
67
|
+
const row = await tx.getRepository(entity).findOne({ where: { domain: { id: domain.id }, id: ref.id } });
|
|
68
|
+
if (!row)
|
|
69
|
+
throw new shell_1.Refusal('expenditure-reference-not-found', { field }, `${field} was not found - reload and try again`);
|
|
70
|
+
return row;
|
|
71
|
+
}
|
|
72
|
+
/** The counterparty and the account of it a counterparty payment names - the account must be that counterparty's. */
|
|
73
|
+
async function payeeOf(tx, domain, head, current) {
|
|
74
|
+
const counterparty = 'counterparty' in head ? await inDomain(tx, biz_base_1.Counterparty, domain, head.counterparty, 'counterparty') : undefined;
|
|
75
|
+
const account = 'payeeAccount' in head ? await inDomain(tx, biz_base_1.CounterpartyBankAccount, domain, head.payeeAccount, 'payeeAccount') : undefined;
|
|
76
|
+
const counterpartyId = counterparty === undefined ? current?.counterpartyId : counterparty?.id;
|
|
77
|
+
if (account && account.counterpartyId !== counterpartyId)
|
|
78
|
+
throw new shell_1.Refusal('expenditure-account-not-of-payee', {}, "the account is not the counterparty's - choose one of its accounts");
|
|
79
|
+
return { counterparty, account };
|
|
80
|
+
}
|
|
81
|
+
/** Apply line patches to a request - checks each line's references and the tax invoice it cites. */
|
|
82
|
+
async function saveItems(tx, domain, user, request, patches) {
|
|
83
|
+
const repo = tx.getRepository(expenditure_request_item_1.ExpenditureRequestItem);
|
|
84
|
+
const deletes = patches.filter(one => one.cuFlag === '-' && one.id).map(one => one.id);
|
|
85
|
+
if (deletes.length) {
|
|
86
|
+
const own = await repo.find({ where: { request: { id: request.id }, id: (0, typeorm_1.In)(deletes) } });
|
|
87
|
+
if (own.length !== new Set(deletes).size)
|
|
88
|
+
throw new shell_1.Refusal('expenditure-line-not-found', {}, 'a line was not found - reload and try again');
|
|
89
|
+
await repo.softDelete({ id: (0, typeorm_1.In)(deletes) });
|
|
90
|
+
}
|
|
91
|
+
const writes = patches.filter(one => one.cuFlag !== '-');
|
|
92
|
+
const accounts = writes.some(one => !('account' in one) && (one.category || one.description))
|
|
93
|
+
? await tx.getRepository(account_1.Account).find({ where: { domain: { id: domain.id }, active: true } })
|
|
94
|
+
: [];
|
|
95
|
+
const match = (0, account_match_1.accountMatcher)(accounts);
|
|
96
|
+
for (const patch of writes) {
|
|
97
|
+
const isNew = !patch.id || patch.cuFlag === '+';
|
|
98
|
+
const current = isNew ? undefined : await repo.findOne({ where: { request: { id: request.id }, id: patch.id } });
|
|
99
|
+
if (!isNew && !current)
|
|
100
|
+
throw new shell_1.Refusal('expenditure-line-not-found', {}, 'a line was not found - reload and try again');
|
|
101
|
+
const row = {};
|
|
102
|
+
if ('spentDate' in patch) {
|
|
103
|
+
row.spentDate = patch.spentDate ? (0, expenditure_request_rules_1.dayOf)(patch.spentDate) : null;
|
|
104
|
+
if (patch.spentDate && !row.spentDate)
|
|
105
|
+
throw new shell_1.Refusal('expenditure-line-day-invalid', { value: String(patch.spentDate) }, `'${patch.spentDate}' is not a day (use YYYY-MM-DD)`);
|
|
106
|
+
}
|
|
107
|
+
for (const field of ['category', 'merchant', 'description'])
|
|
108
|
+
if (field in patch)
|
|
109
|
+
row[field] = patch[field]?.trim() || null;
|
|
110
|
+
if ('amount' in patch)
|
|
111
|
+
row.amount = Number(patch.amount) || 0;
|
|
112
|
+
if ('vatAmount' in patch)
|
|
113
|
+
row.vatAmount = patch.vatAmount === null || patch.vatAmount === undefined ? null : Number(patch.vatAmount);
|
|
114
|
+
if ('paymentMethod' in patch && patch.paymentMethod) {
|
|
115
|
+
if (!METHODS.includes(patch.paymentMethod))
|
|
116
|
+
throw new shell_1.Refusal('expenditure-line-method-invalid', { value: patch.paymentMethod }, `'${patch.paymentMethod}' is not a way to pay`);
|
|
117
|
+
row.paymentMethod = patch.paymentMethod;
|
|
118
|
+
}
|
|
119
|
+
else if (isNew)
|
|
120
|
+
row.paymentMethod = request.kind === expenditure_request_1.ExpenditureKind.VENDOR ? expenditure_request_item_1.ExpenditurePaymentMethod.TRANSFER : expenditure_request_item_1.ExpenditurePaymentMethod.PERSONAL_CARD;
|
|
121
|
+
if ('account' in patch)
|
|
122
|
+
row.account = await inDomain(tx, account_1.Account, domain, patch.account, 'account');
|
|
123
|
+
else {
|
|
124
|
+
/* A line that names no account is given the one its words mean, by its category then its description (§`accountMatcher`). */
|
|
125
|
+
const words = [patch.category ?? current?.category, patch.description ?? current?.description];
|
|
126
|
+
const found = (isNew || 'category' in patch || 'description' in patch) && accounts.length ? match(words, cash_flow_type_1.CashFlowType.EXPENSE) : undefined;
|
|
127
|
+
if (found)
|
|
128
|
+
row.account = found;
|
|
129
|
+
}
|
|
130
|
+
if ('taxInvoice' in patch) {
|
|
131
|
+
const invoice = await inDomain(tx, tax_invoice_1.TaxInvoice, domain, patch.taxInvoice, 'taxInvoice');
|
|
132
|
+
if (invoice) {
|
|
133
|
+
if (request.kind !== expenditure_request_1.ExpenditureKind.VENDOR)
|
|
134
|
+
throw new shell_1.Refusal('expenditure-invoice-on-reimbursement', {}, 'a reimbursement cites receipts, not tax invoices');
|
|
135
|
+
if (invoice.category !== tax_invoice_1.Category.IMPORT)
|
|
136
|
+
throw new shell_1.Refusal('expenditure-invoice-not-purchase', {}, 'cite a purchase tax invoice');
|
|
137
|
+
const elsewhere = await repo.find({ where: { domain: { id: domain.id }, taxInvoice: { id: invoice.id } }, relations: ['request'] });
|
|
138
|
+
if (elsewhere.some(one => one.id !== current?.id && one.request && !one.request.deletedAt))
|
|
139
|
+
throw new shell_1.Refusal('expenditure-invoice-cited-twice', { issueDate: invoice.issueDate }, 'another request already pays this tax invoice');
|
|
140
|
+
if (!('amount' in patch) || !(row.amount > 0))
|
|
141
|
+
row.amount = Number(invoice.totalAmount);
|
|
142
|
+
if (!('spentDate' in patch))
|
|
143
|
+
row.spentDate = invoice.issueDate;
|
|
144
|
+
}
|
|
145
|
+
row.taxInvoice = invoice;
|
|
146
|
+
}
|
|
147
|
+
/* What a line says of its money holds whenever it is written, not only when it is submitted. */
|
|
148
|
+
const amount = 'amount' in row ? row.amount : Number(current?.amount ?? 0);
|
|
149
|
+
if ('amount' in row && !(row.amount > 0))
|
|
150
|
+
throw new shell_1.Refusal('expenditure-line-amount-missing', {}, 'every line needs an amount above zero');
|
|
151
|
+
const vat = 'vatAmount' in row ? row.vatAmount : current?.vatAmount;
|
|
152
|
+
if (vat && amount && Number(vat) > amount)
|
|
153
|
+
throw new shell_1.Refusal('expenditure-line-vat-over-amount', {}, 'the VAT is more than the amount');
|
|
154
|
+
/* A line that cites an invoice asks no more than the invoice - whichever of the two changed. */
|
|
155
|
+
const citedId = 'taxInvoice' in patch ? row.taxInvoice?.id : current?.taxInvoiceId;
|
|
156
|
+
if (citedId && ('amount' in row || 'taxInvoice' in patch)) {
|
|
157
|
+
const invoice = row.taxInvoice ?? (await tx.getRepository(tax_invoice_1.TaxInvoice).findOne({ where: { id: citedId }, withDeleted: true }));
|
|
158
|
+
if (invoice && amount > Number(invoice.totalAmount) + 0.005)
|
|
159
|
+
throw new shell_1.Refusal('expenditure-line-over-invoice', { total: won(Number(invoice.totalAmount)) }, 'a line asks more than the tax invoice it cites');
|
|
160
|
+
}
|
|
161
|
+
await repo.save({ ...(current ?? { domain, request, creator: user, amount: 0 }), ...row, updater: user });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Save a request and its lines in one go - the head, then the lines. A new request belongs to the caller; an existing
|
|
166
|
+
* one only to its requester, and only while it is editable.
|
|
167
|
+
*/
|
|
168
|
+
async function saveRequest(tx, domain, user, head, items) {
|
|
169
|
+
const repo = tx.getRepository(expenditure_request_1.ExpenditureRequest);
|
|
170
|
+
let request;
|
|
171
|
+
if (head.id) {
|
|
172
|
+
request = await ownRequest(tx, head.id, domain, user);
|
|
173
|
+
if (!(0, expenditure_request_rules_1.canEdit)(request.status))
|
|
174
|
+
throw (0, exports.lockedRequest)(request.status);
|
|
175
|
+
if (head.kind && head.kind !== request.kind)
|
|
176
|
+
throw new shell_1.Refusal('expenditure-kind-fixed', {}, 'a request keeps the kind it was written as');
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
if (!head.kind || !KINDS.includes(head.kind))
|
|
180
|
+
throw new shell_1.Refusal('expenditure-kind-missing', {}, 'say whether it pays a counterparty or pays an employee back');
|
|
181
|
+
const employee = await employeeOf(tx, domain, user);
|
|
182
|
+
request = repo.create({
|
|
183
|
+
domain,
|
|
184
|
+
kind: head.kind,
|
|
185
|
+
requester: user,
|
|
186
|
+
department: employee?.department ?? null,
|
|
187
|
+
status: expenditure_request_1.ExpenditureRequestStatus.DRAFT,
|
|
188
|
+
currency: 'KRW',
|
|
189
|
+
totalAmount: 0,
|
|
190
|
+
payableAmount: 0,
|
|
191
|
+
creator: user
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
const reimbursement = request.kind === expenditure_request_1.ExpenditureKind.REIMBURSEMENT;
|
|
195
|
+
if ('month' in head || (!head.id && reimbursement)) {
|
|
196
|
+
const month = (0, expenditure_request_rules_1.monthOf)(head.month);
|
|
197
|
+
if (reimbursement && !month)
|
|
198
|
+
throw new shell_1.Refusal('expenditure-month-invalid', { value: String(head.month ?? '') }, `month: '${head.month ?? ''}' is not a month (use YYYY-MM)`);
|
|
199
|
+
if (head.month && !month)
|
|
200
|
+
throw new shell_1.Refusal('expenditure-month-invalid', { value: String(head.month) }, `month: '${head.month}' is not a month (use YYYY-MM)`);
|
|
201
|
+
request.month = month ?? null;
|
|
202
|
+
}
|
|
203
|
+
if ('dueDate' in head) {
|
|
204
|
+
request.dueDate = head.dueDate ? (0, expenditure_request_rules_1.dayOf)(head.dueDate) ?? null : null;
|
|
205
|
+
if (head.dueDate && !request.dueDate)
|
|
206
|
+
throw new shell_1.Refusal('expenditure-due-date-invalid', { value: String(head.dueDate) }, `'${head.dueDate}' is not a day (use YYYY-MM-DD)`);
|
|
207
|
+
}
|
|
208
|
+
if (reimbursement && ('counterparty' in head || 'payeeAccount' in head))
|
|
209
|
+
throw new shell_1.Refusal('expenditure-reimbursement-pays-requester', {}, 'a reimbursement pays the employee who wrote it');
|
|
210
|
+
const { counterparty, account } = await payeeOf(tx, domain, head, head.id ? request : undefined);
|
|
211
|
+
if (counterparty !== undefined)
|
|
212
|
+
request.counterparty = counterparty;
|
|
213
|
+
if (account !== undefined)
|
|
214
|
+
request.payeeAccount = account;
|
|
215
|
+
/* A changed counterparty drops the account chosen of the former one. */
|
|
216
|
+
if (counterparty !== undefined && account === undefined && request.payeeAccountId) {
|
|
217
|
+
const kept = await tx.getRepository(biz_base_1.CounterpartyBankAccount).findOne({ where: { id: request.payeeAccountId } });
|
|
218
|
+
if (kept?.counterpartyId !== counterparty?.id)
|
|
219
|
+
request.payeeAccount = null;
|
|
220
|
+
}
|
|
221
|
+
if (head.title?.trim())
|
|
222
|
+
request.title = head.title.trim();
|
|
223
|
+
else if (!request.title)
|
|
224
|
+
request.title = reimbursement ? `${request.month} 경비` : `${counterparty?.name ?? ''} 지급`.trim();
|
|
225
|
+
if (head.notes !== undefined)
|
|
226
|
+
request.notes = head.notes;
|
|
227
|
+
request.updater = user;
|
|
228
|
+
request = await repo.save(request);
|
|
229
|
+
if (items?.length)
|
|
230
|
+
await saveItems(tx, domain, user, request, items);
|
|
231
|
+
return await refreshTotals(tx, request);
|
|
232
|
+
}
|
|
233
|
+
/** The payee's account as approvers will see it - written on the request when it is submitted (ADR-0135 §5). */
|
|
234
|
+
async function payeeSnapshot(tx, domain, request, user) {
|
|
235
|
+
if (request.kind === expenditure_request_1.ExpenditureKind.VENDOR) {
|
|
236
|
+
const account = await tx.getRepository(biz_base_1.CounterpartyBankAccount).findOne({ where: { domain: { id: domain.id }, id: request.payeeAccountId } });
|
|
237
|
+
if (!account || account.counterpartyId !== request.counterpartyId)
|
|
238
|
+
throw new shell_1.Refusal('expenditure-payee-account-missing', {}, "choose the counterparty's account to pay into");
|
|
239
|
+
return { payeeBankName: account.bankName ?? null, payeeAccountNumber: account.accountNumber, payeeHolderName: account.holderName ?? null };
|
|
240
|
+
}
|
|
241
|
+
const employee = await employeeOf(tx, domain, user);
|
|
242
|
+
return {
|
|
243
|
+
payeeBankName: employee?.bankName ?? null,
|
|
244
|
+
payeeAccountNumber: employee?.bankAccount ? (0, biz_base_1.accountKey)(employee.bankAccount) || null : null,
|
|
245
|
+
payeeHolderName: user.name ?? null
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/** Put a request up for approval. The approval carries what the approver needs to judge it. */
|
|
249
|
+
async function submitRequest(tx, domain, user, id, context) {
|
|
250
|
+
const request = await ownRequest(tx, id, domain, user);
|
|
251
|
+
const items = await linesOf(tx, request.id);
|
|
252
|
+
const refusal = (0, expenditure_request_rules_1.submitRefusal)({
|
|
253
|
+
kind: request.kind,
|
|
254
|
+
status: request.status,
|
|
255
|
+
month: request.month,
|
|
256
|
+
dueDate: request.dueDate,
|
|
257
|
+
counterpartyId: request.counterpartyId,
|
|
258
|
+
payeeAccountId: request.payeeAccountId
|
|
259
|
+
}, items);
|
|
260
|
+
if (refusal)
|
|
261
|
+
throw refusal;
|
|
262
|
+
Object.assign(request, await payeeSnapshot(tx, domain, request, user));
|
|
263
|
+
const form = (0, expenditure_activity_names_1.activityOfKind)(request.kind);
|
|
264
|
+
const carrier = await (0, worklist_1.activityNamed)(form, context);
|
|
265
|
+
if (!carrier)
|
|
266
|
+
throw new shell_1.Refusal('expenditure-approval-not-installed', { form }, `the approval '${form}' is not installed in this domain`);
|
|
267
|
+
const line = await (0, worklist_1.approvalLineStanding)(carrier, context);
|
|
268
|
+
if (line.refusal)
|
|
269
|
+
throw (0, worklist_1.approvalLineRefusal)(line.refusal);
|
|
270
|
+
const totals = (0, expenditure_request_rules_1.totalsOf)(items);
|
|
271
|
+
const who = user.name || user.email || '';
|
|
272
|
+
const vendor = request.kind === expenditure_request_1.ExpenditureKind.VENDOR;
|
|
273
|
+
const payee = vendor ? (await tx.getRepository(biz_base_1.Counterparty).findOne({ where: { id: request.counterpartyId } }))?.name ?? '' : who;
|
|
274
|
+
const account = [request.payeeBankName, request.payeeAccountNumber, request.payeeHolderName].filter(Boolean).join(' ');
|
|
275
|
+
const instance = await (0, worklist_1.issue)({
|
|
276
|
+
name: vendor ? `지출결의 ${payee} ${request.title}` : `경비 청구 ${who} ${request.month}`,
|
|
277
|
+
description: `${won(totals.total)} · ${totals.count}건${totals.payable !== totals.total ? ` (지급할 금액 ${won(totals.payable)})` : ''}`,
|
|
278
|
+
activityId: carrier.id,
|
|
279
|
+
approvalLine: carrier.approvalLine,
|
|
280
|
+
input: {
|
|
281
|
+
requestId: request.id,
|
|
282
|
+
month: request.month ?? request.dueDate?.slice(0, 7) ?? '',
|
|
283
|
+
requester: who,
|
|
284
|
+
payee,
|
|
285
|
+
headline: vendor
|
|
286
|
+
? `${payee} · ${request.title} ${won(totals.payable)}${request.dueDate ? ` — ${request.dueDate} 지급` : ''}`
|
|
287
|
+
: `${who} · ${request.month} 경비 ${won(totals.total)} (${totals.count}건)`,
|
|
288
|
+
total: totals.total,
|
|
289
|
+
payable: totals.payable,
|
|
290
|
+
account,
|
|
291
|
+
lines: items.map(item => `${item.spentDate ?? ''} ${item.category ?? ''} ${item.merchant ?? ''} ${won(item.amount)}${item.paymentMethod === expenditure_request_item_1.ExpenditurePaymentMethod.CORPORATE_CARD ? ' (법인카드)' : ''}${item.taxInvoiceId ? ' (세금계산서)' : ''} — ${item.description ?? ''}`
|
|
292
|
+
.replace(/\s+/g, ' ')
|
|
293
|
+
.trim()),
|
|
294
|
+
consequence: totals.payable > 0
|
|
295
|
+
? `승인하면 ${won(totals.payable)}을 ${payee}에게 지급할 대상이 됩니다${account ? ` (${account})` : ''}`
|
|
296
|
+
: '법인카드 사용분만 있어 지급할 금액은 없습니다'
|
|
297
|
+
},
|
|
298
|
+
threadsMin: 1,
|
|
299
|
+
threadsMax: 1,
|
|
300
|
+
requireApproval: true
|
|
301
|
+
}, context);
|
|
302
|
+
request.status = expenditure_request_1.ExpenditureRequestStatus.SUBMITTED;
|
|
303
|
+
request.activityInstanceId = instance.id;
|
|
304
|
+
request.submittedAt = new Date();
|
|
305
|
+
request.decidedAt = null;
|
|
306
|
+
request.decidedBy = null;
|
|
307
|
+
request.updater = user;
|
|
308
|
+
return await tx.getRepository(expenditure_request_1.ExpenditureRequest).save(request);
|
|
309
|
+
}
|
|
310
|
+
/** Take a request back from its approvers. worklist's abort does not call the activity back, so the request is set here. */
|
|
311
|
+
async function withdrawRequest(tx, domain, user, id, context) {
|
|
312
|
+
const request = await ownRequest(tx, id, domain, user);
|
|
313
|
+
if (request.status !== expenditure_request_1.ExpenditureRequestStatus.SUBMITTED)
|
|
314
|
+
throw new shell_1.Refusal('expenditure-not-submitted', { status: String(request.status).toLowerCase() }, 'only a request waiting on approvers can be withdrawn');
|
|
315
|
+
if (request.activityInstanceId)
|
|
316
|
+
await (0, worklist_1.abort)({ id: request.activityInstanceId, reason: '작성자가 회수함' }, context);
|
|
317
|
+
request.status = expenditure_request_1.ExpenditureRequestStatus.WITHDRAWN;
|
|
318
|
+
request.updater = user;
|
|
319
|
+
return await tx.getRepository(expenditure_request_1.ExpenditureRequest).save(request);
|
|
320
|
+
}
|
|
321
|
+
/** Delete one of the caller's requests that nobody is judging - with its lines. */
|
|
322
|
+
async function deleteRequest(tx, domain, user, id) {
|
|
323
|
+
const request = await ownRequest(tx, id, domain, user);
|
|
324
|
+
if (!(0, expenditure_request_rules_1.canEdit)(request.status))
|
|
325
|
+
throw new shell_1.Refusal('expenditure-cannot-delete', { status: request.status.toLowerCase() }, `a ${request.status.toLowerCase()} request cannot be deleted`);
|
|
326
|
+
await tx.getRepository(expenditure_request_item_1.ExpenditureRequestItem).softDelete({ request: { id } });
|
|
327
|
+
await tx.getRepository(expenditure_request_1.ExpenditureRequest).softDelete({ id });
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Add one line from the phone. It joins the caller's editable reimbursement of that month (§`requestForLine`), or
|
|
332
|
+
* starts one. The receipt photo is attached to the line.
|
|
333
|
+
*/
|
|
334
|
+
async function addLine(tx, domain, user, line, context) {
|
|
335
|
+
const spentDate = (0, expenditure_request_rules_1.dayOf)(line.spentDate);
|
|
336
|
+
if (!spentDate)
|
|
337
|
+
throw new shell_1.Refusal('expenditure-line-day-missing', {}, 'every line needs its day');
|
|
338
|
+
const month = (0, expenditure_request_rules_1.monthOf)(line.month) ?? spentDate.slice(0, 7);
|
|
339
|
+
const mine = await tx.getRepository(expenditure_request_1.ExpenditureRequest).find({
|
|
340
|
+
where: { domain: { id: domain.id }, requester: { id: user.id }, kind: expenditure_request_1.ExpenditureKind.REIMBURSEMENT, month }
|
|
341
|
+
});
|
|
342
|
+
const joinId = (0, expenditure_request_rules_1.requestForLine)(mine.map(one => ({ id: one.id, status: one.status, month: one.month, updatedAt: one.updatedAt })), month);
|
|
343
|
+
const { receipt, month: _month, ...fields } = line;
|
|
344
|
+
const request = await saveRequest(tx, domain, user, joinId ? { id: joinId } : { kind: expenditure_request_1.ExpenditureKind.REIMBURSEMENT, month }, [{ ...fields, spentDate, cuFlag: '+' }]);
|
|
345
|
+
if (receipt) {
|
|
346
|
+
const [latest] = await tx.getRepository(expenditure_request_item_1.ExpenditureRequestItem).find({ where: { request: { id: request.id } }, order: { createdAt: 'DESC' }, take: 1 });
|
|
347
|
+
if (latest)
|
|
348
|
+
await attachReceipt(latest.id, receipt, context);
|
|
349
|
+
}
|
|
350
|
+
return request;
|
|
351
|
+
}
|
|
352
|
+
/** Put a receipt on a line - replacing the one it had. Only the requester, only while editable. */
|
|
353
|
+
async function replaceReceipt(tx, domain, user, itemId, file, context) {
|
|
354
|
+
const item = await tx.getRepository(expenditure_request_item_1.ExpenditureRequestItem).findOne({ where: { id: itemId, domain: { id: domain.id } }, relations: ['request'] });
|
|
355
|
+
if (!item?.request)
|
|
356
|
+
throw new shell_1.Refusal('expenditure-line-not-found', {}, 'a line was not found - reload and try again');
|
|
357
|
+
const request = await ownRequest(tx, item.request.id, domain, user);
|
|
358
|
+
if (!(0, expenditure_request_rules_1.canEdit)(request.status))
|
|
359
|
+
throw (0, exports.lockedRequest)(request.status);
|
|
360
|
+
await (0, attachment_base_1.deleteAttachmentsByRef)(null, { refBys: [item.id], refType: exports.RECEIPT_REF_TYPE }, context);
|
|
361
|
+
await attachReceipt(item.id, file, context);
|
|
362
|
+
return item;
|
|
363
|
+
}
|
|
364
|
+
async function attachReceipt(itemId, file, context) {
|
|
365
|
+
await (0, attachment_base_1.createAttachment)(null, { attachment: { file, refType: exports.RECEIPT_REF_TYPE, refBy: itemId, category: 'receipt' } }, context);
|
|
366
|
+
}
|
|
367
|
+
/** Approved requests with their lines and how far each is paid - what the payment queue and payments are made of. */
|
|
368
|
+
async function approvedWithStates(tx, domain, ids) {
|
|
369
|
+
const requests = await tx.getRepository(expenditure_request_1.ExpenditureRequest).find({
|
|
370
|
+
where: { domain: { id: domain.id }, status: expenditure_request_1.ExpenditureRequestStatus.APPROVED, ...(ids ? { id: (0, typeorm_1.In)(ids) } : {}) },
|
|
371
|
+
relations: ['items', 'requester', 'counterparty', 'payeeAccount']
|
|
372
|
+
});
|
|
373
|
+
for (const request of requests)
|
|
374
|
+
request.items = (request.items ?? []).filter(item => !item.deletedAt);
|
|
375
|
+
const states = await (0, expenditure_payment_state_1.paymentStatesOf)(tx, domain, requests);
|
|
376
|
+
return { requests, states };
|
|
377
|
+
}
|
|
378
|
+
/** Whether the account approvers saw is still the one on file (§`payeeAccountDrift`); `missing` when none was written. */
|
|
379
|
+
async function driftOf(tx, request) {
|
|
380
|
+
if (!request.payeeAccountNumber)
|
|
381
|
+
return 'missing';
|
|
382
|
+
if (request.kind !== expenditure_request_1.ExpenditureKind.VENDOR)
|
|
383
|
+
return undefined;
|
|
384
|
+
const current = request.payeeAccountId
|
|
385
|
+
? await tx.getRepository(biz_base_1.CounterpartyBankAccount).findOne({ where: { id: request.payeeAccountId }, withDeleted: true })
|
|
386
|
+
: null;
|
|
387
|
+
return (0, expenditure_request_rules_1.payeeAccountDrift)(request, current, biz_base_1.accountKey);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Put a part of a withdrawal on a request (ADR-0135 §2-5). A payable line that cites a tax invoice is paid on that
|
|
391
|
+
* invoice; the rest on the request itself. A link the line already has to the same target is raised, not doubled.
|
|
392
|
+
*/
|
|
393
|
+
async function putOnLine(tx, domain, user, request, state, lineId, amount) {
|
|
394
|
+
if (amount > state.outstanding + 0.005)
|
|
395
|
+
throw new shell_1.Refusal('expenditure-paid-over', { title: request.title }, `more is put on ${request.title} than it asks`);
|
|
396
|
+
const existing = await tx.getRepository(payment_1.Payment).find({ where: { domain: { id: domain.id }, transaction: { id: lineId } } });
|
|
397
|
+
const patchFor = (targetType, targetId, put) => {
|
|
398
|
+
const twin = existing.find(one => one.targetType === targetType && one.targetId === targetId);
|
|
399
|
+
return twin ? { id: twin.id, cuFlag: 'M', amount: Number(twin.amount) + put } : { cuFlag: '+', transaction: { id: lineId }, targetType, targetId, amount: put };
|
|
400
|
+
};
|
|
401
|
+
let remaining = amount;
|
|
402
|
+
const patches = [];
|
|
403
|
+
const cited = (request.items ?? []).filter(item => (0, expenditure_request_rules_1.isPayable)(item) && item.taxInvoiceId);
|
|
404
|
+
if (cited.length) {
|
|
405
|
+
const onInvoices = await tx.getRepository(payment_1.Payment).find({
|
|
406
|
+
where: { domain: { id: domain.id }, targetType: document_targets_1.TAX_INVOICE_TARGET, targetId: (0, typeorm_1.In)(cited.map(item => item.taxInvoiceId)) }
|
|
407
|
+
});
|
|
408
|
+
for (const item of cited) {
|
|
409
|
+
const paid = onInvoices.filter(one => one.targetId === item.taxInvoiceId).reduce((sum, one) => sum + (0, document_targets_1.paidOfDocument)(one), 0);
|
|
410
|
+
const put = Math.min(remaining, Math.max(0, Number(item.amount) - paid));
|
|
411
|
+
if (put > 0)
|
|
412
|
+
patches.push(patchFor(document_targets_1.TAX_INVOICE_TARGET, item.taxInvoiceId, put));
|
|
413
|
+
remaining -= put;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const direct = Math.min(remaining, Math.max(0, state.settleable - state.settled));
|
|
417
|
+
if (direct > 0)
|
|
418
|
+
patches.push(patchFor(expenditure_payment_state_1.EXPENDITURE_TARGET, request.id, direct));
|
|
419
|
+
remaining -= direct;
|
|
420
|
+
if (remaining > 0.005)
|
|
421
|
+
throw new shell_1.Refusal('expenditure-paid-over', { title: request.title }, `more is put on ${request.title} than it asks`);
|
|
422
|
+
await (0, payment_service_1.savePayments)(tx, domain, user, patches);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Pay approved requests - finance transferred the money at the bank and records it. Each request gets a withdrawal of
|
|
426
|
+
* what it still asks, written as a person's line; the next bank statement import matches it, and then the payment is
|
|
427
|
+
* reconciled (§`bank-reconcile`).
|
|
428
|
+
*/
|
|
429
|
+
async function payRequests(tx, domain, user, ids, record) {
|
|
430
|
+
const paidDate = (0, expenditure_request_rules_1.dayOf)(record.paidDate);
|
|
431
|
+
if (!paidDate)
|
|
432
|
+
throw new shell_1.Refusal('expenditure-paid-date-missing', {}, 'paidDate: the day the money went out');
|
|
433
|
+
const bankAccount = await inDomain(tx, bank_account_1.BankAccount, domain, record.bankAccount, 'bankAccount');
|
|
434
|
+
const { requests, states } = await approvedWithStates(tx, domain, [...new Set(ids)]);
|
|
435
|
+
if (requests.length !== new Set(ids).size) {
|
|
436
|
+
const found = await tx.getRepository(expenditure_request_1.ExpenditureRequest).find({ where: { domain: { id: domain.id }, id: (0, typeorm_1.In)(ids) } });
|
|
437
|
+
const titles = found.filter(one => one.status !== expenditure_request_1.ExpenditureRequestStatus.APPROVED).map(one => one.title).join(', ');
|
|
438
|
+
if (titles)
|
|
439
|
+
throw new shell_1.Refusal('expenditure-not-approved', { title: titles }, `only an approved request is paid - ${titles}`);
|
|
440
|
+
throw new shell_1.Refusal('expenditure-not-found', {}, 'some requests were not found - reload and try again');
|
|
441
|
+
}
|
|
442
|
+
for (const request of requests) {
|
|
443
|
+
const state = states.get(request.id);
|
|
444
|
+
if (!(state.outstanding > 0))
|
|
445
|
+
throw new shell_1.Refusal('expenditure-paid-already', { title: request.title }, `${request.title} has nothing left to pay`);
|
|
446
|
+
const payee = request.kind === expenditure_request_1.ExpenditureKind.VENDOR ? request.counterparty?.name : request.requester?.name;
|
|
447
|
+
const [line] = await (0, transaction_service_1.saveTransactions)(tx, domain, user, [
|
|
448
|
+
{
|
|
449
|
+
cuFlag: '+',
|
|
450
|
+
category: transaction_1.TransactionCategory.CONFIRMED,
|
|
451
|
+
source: transaction_1.TransactionSource.MANUAL,
|
|
452
|
+
type: cash_flow_type_1.CashFlowType.EXPENSE,
|
|
453
|
+
transactionDate: paidDate,
|
|
454
|
+
amount: state.outstanding,
|
|
455
|
+
description: `지출 — ${request.title}`,
|
|
456
|
+
counterpartyName: payee ?? undefined,
|
|
457
|
+
...(request.counterpartyId ? { counterparty: { id: request.counterpartyId } } : {}),
|
|
458
|
+
...(bankAccount ? { bankAccount: { id: bankAccount.id } } : { currency: request.currency })
|
|
459
|
+
}
|
|
460
|
+
]);
|
|
461
|
+
await putOnLine(tx, domain, user, request, state, line.id, state.outstanding);
|
|
462
|
+
}
|
|
463
|
+
return requests;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Pay a request with a withdrawal already in the books - a bank statement brought it in first. The part put on the
|
|
467
|
+
* request is what it still asks unless a smaller amount is given.
|
|
468
|
+
*/
|
|
469
|
+
async function payWithLine(tx, domain, user, requestId, transactionId, amount) {
|
|
470
|
+
const { requests, states } = await approvedWithStates(tx, domain, [requestId]);
|
|
471
|
+
const [request] = requests;
|
|
472
|
+
if (!request) {
|
|
473
|
+
const found = await tx.getRepository(expenditure_request_1.ExpenditureRequest).findOne({ where: { domain: { id: domain.id }, id: requestId } });
|
|
474
|
+
if (found)
|
|
475
|
+
throw new shell_1.Refusal('expenditure-not-approved', { title: found.title }, `only an approved request is paid - ${found.title}`);
|
|
476
|
+
throw new shell_1.Refusal('expenditure-not-found', {}, `expenditure request '${requestId}' was not found`);
|
|
477
|
+
}
|
|
478
|
+
const line = await tx.getRepository(transaction_1.Transaction).findOne({ where: { domain: { id: domain.id }, id: transactionId, category: transaction_1.TransactionCategory.CONFIRMED } });
|
|
479
|
+
if (!line)
|
|
480
|
+
throw new shell_1.Refusal('expenditure-line-gone', {}, 'the bank line was not found - reload and try again');
|
|
481
|
+
const state = states.get(request.id);
|
|
482
|
+
const put = amount === null || amount === undefined ? Math.min(state.outstanding, Number(line.amount)) : Number(amount);
|
|
483
|
+
if (!(put > 0))
|
|
484
|
+
throw new shell_1.Refusal('expenditure-paid-already', { title: request.title }, `${request.title} has nothing left to pay`);
|
|
485
|
+
await putOnLine(tx, domain, user, request, state, line.id, put);
|
|
486
|
+
return request;
|
|
487
|
+
}
|
|
488
|
+
//# sourceMappingURL=expenditure-request-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expenditure-request-service.js","sourceRoot":"","sources":["../../../server/service/expenditure-request/expenditure-request-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, In } from 'typeorm'\n\nimport { Refusal } from '@things-factory/shell'\nimport { accountKey, Counterparty, CounterpartyBankAccount } from '@things-factory/biz-base'\nimport { Employee } from '@things-factory/organization'\nimport { Category, TaxInvoice } from '@things-factory/tax-invoice'\nimport { abort, activityNamed, approvalLineRefusal, approvalLineStanding, issue } from '@things-factory/worklist'\nimport { createAttachment, deleteAttachmentsByRef } from '@things-factory/attachment-base'\n\nimport { activityOfKind } from '../../activities/expenditure-activity-names'\nimport { Account } from '../account/account'\nimport { accountMatcher } from '../account/account-match'\nimport { BankAccount } from '../bank-account/bank-account'\nimport { CashFlowType } from '../cash-flow-type'\nimport { Payment } from '../payment/payment'\nimport { paidOfDocument, TAX_INVOICE_TARGET } from '../payment/document-targets'\nimport { savePayments } from '../payment/payment-service'\nimport { Transaction, TransactionCategory, TransactionSource } from '../transaction/transaction'\nimport { saveTransactions } from '../transaction/transaction-service'\nimport { ExpenditureKind, ExpenditureRequest, ExpenditureRequestStatus } from './expenditure-request'\nimport { ExpenditurePaymentMethod, ExpenditureRequestItem } from './expenditure-request-item'\nimport { EXPENDITURE_TARGET, paymentStatesOf, type PaymentState } from './expenditure-payment-state'\nimport {\n canEdit,\n dayOf,\n isPayable,\n monthOf,\n payeeAccountDrift,\n requestForLine,\n submitRefusal,\n totalsOf,\n type RequestKind,\n type RequestStatus\n} from './expenditure-request-rules'\n\nexport const RECEIPT_REF_TYPE = 'ExpenditureRequestItem'\n\nconst won = (value: number) => `${Math.round(value || 0).toLocaleString()}원`\nconst KINDS = Object.values(ExpenditureKind) as string[]\nconst METHODS = Object.values(ExpenditurePaymentMethod) as string[]\n\nexport interface RequestHead {\n id?: string\n kind?: string\n title?: string\n month?: string\n dueDate?: string\n counterparty?: { id: string } | null\n payeeAccount?: { id: string } | null\n notes?: string\n}\n\nexport interface ItemPatch {\n id?: string\n cuFlag?: string\n spentDate?: string\n category?: string\n account?: { id: string } | null\n merchant?: string\n description?: string\n amount?: number\n vatAmount?: number\n paymentMethod?: string\n taxInvoice?: { id: string } | null\n}\n\n/** A request somebody is judging (or that is decided) - it changes only after it is withdrawn. */\nexport const lockedRequest = (status: string) =>\n new Refusal('expenditure-locked', { status: String(status).toLowerCase() }, `a ${String(status).toLowerCase()} request cannot be changed - withdraw it first`)\n\n/** The request of this domain, refusing one that is not the caller's own. */\nexport async function ownRequest(tx: EntityManager, id: string, domain: { id: string }, user: { id: string }): Promise<ExpenditureRequest> {\n const request = await tx.getRepository(ExpenditureRequest).findOne({ where: { id, domain: { id: domain.id } } as any, relations: ['requester'] })\n if (!request) throw new Refusal('expenditure-not-found', {}, `expenditure request '${id}' was not found`)\n if (request.requester?.id !== user.id) throw new Refusal('expenditure-not-yours', {}, 'only the requester can change this request')\n return request\n}\n\nasync function employeeOf(tx: EntityManager, domain: { id: string }, user: { id: string }) {\n return await tx.getRepository(Employee).findOne({ where: { domain: { id: domain.id }, user: { id: user.id } } as any, relations: ['department'] })\n}\n\nasync function linesOf(tx: EntityManager, requestId: string): Promise<ExpenditureRequestItem[]> {\n return await tx.getRepository(ExpenditureRequestItem).find({ where: { request: { id: requestId } } as any, order: { spentDate: 'ASC', createdAt: 'ASC' } as any })\n}\n\nasync function refreshTotals(tx: EntityManager, request: ExpenditureRequest): Promise<ExpenditureRequest> {\n const totals = totalsOf(await linesOf(tx, request.id))\n request.totalAmount = totals.total\n request.payableAmount = totals.payable\n return await tx.getRepository(ExpenditureRequest).save(request)\n}\n\nasync function inDomain<T>(tx: EntityManager, entity: new () => T, domain: { id: string }, ref: { id: string } | null | undefined, field: string): Promise<T | null> {\n if (!ref?.id) return null\n const row = await tx.getRepository(entity).findOne({ where: { domain: { id: domain.id }, id: ref.id } as any })\n if (!row) throw new Refusal('expenditure-reference-not-found', { field }, `${field} was not found - reload and try again`)\n return row\n}\n\n/** The counterparty and the account of it a counterparty payment names - the account must be that counterparty's. */\nasync function payeeOf(tx: EntityManager, domain: { id: string }, head: RequestHead, current?: ExpenditureRequest) {\n const counterparty = 'counterparty' in head ? await inDomain(tx, Counterparty, domain, head.counterparty, 'counterparty') : undefined\n const account = 'payeeAccount' in head ? await inDomain(tx, CounterpartyBankAccount, domain, head.payeeAccount, 'payeeAccount') : undefined\n const counterpartyId = counterparty === undefined ? current?.counterpartyId : counterparty?.id\n if (account && account.counterpartyId !== counterpartyId)\n throw new Refusal('expenditure-account-not-of-payee', {}, \"the account is not the counterparty's - choose one of its accounts\")\n return { counterparty, account }\n}\n\n/** Apply line patches to a request - checks each line's references and the tax invoice it cites. */\nasync function saveItems(tx: EntityManager, domain: { id: string }, user: any, request: ExpenditureRequest, patches: ItemPatch[]) {\n const repo = tx.getRepository(ExpenditureRequestItem)\n\n const deletes = patches.filter(one => one.cuFlag === '-' && one.id).map(one => one.id!)\n if (deletes.length) {\n const own = await repo.find({ where: { request: { id: request.id }, id: In(deletes) } as any })\n if (own.length !== new Set(deletes).size) throw new Refusal('expenditure-line-not-found', {}, 'a line was not found - reload and try again')\n await repo.softDelete({ id: In(deletes) })\n }\n\n const writes = patches.filter(one => one.cuFlag !== '-')\n const accounts = writes.some(one => !('account' in one) && (one.category || one.description))\n ? await tx.getRepository(Account).find({ where: { domain: { id: domain.id }, active: true } as any })\n : []\n const match = accountMatcher(accounts)\n\n for (const patch of writes) {\n const isNew = !patch.id || patch.cuFlag === '+'\n const current = isNew ? undefined : await repo.findOne({ where: { request: { id: request.id }, id: patch.id } as any })\n if (!isNew && !current) throw new Refusal('expenditure-line-not-found', {}, 'a line was not found - reload and try again')\n\n const row: Record<string, any> = {}\n if ('spentDate' in patch) {\n row.spentDate = patch.spentDate ? dayOf(patch.spentDate) : null\n if (patch.spentDate && !row.spentDate) throw new Refusal('expenditure-line-day-invalid', { value: String(patch.spentDate) }, `'${patch.spentDate}' is not a day (use YYYY-MM-DD)`)\n }\n for (const field of ['category', 'merchant', 'description'] as const) if (field in patch) row[field] = patch[field]?.trim() || null\n if ('amount' in patch) row.amount = Number(patch.amount) || 0\n if ('vatAmount' in patch) row.vatAmount = patch.vatAmount === null || patch.vatAmount === undefined ? null : Number(patch.vatAmount)\n if ('paymentMethod' in patch && patch.paymentMethod) {\n if (!METHODS.includes(patch.paymentMethod)) throw new Refusal('expenditure-line-method-invalid', { value: patch.paymentMethod }, `'${patch.paymentMethod}' is not a way to pay`)\n row.paymentMethod = patch.paymentMethod\n } else if (isNew) row.paymentMethod = request.kind === ExpenditureKind.VENDOR ? ExpenditurePaymentMethod.TRANSFER : ExpenditurePaymentMethod.PERSONAL_CARD\n\n if ('account' in patch) row.account = await inDomain(tx, Account, domain, patch.account, 'account')\n else {\n /* A line that names no account is given the one its words mean, by its category then its description (§`accountMatcher`). */\n const words = [patch.category ?? current?.category, patch.description ?? current?.description]\n const found = (isNew || 'category' in patch || 'description' in patch) && accounts.length ? match(words, CashFlowType.EXPENSE) : undefined\n if (found) row.account = found\n }\n\n if ('taxInvoice' in patch) {\n const invoice = await inDomain(tx, TaxInvoice, domain, patch.taxInvoice, 'taxInvoice')\n if (invoice) {\n if (request.kind !== ExpenditureKind.VENDOR) throw new Refusal('expenditure-invoice-on-reimbursement', {}, 'a reimbursement cites receipts, not tax invoices')\n if (invoice.category !== Category.IMPORT) throw new Refusal('expenditure-invoice-not-purchase', {}, 'cite a purchase tax invoice')\n const elsewhere = await repo.find({ where: { domain: { id: domain.id }, taxInvoice: { id: invoice.id } } as any, relations: ['request'] })\n if (elsewhere.some(one => one.id !== current?.id && one.request && !one.request.deletedAt))\n throw new Refusal('expenditure-invoice-cited-twice', { issueDate: invoice.issueDate }, 'another request already pays this tax invoice')\n if (!('amount' in patch) || !(row.amount > 0)) row.amount = Number(invoice.totalAmount)\n if (!('spentDate' in patch)) row.spentDate = invoice.issueDate\n }\n row.taxInvoice = invoice\n }\n\n /* What a line says of its money holds whenever it is written, not only when it is submitted. */\n const amount = 'amount' in row ? row.amount : Number(current?.amount ?? 0)\n if ('amount' in row && !(row.amount > 0)) throw new Refusal('expenditure-line-amount-missing', {}, 'every line needs an amount above zero')\n const vat = 'vatAmount' in row ? row.vatAmount : current?.vatAmount\n if (vat && amount && Number(vat) > amount) throw new Refusal('expenditure-line-vat-over-amount', {}, 'the VAT is more than the amount')\n\n /* A line that cites an invoice asks no more than the invoice - whichever of the two changed. */\n const citedId = 'taxInvoice' in patch ? row.taxInvoice?.id : current?.taxInvoiceId\n if (citedId && ('amount' in row || 'taxInvoice' in patch)) {\n const invoice = row.taxInvoice ?? (await tx.getRepository(TaxInvoice).findOne({ where: { id: citedId } as any, withDeleted: true }))\n if (invoice && amount > Number(invoice.totalAmount) + 0.005)\n throw new Refusal('expenditure-line-over-invoice', { total: won(Number(invoice.totalAmount)) }, 'a line asks more than the tax invoice it cites')\n }\n\n await repo.save({ ...(current ?? { domain, request, creator: user, amount: 0 }), ...row, updater: user } as any)\n }\n}\n\n/**\n * Save a request and its lines in one go - the head, then the lines. A new request belongs to the caller; an existing\n * one only to its requester, and only while it is editable.\n */\nexport async function saveRequest(tx: EntityManager, domain: any, user: any, head: RequestHead, items: ItemPatch[]): Promise<ExpenditureRequest> {\n const repo = tx.getRepository(ExpenditureRequest)\n\n let request: ExpenditureRequest\n if (head.id) {\n request = await ownRequest(tx, head.id, domain, user)\n if (!canEdit(request.status as RequestStatus)) throw lockedRequest(request.status)\n if (head.kind && head.kind !== request.kind) throw new Refusal('expenditure-kind-fixed', {}, 'a request keeps the kind it was written as')\n } else {\n if (!head.kind || !KINDS.includes(head.kind)) throw new Refusal('expenditure-kind-missing', {}, 'say whether it pays a counterparty or pays an employee back')\n const employee = await employeeOf(tx, domain, user)\n request = repo.create({\n domain,\n kind: head.kind as ExpenditureKind,\n requester: user,\n department: employee?.department ?? null,\n status: ExpenditureRequestStatus.DRAFT,\n currency: 'KRW',\n totalAmount: 0,\n payableAmount: 0,\n creator: user\n } as any) as unknown as ExpenditureRequest\n }\n\n const reimbursement = request.kind === ExpenditureKind.REIMBURSEMENT\n if ('month' in head || (!head.id && reimbursement)) {\n const month = monthOf(head.month)\n if (reimbursement && !month) throw new Refusal('expenditure-month-invalid', { value: String(head.month ?? '') }, `month: '${head.month ?? ''}' is not a month (use YYYY-MM)`)\n if (head.month && !month) throw new Refusal('expenditure-month-invalid', { value: String(head.month) }, `month: '${head.month}' is not a month (use YYYY-MM)`)\n request.month = month ?? null\n }\n if ('dueDate' in head) {\n request.dueDate = head.dueDate ? dayOf(head.dueDate) ?? null : null\n if (head.dueDate && !request.dueDate) throw new Refusal('expenditure-due-date-invalid', { value: String(head.dueDate) }, `'${head.dueDate}' is not a day (use YYYY-MM-DD)`)\n }\n if (reimbursement && ('counterparty' in head || 'payeeAccount' in head)) throw new Refusal('expenditure-reimbursement-pays-requester', {}, 'a reimbursement pays the employee who wrote it')\n const { counterparty, account } = await payeeOf(tx, domain, head, head.id ? request : undefined)\n if (counterparty !== undefined) request.counterparty = counterparty\n if (account !== undefined) request.payeeAccount = account\n /* A changed counterparty drops the account chosen of the former one. */\n if (counterparty !== undefined && account === undefined && request.payeeAccountId) {\n const kept = await tx.getRepository(CounterpartyBankAccount).findOne({ where: { id: request.payeeAccountId } as any })\n if (kept?.counterpartyId !== counterparty?.id) request.payeeAccount = null\n }\n\n if (head.title?.trim()) request.title = head.title.trim()\n else if (!request.title) request.title = reimbursement ? `${request.month} 경비` : `${counterparty?.name ?? ''} 지급`.trim()\n if (head.notes !== undefined) request.notes = head.notes\n request.updater = user\n request = await repo.save(request)\n\n if (items?.length) await saveItems(tx, domain, user, request, items)\n return await refreshTotals(tx, request)\n}\n\n/** The payee's account as approvers will see it - written on the request when it is submitted (ADR-0135 §5). */\nasync function payeeSnapshot(tx: EntityManager, domain: { id: string }, request: ExpenditureRequest, user: any) {\n if (request.kind === ExpenditureKind.VENDOR) {\n const account = await tx.getRepository(CounterpartyBankAccount).findOne({ where: { domain: { id: domain.id }, id: request.payeeAccountId } as any })\n if (!account || account.counterpartyId !== request.counterpartyId)\n throw new Refusal('expenditure-payee-account-missing', {}, \"choose the counterparty's account to pay into\")\n return { payeeBankName: account.bankName ?? null, payeeAccountNumber: account.accountNumber, payeeHolderName: account.holderName ?? null }\n }\n const employee = await employeeOf(tx, domain, user)\n return {\n payeeBankName: employee?.bankName ?? null,\n payeeAccountNumber: employee?.bankAccount ? accountKey(employee.bankAccount) || null : null,\n payeeHolderName: user.name ?? null\n }\n}\n\n/** Put a request up for approval. The approval carries what the approver needs to judge it. */\nexport async function submitRequest(tx: EntityManager, domain: any, user: any, id: string, context: ResolverContext): Promise<ExpenditureRequest> {\n const request = await ownRequest(tx, id, domain, user)\n const items = await linesOf(tx, request.id)\n\n const refusal = submitRefusal(\n {\n kind: request.kind as RequestKind,\n status: request.status as RequestStatus,\n month: request.month,\n dueDate: request.dueDate,\n counterpartyId: request.counterpartyId,\n payeeAccountId: request.payeeAccountId\n },\n items\n )\n if (refusal) throw refusal\n Object.assign(request, await payeeSnapshot(tx, domain, request, user))\n\n const form = activityOfKind(request.kind)\n const carrier = await activityNamed(form, context)\n if (!carrier) throw new Refusal('expenditure-approval-not-installed', { form }, `the approval '${form}' is not installed in this domain`)\n const line = await approvalLineStanding(carrier, context)\n if (line.refusal) throw approvalLineRefusal(line.refusal)\n\n const totals = totalsOf(items)\n const who = user.name || user.email || ''\n const vendor = request.kind === ExpenditureKind.VENDOR\n const payee = vendor ? (await tx.getRepository(Counterparty).findOne({ where: { id: request.counterpartyId } as any }))?.name ?? '' : who\n const account = [request.payeeBankName, request.payeeAccountNumber, request.payeeHolderName].filter(Boolean).join(' ')\n\n const instance = await issue(\n {\n name: vendor ? `지출결의 ${payee} ${request.title}` : `경비 청구 ${who} ${request.month}`,\n description: `${won(totals.total)} · ${totals.count}건${totals.payable !== totals.total ? ` (지급할 금액 ${won(totals.payable)})` : ''}`,\n activityId: carrier.id,\n approvalLine: carrier.approvalLine,\n input: {\n requestId: request.id,\n month: request.month ?? request.dueDate?.slice(0, 7) ?? '',\n requester: who,\n payee,\n headline: vendor\n ? `${payee} · ${request.title} ${won(totals.payable)}${request.dueDate ? ` — ${request.dueDate} 지급` : ''}`\n : `${who} · ${request.month} 경비 ${won(totals.total)} (${totals.count}건)`,\n total: totals.total,\n payable: totals.payable,\n account,\n lines: items.map(item =>\n `${item.spentDate ?? ''} ${item.category ?? ''} ${item.merchant ?? ''} ${won(item.amount)}${item.paymentMethod === ExpenditurePaymentMethod.CORPORATE_CARD ? ' (법인카드)' : ''}${item.taxInvoiceId ? ' (세금계산서)' : ''} — ${item.description ?? ''}`\n .replace(/\\s+/g, ' ')\n .trim()\n ),\n consequence:\n totals.payable > 0\n ? `승인하면 ${won(totals.payable)}을 ${payee}에게 지급할 대상이 됩니다${account ? ` (${account})` : ''}`\n : '법인카드 사용분만 있어 지급할 금액은 없습니다'\n },\n threadsMin: 1,\n threadsMax: 1,\n requireApproval: true\n } as any,\n context\n )\n\n request.status = ExpenditureRequestStatus.SUBMITTED\n request.activityInstanceId = instance.id\n request.submittedAt = new Date()\n request.decidedAt = null\n request.decidedBy = null\n request.updater = user\n return await tx.getRepository(ExpenditureRequest).save(request)\n}\n\n/** Take a request back from its approvers. worklist's abort does not call the activity back, so the request is set here. */\nexport async function withdrawRequest(tx: EntityManager, domain: any, user: any, id: string, context: ResolverContext): Promise<ExpenditureRequest> {\n const request = await ownRequest(tx, id, domain, user)\n if (request.status !== ExpenditureRequestStatus.SUBMITTED)\n throw new Refusal('expenditure-not-submitted', { status: String(request.status).toLowerCase() }, 'only a request waiting on approvers can be withdrawn')\n\n if (request.activityInstanceId) await abort({ id: request.activityInstanceId, reason: '작성자가 회수함' }, context)\n\n request.status = ExpenditureRequestStatus.WITHDRAWN\n request.updater = user\n return await tx.getRepository(ExpenditureRequest).save(request)\n}\n\n/** Delete one of the caller's requests that nobody is judging - with its lines. */\nexport async function deleteRequest(tx: EntityManager, domain: any, user: any, id: string): Promise<boolean> {\n const request = await ownRequest(tx, id, domain, user)\n if (!canEdit(request.status as RequestStatus))\n throw new Refusal('expenditure-cannot-delete', { status: request.status.toLowerCase() }, `a ${request.status.toLowerCase()} request cannot be deleted`)\n await tx.getRepository(ExpenditureRequestItem).softDelete({ request: { id } } as any)\n await tx.getRepository(ExpenditureRequest).softDelete({ id })\n return true\n}\n\n/**\n * Add one line from the phone. It joins the caller's editable reimbursement of that month (§`requestForLine`), or\n * starts one. The receipt photo is attached to the line.\n */\nexport async function addLine(tx: EntityManager, domain: any, user: any, line: ItemPatch & { month?: string; receipt?: any }, context: ResolverContext): Promise<ExpenditureRequest> {\n const spentDate = dayOf(line.spentDate)\n if (!spentDate) throw new Refusal('expenditure-line-day-missing', {}, 'every line needs its day')\n const month = monthOf(line.month) ?? spentDate.slice(0, 7)\n\n const mine = await tx.getRepository(ExpenditureRequest).find({\n where: { domain: { id: domain.id }, requester: { id: user.id }, kind: ExpenditureKind.REIMBURSEMENT, month } as any\n })\n const joinId = requestForLine(\n mine.map(one => ({ id: one.id, status: one.status as RequestStatus, month: one.month, updatedAt: one.updatedAt })),\n month\n )\n\n const { receipt, month: _month, ...fields } = line\n const request = await saveRequest(tx, domain, user, joinId ? { id: joinId } : { kind: ExpenditureKind.REIMBURSEMENT, month }, [{ ...fields, spentDate, cuFlag: '+' }])\n\n if (receipt) {\n const [latest] = await tx.getRepository(ExpenditureRequestItem).find({ where: { request: { id: request.id } } as any, order: { createdAt: 'DESC' } as any, take: 1 })\n if (latest) await attachReceipt(latest.id, receipt, context)\n }\n return request\n}\n\n/** Put a receipt on a line - replacing the one it had. Only the requester, only while editable. */\nexport async function replaceReceipt(tx: EntityManager, domain: any, user: any, itemId: string, file: any, context: ResolverContext): Promise<ExpenditureRequestItem> {\n const item = await tx.getRepository(ExpenditureRequestItem).findOne({ where: { id: itemId, domain: { id: domain.id } } as any, relations: ['request'] })\n if (!item?.request) throw new Refusal('expenditure-line-not-found', {}, 'a line was not found - reload and try again')\n const request = await ownRequest(tx, item.request.id, domain, user)\n if (!canEdit(request.status as RequestStatus)) throw lockedRequest(request.status)\n\n await deleteAttachmentsByRef(null, { refBys: [item.id], refType: RECEIPT_REF_TYPE } as any, context)\n await attachReceipt(item.id, file, context)\n return item\n}\n\nasync function attachReceipt(itemId: string, file: any, context: ResolverContext) {\n await createAttachment(null, { attachment: { file, refType: RECEIPT_REF_TYPE, refBy: itemId, category: 'receipt' } } as any, context)\n}\n\n/** Approved requests with their lines and how far each is paid - what the payment queue and payments are made of. */\nexport async function approvedWithStates(tx: EntityManager, domain: { id: string }, ids?: string[]) {\n const requests = await tx.getRepository(ExpenditureRequest).find({\n where: { domain: { id: domain.id }, status: ExpenditureRequestStatus.APPROVED, ...(ids ? { id: In(ids) } : {}) } as any,\n relations: ['items', 'requester', 'counterparty', 'payeeAccount']\n })\n for (const request of requests) request.items = (request.items ?? []).filter(item => !item.deletedAt)\n const states = await paymentStatesOf(tx, domain, requests as any)\n return { requests, states }\n}\n\n/** Whether the account approvers saw is still the one on file (§`payeeAccountDrift`); `missing` when none was written. */\nexport async function driftOf(tx: EntityManager, request: ExpenditureRequest): Promise<string | undefined> {\n if (!request.payeeAccountNumber) return 'missing'\n if (request.kind !== ExpenditureKind.VENDOR) return undefined\n const current = request.payeeAccountId\n ? await tx.getRepository(CounterpartyBankAccount).findOne({ where: { id: request.payeeAccountId } as any, withDeleted: true })\n : null\n return payeeAccountDrift(request, current, accountKey)\n}\n\n/**\n * Put a part of a withdrawal on a request (ADR-0135 §2-5). A payable line that cites a tax invoice is paid on that\n * invoice; the rest on the request itself. A link the line already has to the same target is raised, not doubled.\n */\nasync function putOnLine(tx: EntityManager, domain: { id: string }, user: any, request: ExpenditureRequest, state: PaymentState, lineId: string, amount: number) {\n if (amount > state.outstanding + 0.005)\n throw new Refusal('expenditure-paid-over', { title: request.title }, `more is put on ${request.title} than it asks`)\n\n const existing = await tx.getRepository(Payment).find({ where: { domain: { id: domain.id }, transaction: { id: lineId } } as any })\n const patchFor = (targetType: string, targetId: string, put: number) => {\n const twin = existing.find(one => one.targetType === targetType && one.targetId === targetId)\n return twin ? { id: twin.id, cuFlag: 'M', amount: Number(twin.amount) + put } : { cuFlag: '+', transaction: { id: lineId }, targetType, targetId, amount: put }\n }\n\n let remaining = amount\n const patches: any[] = []\n const cited = (request.items ?? []).filter(item => isPayable(item) && item.taxInvoiceId)\n if (cited.length) {\n const onInvoices = await tx.getRepository(Payment).find({\n where: { domain: { id: domain.id }, targetType: TAX_INVOICE_TARGET, targetId: In(cited.map(item => item.taxInvoiceId)) } as any\n })\n for (const item of cited) {\n const paid = onInvoices.filter(one => one.targetId === item.taxInvoiceId).reduce((sum, one) => sum + paidOfDocument(one), 0)\n const put = Math.min(remaining, Math.max(0, Number(item.amount) - paid))\n if (put > 0) patches.push(patchFor(TAX_INVOICE_TARGET, item.taxInvoiceId!, put))\n remaining -= put\n }\n }\n const direct = Math.min(remaining, Math.max(0, state.settleable - state.settled))\n if (direct > 0) patches.push(patchFor(EXPENDITURE_TARGET, request.id, direct))\n remaining -= direct\n if (remaining > 0.005) throw new Refusal('expenditure-paid-over', { title: request.title }, `more is put on ${request.title} than it asks`)\n\n await savePayments(tx, domain, user, patches)\n}\n\nexport interface PaymentRecord {\n paidDate: string\n bankAccount?: { id: string } | null\n}\n\n/**\n * Pay approved requests - finance transferred the money at the bank and records it. Each request gets a withdrawal of\n * what it still asks, written as a person's line; the next bank statement import matches it, and then the payment is\n * reconciled (§`bank-reconcile`).\n */\nexport async function payRequests(tx: EntityManager, domain: any, user: any, ids: string[], record: PaymentRecord): Promise<ExpenditureRequest[]> {\n const paidDate = dayOf(record.paidDate)\n if (!paidDate) throw new Refusal('expenditure-paid-date-missing', {}, 'paidDate: the day the money went out')\n const bankAccount = await inDomain(tx, BankAccount, domain, record.bankAccount, 'bankAccount')\n\n const { requests, states } = await approvedWithStates(tx, domain, [...new Set(ids)])\n if (requests.length !== new Set(ids).size) {\n const found = await tx.getRepository(ExpenditureRequest).find({ where: { domain: { id: domain.id }, id: In(ids) } as any })\n const titles = found.filter(one => one.status !== ExpenditureRequestStatus.APPROVED).map(one => one.title).join(', ')\n if (titles) throw new Refusal('expenditure-not-approved', { title: titles }, `only an approved request is paid - ${titles}`)\n throw new Refusal('expenditure-not-found', {}, 'some requests were not found - reload and try again')\n }\n\n for (const request of requests) {\n const state = states.get(request.id)!\n if (!(state.outstanding > 0)) throw new Refusal('expenditure-paid-already', { title: request.title }, `${request.title} has nothing left to pay`)\n const payee = request.kind === ExpenditureKind.VENDOR ? request.counterparty?.name : request.requester?.name\n const [line] = await saveTransactions(tx, domain, user, [\n {\n cuFlag: '+',\n category: TransactionCategory.CONFIRMED,\n source: TransactionSource.MANUAL,\n type: CashFlowType.EXPENSE,\n transactionDate: paidDate,\n amount: state.outstanding,\n description: `지출 — ${request.title}`,\n counterpartyName: payee ?? undefined,\n ...(request.counterpartyId ? { counterparty: { id: request.counterpartyId } } : {}),\n ...(bankAccount ? { bankAccount: { id: bankAccount.id } } : { currency: request.currency })\n }\n ])\n await putOnLine(tx, domain, user, request, state, line.id, state.outstanding)\n }\n return requests\n}\n\n/**\n * Pay a request with a withdrawal already in the books - a bank statement brought it in first. The part put on the\n * request is what it still asks unless a smaller amount is given.\n */\nexport async function payWithLine(tx: EntityManager, domain: any, user: any, requestId: string, transactionId: string, amount?: number | null): Promise<ExpenditureRequest> {\n const { requests, states } = await approvedWithStates(tx, domain, [requestId])\n const [request] = requests\n if (!request) {\n const found = await tx.getRepository(ExpenditureRequest).findOne({ where: { domain: { id: domain.id }, id: requestId } as any })\n if (found) throw new Refusal('expenditure-not-approved', { title: found.title }, `only an approved request is paid - ${found.title}`)\n throw new Refusal('expenditure-not-found', {}, `expenditure request '${requestId}' was not found`)\n }\n const line = await tx.getRepository(Transaction).findOne({ where: { domain: { id: domain.id }, id: transactionId, category: TransactionCategory.CONFIRMED } as any })\n if (!line) throw new Refusal('expenditure-line-gone', {}, 'the bank line was not found - reload and try again')\n\n const state = states.get(request.id)!\n const put = amount === null || amount === undefined ? Math.min(state.outstanding, Number(line.amount)) : Number(amount)\n if (!(put > 0)) throw new Refusal('expenditure-paid-already', { title: request.title }, `${request.title} has nothing left to pay`)\n await putOnLine(tx, domain, user, request, state, line.id, put)\n return request\n}\n"]}
|