@things-factory/accounting 10.1.233 → 10.1.234
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/service/account/account-mutation.js +7 -53
- package/dist-server/service/account/account-mutation.js.map +1 -1
- package/dist-server/service/account/account-service.d.ts +15 -0
- package/dist-server/service/account/account-service.js +40 -0
- package/dist-server/service/account/account-service.js.map +1 -1
- package/dist-server/service/bank-account/bank-account-history.d.ts +7 -0
- package/dist-server/service/bank-account/bank-account-history.js +29 -0
- package/dist-server/service/bank-account/bank-account-history.js.map +1 -1
- package/dist-server/service/bank-account/bank-account-mutation.js +4 -4
- package/dist-server/service/bank-account/bank-account-mutation.js.map +1 -1
- package/dist-server/service/bank-account/bank-account-query.d.ts +3 -0
- package/dist-server/service/bank-account/bank-account-query.js +23 -0
- package/dist-server/service/bank-account/bank-account-query.js.map +1 -1
- package/dist-server/service/bank-account/bank-account-service.d.ts +27 -0
- package/dist-server/service/bank-account/bank-account-service.js +55 -0
- package/dist-server/service/bank-account/bank-account-service.js.map +1 -1
- package/dist-server/service/bank-account/bank-account-type.d.ts +9 -0
- package/dist-server/service/bank-account/bank-account-type.js +33 -0
- package/dist-server/service/bank-account/bank-account-type.js.map +1 -1
- package/dist-server/service/bank-account/bank-account.d.ts +7 -0
- package/dist-server/service/bank-account/bank-account.js +29 -0
- package/dist-server/service/bank-account/bank-account.js.map +1 -1
- package/dist-server/service/bank-account/card-query.d.ts +24 -0
- package/dist-server/service/bank-account/card-query.js +140 -0
- package/dist-server/service/bank-account/card-query.js.map +1 -0
- package/dist-server/service/bank-account/card-rules.d.ts +21 -0
- package/dist-server/service/bank-account/card-rules.js +30 -0
- package/dist-server/service/bank-account/card-rules.js.map +1 -0
- package/dist-server/service/bank-account/card-summary.d.ts +29 -0
- package/dist-server/service/bank-account/card-summary.js +42 -0
- package/dist-server/service/bank-account/card-summary.js.map +1 -0
- package/dist-server/service/bank-account/index.d.ts +2 -1
- package/dist-server/service/bank-account/index.js +2 -1
- package/dist-server/service/bank-account/index.js.map +1 -1
- package/dist-server/service/expenditure-request/card-charge-query.d.ts +10 -0
- package/dist-server/service/expenditure-request/card-charge-query.js +54 -0
- package/dist-server/service/expenditure-request/card-charge-query.js.map +1 -0
- package/dist-server/service/expenditure-request/expenditure-request-item.d.ts +4 -0
- package/dist-server/service/expenditure-request/expenditure-request-item.js +19 -1
- package/dist-server/service/expenditure-request/expenditure-request-item.js.map +1 -1
- package/dist-server/service/expenditure-request/expenditure-request-mutation.js +2 -1
- package/dist-server/service/expenditure-request/expenditure-request-mutation.js.map +1 -1
- package/dist-server/service/expenditure-request/expenditure-request-rules.d.ts +26 -2
- package/dist-server/service/expenditure-request/expenditure-request-rules.js +19 -2
- package/dist-server/service/expenditure-request/expenditure-request-rules.js.map +1 -1
- package/dist-server/service/expenditure-request/expenditure-request-service.d.ts +6 -0
- package/dist-server/service/expenditure-request/expenditure-request-service.js +35 -1
- package/dist-server/service/expenditure-request/expenditure-request-service.js.map +1 -1
- package/dist-server/service/expenditure-request/expenditure-request-type.d.ts +2 -0
- package/dist-server/service/expenditure-request/expenditure-request-type.js +8 -0
- package/dist-server/service/expenditure-request/expenditure-request-type.js.map +1 -1
- package/dist-server/service/expenditure-request/index.d.ts +2 -1
- package/dist-server/service/expenditure-request/index.js +2 -1
- package/dist-server/service/expenditure-request/index.js.map +1 -1
- package/dist-server/service/index.d.ts +4 -1
- package/dist-server/service/index.js +3 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/payment/card-bill-target.d.ts +2 -0
- package/dist-server/service/payment/card-bill-target.js +37 -0
- package/dist-server/service/payment/card-bill-target.js.map +1 -0
- package/dist-server/service/payment/index.d.ts +1 -0
- package/dist-server/service/payment/index.js +1 -0
- package/dist-server/service/payment/index.js.map +1 -1
- package/dist-server/service/recurring-cash-item/recurring-cash-item-query.d.ts +2 -0
- package/dist-server/service/recurring-cash-item/recurring-cash-item-query.js +14 -0
- package/dist-server/service/recurring-cash-item/recurring-cash-item-query.js.map +1 -1
- package/dist-server/service/recurring-cash-item/recurring-cash-item-service.d.ts +5 -0
- package/dist-server/service/recurring-cash-item/recurring-cash-item-service.js +22 -1
- package/dist-server/service/recurring-cash-item/recurring-cash-item-service.js.map +1 -1
- package/dist-server/service/recurring-cash-item/recurring-schedule.d.ts +5 -1
- package/dist-server/service/recurring-cash-item/recurring-schedule.js +10 -2
- package/dist-server/service/recurring-cash-item/recurring-schedule.js.map +1 -1
- package/dist-server/service/transaction/transaction-query.d.ts +5 -0
- package/dist-server/service/transaction/transaction-query.js +3 -3
- package/dist-server/service/transaction/transaction-query.js.map +1 -1
- package/dist-server/service/transaction/transaction-service.d.ts +5 -1
- package/dist-server/service/transaction/transaction-service.js +15 -10
- package/dist-server/service/transaction/transaction-service.js.map +1 -1
- package/dist-server/service/transaction/transaction.d.ts +5 -3
- package/dist-server/service/transaction/transaction.js +6 -4
- package/dist-server/service/transaction/transaction.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -10
- package/translations/en.json +30 -1
- package/translations/es.json +30 -1
- package/translations/ja.json +30 -1
- package/translations/ko.json +30 -1
- package/translations/ms.json +30 -1
- package/translations/zh.json +30 -1
|
@@ -6,7 +6,8 @@ const bank_account_history_1 = require("./bank-account-history");
|
|
|
6
6
|
const event_subscriber_1 = require("./event-subscriber");
|
|
7
7
|
const bank_account_query_1 = require("./bank-account-query");
|
|
8
8
|
const bank_account_mutation_1 = require("./bank-account-mutation");
|
|
9
|
+
const card_query_1 = require("./card-query");
|
|
9
10
|
exports.entities = [bank_account_1.BankAccount, bank_account_history_1.BankAccountHistory];
|
|
10
|
-
exports.resolvers = [bank_account_query_1.BankAccountQuery, bank_account_mutation_1.BankAccountMutation];
|
|
11
|
+
exports.resolvers = [bank_account_query_1.BankAccountQuery, bank_account_mutation_1.BankAccountMutation, card_query_1.CardQuery];
|
|
11
12
|
exports.subscribers = [event_subscriber_1.BankAccountHistoryEntitySubscriber];
|
|
12
13
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/bank-account/index.ts"],"names":[],"mappings":";;;AAAA,iDAA4C;AAC5C,iEAA2D;AAC3D,yDAAuE;AACvE,6DAAuD;AACvD,mEAA6D;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/bank-account/index.ts"],"names":[],"mappings":";;;AAAA,iDAA4C;AAC5C,iEAA2D;AAC3D,yDAAuE;AACvE,6DAAuD;AACvD,mEAA6D;AAC7D,6CAAwC;AAE3B,QAAA,QAAQ,GAAG,CAAC,0BAAW,EAAE,yCAAkB,CAAC,CAAA;AAC5C,QAAA,SAAS,GAAG,CAAC,qCAAgB,EAAE,2CAAmB,EAAE,sBAAS,CAAC,CAAA;AAC9D,QAAA,WAAW,GAAG,CAAC,qDAAkC,CAAC,CAAA","sourcesContent":["import { BankAccount } from './bank-account'\nimport { BankAccountHistory } from './bank-account-history'\nimport { BankAccountHistoryEntitySubscriber } from './event-subscriber'\nimport { BankAccountQuery } from './bank-account-query'\nimport { BankAccountMutation } from './bank-account-mutation'\nimport { CardQuery } from './card-query'\n\nexport const entities = [BankAccount, BankAccountHistory]\nexport const resolvers = [BankAccountQuery, BankAccountMutation, CardQuery]\nexport const subscribers = [BankAccountHistoryEntitySubscriber]\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ListParam } from '@things-factory/shell';
|
|
2
|
+
import { TransactionList } from '../transaction/transaction-type';
|
|
3
|
+
/** What a corporate-card line of an expenditure request picks from (ADR-0178 결정 5). */
|
|
4
|
+
export declare class CardChargeQuery {
|
|
5
|
+
/**
|
|
6
|
+
* The caller's card charges no line explains yet - what a corporate-card line of an expenditure request picks from.
|
|
7
|
+
* A holder sees their own cards' charges; whoever keeps the bank book sees every card's (ADR-0178 결정 7).
|
|
8
|
+
*/
|
|
9
|
+
cardChargesToExplain(params: ListParam, context: ResolverContext): Promise<TransactionList>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CardChargeQuery = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const type_graphql_1 = require("type-graphql");
|
|
6
|
+
const typeorm_1 = require("typeorm");
|
|
7
|
+
const shell_1 = require("@things-factory/shell");
|
|
8
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
9
|
+
const bank_account_1 = require("../bank-account/bank-account");
|
|
10
|
+
const card_query_1 = require("../bank-account/card-query");
|
|
11
|
+
const cash_flow_type_1 = require("../cash-flow-type");
|
|
12
|
+
const transaction_1 = require("../transaction/transaction");
|
|
13
|
+
const transaction_type_1 = require("../transaction/transaction-type");
|
|
14
|
+
/** What a corporate-card line of an expenditure request picks from (ADR-0178 결정 5). */
|
|
15
|
+
let CardChargeQuery = class CardChargeQuery {
|
|
16
|
+
/**
|
|
17
|
+
* The caller's card charges no line explains yet - what a corporate-card line of an expenditure request picks from.
|
|
18
|
+
* A holder sees their own cards' charges; whoever keeps the bank book sees every card's (ADR-0178 결정 7).
|
|
19
|
+
*/
|
|
20
|
+
async cardChargesToExplain(params, context) {
|
|
21
|
+
const { domain, user, unsafeIP, prohibitedPrivileges } = context.state;
|
|
22
|
+
const anyCard = await (0, auth_base_1.checkPermission)({ category: 'bank', privilege: 'query', owner: false }, user, domain, unsafeIP, prohibitedPrivileges);
|
|
23
|
+
const cards = await (0, shell_1.getRepository)(bank_account_1.BankAccount).find({
|
|
24
|
+
where: { domain: { id: domain.id }, accountType: bank_account_1.AccountType.CARD, ...(anyCard ? {} : { holder: { user: { id: user.id } } }) }
|
|
25
|
+
});
|
|
26
|
+
if (!cards.length)
|
|
27
|
+
return { items: [], total: 0 };
|
|
28
|
+
const charges = await (0, shell_1.getRepository)(transaction_1.Transaction).find({
|
|
29
|
+
where: { domain: { id: domain.id }, category: transaction_1.TransactionCategory.CARD, type: cash_flow_type_1.CashFlowType.EXPENSE, bankAccount: { id: (0, typeorm_1.In)(cards.map(card => card.id)) } },
|
|
30
|
+
order: { transactionDate: 'DESC', createdAt: 'DESC' }
|
|
31
|
+
});
|
|
32
|
+
const explained = new Set((await (0, card_query_1.explanationsOf)(domain, charges.map(one => one.id))).map(item => item.cardTransactionId));
|
|
33
|
+
/* A picker pages and searches what is left - the shop or the words on the line. */
|
|
34
|
+
const words = params.search?.trim().toLowerCase();
|
|
35
|
+
const left = charges.filter(one => !explained.has(one.id) && (!words || [one.counterpartyName, one.bankCounterpartyName, one.bankDescription, one.description].some(text => text?.toLowerCase().includes(words))));
|
|
36
|
+
const { page = 1, limit = 100 } = params.pagination ?? {};
|
|
37
|
+
return { items: limit > 0 ? left.slice((page - 1) * limit, page * limit) : left, total: left.length };
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
exports.CardChargeQuery = CardChargeQuery;
|
|
41
|
+
tslib_1.__decorate([
|
|
42
|
+
(0, type_graphql_1.Directive)('@privilege(category: "accounting-self", privilege: "query", domainOwnerGranted: true)'),
|
|
43
|
+
(0, type_graphql_1.Query)(returns => transaction_type_1.TransactionList, { description: "Card charges no expenditure line explains yet - the caller's cards, newest first" }),
|
|
44
|
+
tslib_1.__param(0, (0, type_graphql_1.Args)(type => shell_1.ListParam)),
|
|
45
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
46
|
+
tslib_1.__metadata("design:type", Function),
|
|
47
|
+
tslib_1.__metadata("design:paramtypes", [shell_1.ListParam, Object]),
|
|
48
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
49
|
+
], CardChargeQuery.prototype, "cardChargesToExplain", null);
|
|
50
|
+
exports.CardChargeQuery = CardChargeQuery = tslib_1.__decorate([
|
|
51
|
+
(0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals),
|
|
52
|
+
(0, type_graphql_1.Resolver)(transaction_1.Transaction)
|
|
53
|
+
], CardChargeQuery);
|
|
54
|
+
//# sourceMappingURL=card-charge-query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card-charge-query.js","sourceRoot":"","sources":["../../../server/service/expenditure-request/card-charge-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Args, Ctx, Directive, Query, Resolver, UseMiddleware } from 'type-graphql'\nimport { In } from 'typeorm'\n\nimport { getRepository, ListParam, SpeakRefusals } from '@things-factory/shell'\nimport { checkPermission } from '@things-factory/auth-base'\n\nimport { AccountType, BankAccount } from '../bank-account/bank-account'\nimport { explanationsOf } from '../bank-account/card-query'\nimport { CashFlowType } from '../cash-flow-type'\nimport { Transaction, TransactionCategory } from '../transaction/transaction'\nimport { TransactionList } from '../transaction/transaction-type'\n\n/** What a corporate-card line of an expenditure request picks from (ADR-0178 결정 5). */\n@UseMiddleware(SpeakRefusals)\n@Resolver(Transaction)\nexport class CardChargeQuery {\n /**\n * The caller's card charges no line explains yet - what a corporate-card line of an expenditure request picks from.\n * A holder sees their own cards' charges; whoever keeps the bank book sees every card's (ADR-0178 결정 7).\n */\n @Directive('@privilege(category: \"accounting-self\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => TransactionList, { description: \"Card charges no expenditure line explains yet - the caller's cards, newest first\" })\n async cardChargesToExplain(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<TransactionList> {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n const anyCard = await checkPermission({ category: 'bank', privilege: 'query', owner: false }, user, domain, unsafeIP, prohibitedPrivileges)\n const cards = await getRepository(BankAccount).find({\n where: { domain: { id: domain.id }, accountType: AccountType.CARD, ...(anyCard ? {} : { holder: { user: { id: user.id } } }) } as any\n })\n if (!cards.length) return { items: [], total: 0 }\n const charges = await getRepository(Transaction).find({\n where: { domain: { id: domain.id }, category: TransactionCategory.CARD, type: CashFlowType.EXPENSE, bankAccount: { id: In(cards.map(card => card.id)) } } as any,\n order: { transactionDate: 'DESC', createdAt: 'DESC' } as any\n })\n const explained = new Set((await explanationsOf(domain, charges.map(one => one.id))).map(item => item.cardTransactionId))\n /* A picker pages and searches what is left - the shop or the words on the line. */\n const words = params.search?.trim().toLowerCase()\n const left = charges.filter(\n one => !explained.has(one.id) && (!words || [one.counterpartyName, one.bankCounterpartyName, one.bankDescription, one.description].some(text => text?.toLowerCase().includes(words)))\n )\n const { page = 1, limit = 100 } = params.pagination ?? {}\n return { items: limit > 0 ? left.slice((page - 1) * limit, page * limit) : left, total: left.length }\n }\n}\n"]}
|
|
@@ -3,6 +3,7 @@ import { User } from '@things-factory/auth-base';
|
|
|
3
3
|
import { TaxInvoice } from '@things-factory/tax-invoice';
|
|
4
4
|
import { Attachment } from '@things-factory/attachment-base';
|
|
5
5
|
import { Account } from '../account/account';
|
|
6
|
+
import { Transaction } from '../transaction/transaction';
|
|
6
7
|
import { ExpenditureRequest } from './expenditure-request';
|
|
7
8
|
export declare enum ExpenditurePaymentMethod {
|
|
8
9
|
PERSONAL_CARD = "PERSONAL_CARD",
|
|
@@ -31,6 +32,9 @@ export declare class ExpenditureRequestItem {
|
|
|
31
32
|
paymentMethod: ExpenditurePaymentMethod;
|
|
32
33
|
taxInvoice?: TaxInvoice;
|
|
33
34
|
taxInvoiceId?: string;
|
|
35
|
+
cardTransaction?: Transaction;
|
|
36
|
+
cardTransactionId?: string;
|
|
37
|
+
personalUse: boolean;
|
|
34
38
|
files?: Attachment[];
|
|
35
39
|
createdAt?: Date;
|
|
36
40
|
updatedAt?: Date;
|
|
@@ -9,6 +9,7 @@ const auth_base_1 = require("@things-factory/auth-base");
|
|
|
9
9
|
const tax_invoice_1 = require("@things-factory/tax-invoice");
|
|
10
10
|
const attachment_base_1 = require("@things-factory/attachment-base");
|
|
11
11
|
const account_1 = require("../account/account");
|
|
12
|
+
const transaction_1 = require("../transaction/transaction");
|
|
12
13
|
const expenditure_request_1 = require("./expenditure-request");
|
|
13
14
|
var ExpenditurePaymentMethod;
|
|
14
15
|
(function (ExpenditurePaymentMethod) {
|
|
@@ -19,7 +20,7 @@ var ExpenditurePaymentMethod;
|
|
|
19
20
|
})(ExpenditurePaymentMethod || (exports.ExpenditurePaymentMethod = ExpenditurePaymentMethod = {}));
|
|
20
21
|
(0, type_graphql_1.registerEnumType)(ExpenditurePaymentMethod, {
|
|
21
22
|
name: 'ExpenditurePaymentMethod',
|
|
22
|
-
description: 'How a line was paid - the corporate card is reported and approved, but the company already paid it'
|
|
23
|
+
description: 'How a line was paid - the corporate card is reported and approved, but the company already paid it; the line explains a charge on the card (ADR-0178)'
|
|
23
24
|
});
|
|
24
25
|
/**
|
|
25
26
|
* One thing a request pays for - a meal, a taxi, a supplier's invoice. Its receipts and papers are attached to it by
|
|
@@ -105,6 +106,22 @@ tslib_1.__decorate([
|
|
|
105
106
|
(0, typeorm_1.RelationId)((item) => item.taxInvoice),
|
|
106
107
|
tslib_1.__metadata("design:type", String)
|
|
107
108
|
], ExpenditureRequestItem.prototype, "taxInvoiceId", void 0);
|
|
109
|
+
tslib_1.__decorate([
|
|
110
|
+
(0, typeorm_1.ManyToOne)(type => transaction_1.Transaction, { nullable: true }),
|
|
111
|
+
(0, type_graphql_1.Directive)('@privilege(category: "bank", privilege: "query", domainOwnerGranted: false)'),
|
|
112
|
+
(0, type_graphql_1.Field)(type => transaction_1.Transaction, { nullable: true, description: 'The charge on a corporate card this line explains (ADR-0178)' }),
|
|
113
|
+
tslib_1.__metadata("design:type", transaction_1.Transaction)
|
|
114
|
+
], ExpenditureRequestItem.prototype, "cardTransaction", void 0);
|
|
115
|
+
tslib_1.__decorate([
|
|
116
|
+
(0, typeorm_1.RelationId)((item) => item.cardTransaction),
|
|
117
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.ID, { nullable: true, description: 'Which card charge the line explains - its id only' }),
|
|
118
|
+
tslib_1.__metadata("design:type", String)
|
|
119
|
+
], ExpenditureRequestItem.prototype, "cardTransactionId", void 0);
|
|
120
|
+
tslib_1.__decorate([
|
|
121
|
+
(0, typeorm_1.Column)({ default: false }),
|
|
122
|
+
(0, type_graphql_1.Field)({ description: 'Spent on the corporate card for oneself - the holder owes it back' }),
|
|
123
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
124
|
+
], ExpenditureRequestItem.prototype, "personalUse", void 0);
|
|
108
125
|
tslib_1.__decorate([
|
|
109
126
|
(0, type_graphql_1.Field)(type => [attachment_base_1.Attachment], { nullable: true, description: 'The receipts and papers the line is proven by, the first attached first' }),
|
|
110
127
|
tslib_1.__metadata("design:type", Array)
|
|
@@ -146,6 +163,7 @@ exports.ExpenditureRequestItem = ExpenditureRequestItem = tslib_1.__decorate([
|
|
|
146
163
|
(0, typeorm_1.Entity)(),
|
|
147
164
|
(0, typeorm_1.Index)('ix_expenditure_request_item_0', (item) => [item.domain, item.request]),
|
|
148
165
|
(0, typeorm_1.Index)('ix_expenditure_request_item_1', (item) => [item.domain, item.taxInvoice]),
|
|
166
|
+
(0, typeorm_1.Index)('ix_expenditure_request_item_2', (item) => [item.domain, item.cardTransaction]),
|
|
149
167
|
(0, type_graphql_1.ObjectType)({ description: 'One thing an expenditure request pays for' })
|
|
150
168
|
], ExpenditureRequestItem);
|
|
151
169
|
//# sourceMappingURL=expenditure-request-item.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expenditure-request-item.js","sourceRoot":"","sources":["../../../server/service/expenditure-request/expenditure-request-item.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Directive, Field, Float, ID, ObjectType, registerEnumType } from 'type-graphql'\n\nimport { Domain, moneyColumn } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { TaxInvoice } from '@things-factory/tax-invoice'\nimport { Attachment } from '@things-factory/attachment-base'\n\nimport { Account } from '../account/account'\nimport { ExpenditureRequest } from './expenditure-request'\n\nexport enum ExpenditurePaymentMethod {\n PERSONAL_CARD = 'PERSONAL_CARD',\n CORPORATE_CARD = 'CORPORATE_CARD',\n CASH = 'CASH',\n TRANSFER = 'TRANSFER'\n}\n\nregisterEnumType(ExpenditurePaymentMethod, {\n name: 'ExpenditurePaymentMethod',\n description: 'How a line was paid - the corporate card is reported and approved, but the company already paid it'\n})\n\n/**\n * One thing a request pays for - a meal, a taxi, a supplier's invoice. Its receipts and papers are attached to it by\n * id; a purchase tax invoice it pays is cited, and then the bank line pays that invoice (ADR-0135 §2-5).\n */\n@Entity()\n@Index('ix_expenditure_request_item_0', (item: ExpenditureRequestItem) => [item.domain, item.request])\n@Index('ix_expenditure_request_item_1', (item: ExpenditureRequestItem) => [item.domain, item.taxInvoice])\n@ObjectType({ description: 'One thing an expenditure request pays for' })\nexport class ExpenditureRequestItem {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'The domain it belongs to' })\n domain?: Domain\n\n @RelationId((item: ExpenditureRequestItem) => item.domain)\n domainId?: string\n\n @ManyToOne(type => ExpenditureRequest, request => request.items, { nullable: false, onDelete: 'CASCADE' })\n @Field(type => ExpenditureRequest, { nullable: true, description: 'The request the line is on' })\n request?: ExpenditureRequest\n\n @RelationId((item: ExpenditureRequestItem) => item.request)\n requestId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'YYYY-MM-DD it was spent, or the day of the invoice' })\n spentDate?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: \"What the requester called it - '식대', '교통비' - until an account is chosen\" })\n category?: string\n\n @ManyToOne(type => Account, { nullable: true, onDelete: 'SET NULL' })\n @Field(type => Account, { nullable: true, description: '계정과목' })\n account?: Account\n\n @RelationId((item: ExpenditureRequestItem) => item.account)\n accountId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Where - the shop or company on the receipt' })\n merchant?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'What for - the purpose an approver judges' })\n description?: string\n\n @Column(moneyColumn())\n @Field(type => Float, { description: 'Amount paid, VAT included' })\n amount: number\n\n @Column(moneyColumn({ nullable: true, default: null }))\n @Field(type => Float, { nullable: true, description: 'VAT in the amount, when the receipt shows it' })\n vatAmount?: number\n\n @Column({ default: ExpenditurePaymentMethod.PERSONAL_CARD })\n @Field(type => ExpenditurePaymentMethod, { description: 'How it was paid' })\n paymentMethod: ExpenditurePaymentMethod\n\n /* The tax-invoice ledger's row (ADR-0139): a requester without it sees the line, and FORBIDDEN on this path. */\n @ManyToOne(type => TaxInvoice, { nullable: true })\n @Directive('@privilege(category: \"tax-invoice\", privilege: \"query\", domainOwnerGranted: false)')\n @Field(type => TaxInvoice, { nullable: true, description: 'The purchase tax invoice this line pays' })\n taxInvoice?: TaxInvoice\n\n @RelationId((item: ExpenditureRequestItem) => item.taxInvoice)\n taxInvoiceId?: string\n\n @Field(type => [Attachment], { nullable: true, description: 'The receipts and papers the line is proven by, the first attached first' })\n files?: Attachment[]\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'When it was written' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'When it was last changed' })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true, description: 'When it was deleted - financial rows are kept, not erased' })\n deletedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who wrote it' })\n creator?: User\n\n @RelationId((item: ExpenditureRequestItem) => item.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who changed it last' })\n updater?: User\n\n @RelationId((item: ExpenditureRequestItem) => item.updater)\n updaterId?: string\n}\n"]}
|
|
1
|
+
{"version":3,"file":"expenditure-request-item.js","sourceRoot":"","sources":["../../../server/service/expenditure-request/expenditure-request-item.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Directive, Field, Float, ID, ObjectType, registerEnumType } from 'type-graphql'\n\nimport { Domain, moneyColumn } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { TaxInvoice } from '@things-factory/tax-invoice'\nimport { Attachment } from '@things-factory/attachment-base'\n\nimport { Account } from '../account/account'\nimport { Transaction } from '../transaction/transaction'\nimport { ExpenditureRequest } from './expenditure-request'\n\nexport enum ExpenditurePaymentMethod {\n PERSONAL_CARD = 'PERSONAL_CARD',\n CORPORATE_CARD = 'CORPORATE_CARD',\n CASH = 'CASH',\n TRANSFER = 'TRANSFER'\n}\n\nregisterEnumType(ExpenditurePaymentMethod, {\n name: 'ExpenditurePaymentMethod',\n description: 'How a line was paid - the corporate card is reported and approved, but the company already paid it; the line explains a charge on the card (ADR-0178)'\n})\n\n/**\n * One thing a request pays for - a meal, a taxi, a supplier's invoice. Its receipts and papers are attached to it by\n * id; a purchase tax invoice it pays is cited, and then the bank line pays that invoice (ADR-0135 §2-5).\n */\n@Entity()\n@Index('ix_expenditure_request_item_0', (item: ExpenditureRequestItem) => [item.domain, item.request])\n@Index('ix_expenditure_request_item_1', (item: ExpenditureRequestItem) => [item.domain, item.taxInvoice])\n@Index('ix_expenditure_request_item_2', (item: ExpenditureRequestItem) => [item.domain, item.cardTransaction])\n@ObjectType({ description: 'One thing an expenditure request pays for' })\nexport class ExpenditureRequestItem {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'The domain it belongs to' })\n domain?: Domain\n\n @RelationId((item: ExpenditureRequestItem) => item.domain)\n domainId?: string\n\n @ManyToOne(type => ExpenditureRequest, request => request.items, { nullable: false, onDelete: 'CASCADE' })\n @Field(type => ExpenditureRequest, { nullable: true, description: 'The request the line is on' })\n request?: ExpenditureRequest\n\n @RelationId((item: ExpenditureRequestItem) => item.request)\n requestId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'YYYY-MM-DD it was spent, or the day of the invoice' })\n spentDate?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: \"What the requester called it - '식대', '교통비' - until an account is chosen\" })\n category?: string\n\n @ManyToOne(type => Account, { nullable: true, onDelete: 'SET NULL' })\n @Field(type => Account, { nullable: true, description: '계정과목' })\n account?: Account\n\n @RelationId((item: ExpenditureRequestItem) => item.account)\n accountId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Where - the shop or company on the receipt' })\n merchant?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'What for - the purpose an approver judges' })\n description?: string\n\n @Column(moneyColumn())\n @Field(type => Float, { description: 'Amount paid, VAT included' })\n amount: number\n\n @Column(moneyColumn({ nullable: true, default: null }))\n @Field(type => Float, { nullable: true, description: 'VAT in the amount, when the receipt shows it' })\n vatAmount?: number\n\n @Column({ default: ExpenditurePaymentMethod.PERSONAL_CARD })\n @Field(type => ExpenditurePaymentMethod, { description: 'How it was paid' })\n paymentMethod: ExpenditurePaymentMethod\n\n /* The tax-invoice ledger's row (ADR-0139): a requester without it sees the line, and FORBIDDEN on this path. */\n @ManyToOne(type => TaxInvoice, { nullable: true })\n @Directive('@privilege(category: \"tax-invoice\", privilege: \"query\", domainOwnerGranted: false)')\n @Field(type => TaxInvoice, { nullable: true, description: 'The purchase tax invoice this line pays' })\n taxInvoice?: TaxInvoice\n\n @RelationId((item: ExpenditureRequestItem) => item.taxInvoice)\n taxInvoiceId?: string\n\n /* The card's line is a bank-book row (ADR-0139); the line's own day, shop and amount are what a requester reads. */\n @ManyToOne(type => Transaction, { nullable: true })\n @Directive('@privilege(category: \"bank\", privilege: \"query\", domainOwnerGranted: false)')\n @Field(type => Transaction, { nullable: true, description: 'The charge on a corporate card this line explains (ADR-0178)' })\n cardTransaction?: Transaction\n\n @RelationId((item: ExpenditureRequestItem) => item.cardTransaction)\n @Field(type => ID, { nullable: true, description: 'Which card charge the line explains - its id only' })\n cardTransactionId?: string\n\n @Column({ default: false })\n @Field({ description: 'Spent on the corporate card for oneself - the holder owes it back' })\n personalUse: boolean\n\n @Field(type => [Attachment], { nullable: true, description: 'The receipts and papers the line is proven by, the first attached first' })\n files?: Attachment[]\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'When it was written' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'When it was last changed' })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true, description: 'When it was deleted - financial rows are kept, not erased' })\n deletedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who wrote it' })\n creator?: User\n\n @RelationId((item: ExpenditureRequestItem) => item.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who changed it last' })\n updater?: User\n\n @RelationId((item: ExpenditureRequestItem) => item.updater)\n updaterId?: string\n}\n"]}
|
|
@@ -15,7 +15,8 @@ let ExpenditureRequestMutation = class ExpenditureRequestMutation {
|
|
|
15
15
|
async saveExpenditureRequest(request, items, unpaidInvoicesOnly, context) {
|
|
16
16
|
const { domain, user, tx, unsafeIP, prohibitedPrivileges } = context.state;
|
|
17
17
|
const taxInvoices = await (0, auth_base_1.checkPermission)({ category: 'tax-invoice', privilege: 'query', owner: false }, user, domain, unsafeIP, prohibitedPrivileges);
|
|
18
|
-
|
|
18
|
+
const anyCard = await (0, auth_base_1.checkPermission)({ category: 'bank', privilege: 'query', owner: false }, user, domain, unsafeIP, prohibitedPrivileges);
|
|
19
|
+
return await (0, expenditure_request_service_1.saveRequest)(tx, domain, user, request, items ?? [], { taxInvoices, unpaidInvoicesOnly: !!unpaidInvoicesOnly, anyCard });
|
|
19
20
|
}
|
|
20
21
|
/** One line from the phone - it joins this month's editable reimbursement, or starts one. */
|
|
21
22
|
async addExpenditureLine(line, context) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expenditure-request-mutation.js","sourceRoot":"","sources":["../../../server/service/expenditure-request/expenditure-request-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Float, Mutation, Resolver, UseMiddleware } from 'type-graphql'\n\nimport type { FileUpload } from 'graphql-upload/GraphQLUpload.js'\nimport GraphQLUpload from 'graphql-upload/GraphQLUpload.js'\n\nimport { checkPermission } from '@things-factory/auth-base'\nimport { SpeakRefusals } from '@things-factory/shell'\n\nimport { ExpenditureRequest } from './expenditure-request'\nimport { ExpenditureRequestItem } from './expenditure-request-item'\nimport { addLine, deleteRequest, payRequests, payWithLine, saveLineFiles, saveRequest, submitRequest, withdrawRequest } from './expenditure-request-service'\nimport { ExpenditurePaymentInput, ExpenditureRequestInput, ExpenditureRequestItemPatch, NewExpenditureLine } from './expenditure-request-type'\n\n@UseMiddleware(SpeakRefusals)\n@Resolver(ExpenditureRequest)\nexport class ExpenditureRequestMutation {\n /** Write a request and its lines. A new request is the caller's; an existing one only its requester's, while editable. */\n @Directive('@transaction')\n @Directive('@privilege(category: \"accounting-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => ExpenditureRequest, { description: 'Save an expenditure request and its lines' })\n async saveExpenditureRequest(\n @Arg('request', type => ExpenditureRequestInput) request: ExpenditureRequestInput,\n @Arg('items', type => [ExpenditureRequestItemPatch], { nullable: true }) items: ExpenditureRequestItemPatch[],\n @Arg('unpaidInvoicesOnly', type => Boolean, { nullable: true }) unpaidInvoicesOnly: boolean,\n @Ctx() context: ResolverContext\n ): Promise<ExpenditureRequest> {\n const { domain, user, tx, unsafeIP, prohibitedPrivileges } = context.state\n const taxInvoices = await checkPermission({ category: 'tax-invoice', privilege: 'query', owner: false }, user, domain, unsafeIP, prohibitedPrivileges)\n return await saveRequest(tx, domain, user, request, items ?? [], { taxInvoices, unpaidInvoicesOnly: !!unpaidInvoicesOnly })\n }\n\n /** One line from the phone - it joins this month's editable reimbursement, or starts one. */\n @Directive('@transaction')\n @Directive('@privilege(category: \"accounting-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => ExpenditureRequest, { description: \"Add one expense line to the caller's reimbursement of that month\" })\n async addExpenditureLine(@Arg('line', type => NewExpenditureLine) line: NewExpenditureLine, @Ctx() context: ResolverContext): Promise<ExpenditureRequest> {\n const { domain, user, tx } = context.state\n return await addLine(tx, domain, user, line, context)\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"accounting-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => ExpenditureRequestItem, { description: \"Attach files to a line and take others off it - the line's receipts and papers\" })\n async saveExpenditureLineFiles(\n @Arg('itemId') itemId: string,\n @Arg('attach', type => [GraphQLUpload], { nullable: true }) attach: FileUpload[],\n @Arg('remove', type => [String], { nullable: true }) remove: string[],\n @Ctx() context: ResolverContext\n ): Promise<ExpenditureRequestItem> {\n const { domain, user, tx } = context.state\n return await saveLineFiles(tx, domain, user, itemId, { attach: attach ?? [], remove: remove ?? [] }, context)\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"accounting-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => ExpenditureRequest, { description: 'Put an expenditure request up for approval (상신)' })\n async submitExpenditureRequest(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<ExpenditureRequest> {\n const { domain, user, tx } = context.state\n return await submitRequest(tx, domain, user, id, context)\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"accounting-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => ExpenditureRequest, { description: 'Take a submitted request back from its approvers (회수)' })\n async withdrawExpenditureRequest(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<ExpenditureRequest> {\n const { domain, user, tx } = context.state\n return await withdrawRequest(tx, domain, user, id, context)\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"accounting-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Boolean, { description: 'Delete an editable expenditure request of the caller' })\n async deleteExpenditureRequest(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const { domain, user, tx } = context.state\n return await deleteRequest(tx, domain, user, id)\n }\n\n /** Finance paid approved requests at the bank; each becomes a withdrawal that the next statement import matches. */\n @Directive('@transaction')\n @Directive('@privilege(category: \"expenditure\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => [ExpenditureRequest], { description: 'Record that approved expenditure requests were paid' })\n async payExpenditureRequests(\n @Arg('ids', type => [String]) ids: string[],\n @Arg('payment', type => ExpenditurePaymentInput) payment: ExpenditurePaymentInput,\n @Ctx() context: ResolverContext\n ): Promise<ExpenditureRequest[]> {\n const { domain, user, tx } = context.state\n return await payRequests(tx, domain, user, ids, payment)\n }\n\n /** Pay a request with a withdrawal already in the books - a bank statement brought it in first. */\n @Directive('@transaction')\n @Directive('@privilege(category: \"expenditure\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => ExpenditureRequest, { description: 'Put a withdrawal already in the books on an approved expenditure request' })\n async payExpenditureRequestWithLine(\n @Arg('id') id: string,\n @Arg('transactionId') transactionId: string,\n @Arg('amount', type => Float, { nullable: true }) amount: number,\n @Ctx() context: ResolverContext\n ): Promise<ExpenditureRequest> {\n const { domain, user, tx } = context.state\n return await payWithLine(tx, domain, user, id, transactionId, amount)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"expenditure-request-mutation.js","sourceRoot":"","sources":["../../../server/service/expenditure-request/expenditure-request-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Float, Mutation, Resolver, UseMiddleware } from 'type-graphql'\n\nimport type { FileUpload } from 'graphql-upload/GraphQLUpload.js'\nimport GraphQLUpload from 'graphql-upload/GraphQLUpload.js'\n\nimport { checkPermission } from '@things-factory/auth-base'\nimport { SpeakRefusals } from '@things-factory/shell'\n\nimport { ExpenditureRequest } from './expenditure-request'\nimport { ExpenditureRequestItem } from './expenditure-request-item'\nimport { addLine, deleteRequest, payRequests, payWithLine, saveLineFiles, saveRequest, submitRequest, withdrawRequest } from './expenditure-request-service'\nimport { ExpenditurePaymentInput, ExpenditureRequestInput, ExpenditureRequestItemPatch, NewExpenditureLine } from './expenditure-request-type'\n\n@UseMiddleware(SpeakRefusals)\n@Resolver(ExpenditureRequest)\nexport class ExpenditureRequestMutation {\n /** Write a request and its lines. A new request is the caller's; an existing one only its requester's, while editable. */\n @Directive('@transaction')\n @Directive('@privilege(category: \"accounting-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => ExpenditureRequest, { description: 'Save an expenditure request and its lines' })\n async saveExpenditureRequest(\n @Arg('request', type => ExpenditureRequestInput) request: ExpenditureRequestInput,\n @Arg('items', type => [ExpenditureRequestItemPatch], { nullable: true }) items: ExpenditureRequestItemPatch[],\n @Arg('unpaidInvoicesOnly', type => Boolean, { nullable: true }) unpaidInvoicesOnly: boolean,\n @Ctx() context: ResolverContext\n ): Promise<ExpenditureRequest> {\n const { domain, user, tx, unsafeIP, prohibitedPrivileges } = context.state\n const taxInvoices = await checkPermission({ category: 'tax-invoice', privilege: 'query', owner: false }, user, domain, unsafeIP, prohibitedPrivileges)\n const anyCard = await checkPermission({ category: 'bank', privilege: 'query', owner: false }, user, domain, unsafeIP, prohibitedPrivileges)\n return await saveRequest(tx, domain, user, request, items ?? [], { taxInvoices, unpaidInvoicesOnly: !!unpaidInvoicesOnly, anyCard })\n }\n\n /** One line from the phone - it joins this month's editable reimbursement, or starts one. */\n @Directive('@transaction')\n @Directive('@privilege(category: \"accounting-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => ExpenditureRequest, { description: \"Add one expense line to the caller's reimbursement of that month\" })\n async addExpenditureLine(@Arg('line', type => NewExpenditureLine) line: NewExpenditureLine, @Ctx() context: ResolverContext): Promise<ExpenditureRequest> {\n const { domain, user, tx } = context.state\n return await addLine(tx, domain, user, line, context)\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"accounting-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => ExpenditureRequestItem, { description: \"Attach files to a line and take others off it - the line's receipts and papers\" })\n async saveExpenditureLineFiles(\n @Arg('itemId') itemId: string,\n @Arg('attach', type => [GraphQLUpload], { nullable: true }) attach: FileUpload[],\n @Arg('remove', type => [String], { nullable: true }) remove: string[],\n @Ctx() context: ResolverContext\n ): Promise<ExpenditureRequestItem> {\n const { domain, user, tx } = context.state\n return await saveLineFiles(tx, domain, user, itemId, { attach: attach ?? [], remove: remove ?? [] }, context)\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"accounting-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => ExpenditureRequest, { description: 'Put an expenditure request up for approval (상신)' })\n async submitExpenditureRequest(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<ExpenditureRequest> {\n const { domain, user, tx } = context.state\n return await submitRequest(tx, domain, user, id, context)\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"accounting-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => ExpenditureRequest, { description: 'Take a submitted request back from its approvers (회수)' })\n async withdrawExpenditureRequest(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<ExpenditureRequest> {\n const { domain, user, tx } = context.state\n return await withdrawRequest(tx, domain, user, id, context)\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"accounting-self\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => Boolean, { description: 'Delete an editable expenditure request of the caller' })\n async deleteExpenditureRequest(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {\n const { domain, user, tx } = context.state\n return await deleteRequest(tx, domain, user, id)\n }\n\n /** Finance paid approved requests at the bank; each becomes a withdrawal that the next statement import matches. */\n @Directive('@transaction')\n @Directive('@privilege(category: \"expenditure\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => [ExpenditureRequest], { description: 'Record that approved expenditure requests were paid' })\n async payExpenditureRequests(\n @Arg('ids', type => [String]) ids: string[],\n @Arg('payment', type => ExpenditurePaymentInput) payment: ExpenditurePaymentInput,\n @Ctx() context: ResolverContext\n ): Promise<ExpenditureRequest[]> {\n const { domain, user, tx } = context.state\n return await payRequests(tx, domain, user, ids, payment)\n }\n\n /** Pay a request with a withdrawal already in the books - a bank statement brought it in first. */\n @Directive('@transaction')\n @Directive('@privilege(category: \"expenditure\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => ExpenditureRequest, { description: 'Put a withdrawal already in the books on an approved expenditure request' })\n async payExpenditureRequestWithLine(\n @Arg('id') id: string,\n @Arg('transactionId') transactionId: string,\n @Arg('amount', type => Float, { nullable: true }) amount: number,\n @Ctx() context: ResolverContext\n ): Promise<ExpenditureRequest> {\n const { domain, user, tx } = context.state\n return await payWithLine(tx, domain, user, id, transactionId, amount)\n }\n}\n"]}
|
|
@@ -27,6 +27,8 @@ export interface RequestLine {
|
|
|
27
27
|
accountId?: string | null;
|
|
28
28
|
spentDate?: string | null;
|
|
29
29
|
taxInvoiceId?: string | null;
|
|
30
|
+
cardTransactionId?: string | null;
|
|
31
|
+
personalUse?: boolean | null;
|
|
30
32
|
}
|
|
31
33
|
/** A day as people write it - 2026-09-03, 2026.9.3, 2026/9/3 - in YYYY-MM-DD, or undefined when it is not one. */
|
|
32
34
|
export declare function dayOf(value: unknown): string | undefined;
|
|
@@ -52,8 +54,8 @@ export interface SubmittedHead {
|
|
|
52
54
|
counterpartyId?: string | null;
|
|
53
55
|
payeeAccountId?: string | null;
|
|
54
56
|
}
|
|
55
|
-
/** Why a request cannot be submitted, or undefined when it can. */
|
|
56
|
-
export declare function submitRefusal(head: SubmittedHead, lines: readonly RequestLine[]): Refusal | undefined;
|
|
57
|
+
/** Why a request cannot be submitted, or undefined when it can. Once the domain has a card, a card line names its charge. */
|
|
58
|
+
export declare function submitRefusal(head: SubmittedHead, lines: readonly RequestLine[], cardsRegistered?: boolean): Refusal | undefined;
|
|
57
59
|
/**
|
|
58
60
|
* What an approval outcome does to the request.
|
|
59
61
|
*
|
|
@@ -126,3 +128,25 @@ export declare function monthlySummary(requests: readonly SummaryRequest[]): Mon
|
|
|
126
128
|
export declare function statusCounts(requests: readonly {
|
|
127
129
|
status: RequestStatus;
|
|
128
130
|
}[]): Record<RequestStatus, number>;
|
|
131
|
+
/** A charge on a corporate card, as a line that explains it sees it (ADR-0178 결정 5). */
|
|
132
|
+
export interface CardCharge {
|
|
133
|
+
category: string;
|
|
134
|
+
type: string;
|
|
135
|
+
transactionDate?: string | null;
|
|
136
|
+
amount: number;
|
|
137
|
+
merchant?: string | null;
|
|
138
|
+
/** The user the card's holder signs in as. */
|
|
139
|
+
holderId?: string | null;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Whether the caller may explain this charge, and what the line takes from it - its day and amount, and its shop unless
|
|
143
|
+
* the line names one. The card's holder explains its charges; a card the office shares, or another's card, only someone
|
|
144
|
+
* who keeps the bank book (`mayAny`). A refund is not explained - it takes back a charge. Pure.
|
|
145
|
+
*/
|
|
146
|
+
export declare function explainCharge(charge: CardCharge, caller: {
|
|
147
|
+
id: string;
|
|
148
|
+
}, mayAny: boolean): {
|
|
149
|
+
spentDate: string | null;
|
|
150
|
+
amount: number;
|
|
151
|
+
merchant: string | null;
|
|
152
|
+
};
|
|
@@ -27,6 +27,7 @@ exports.requestForLine = requestForLine;
|
|
|
27
27
|
exports.payeeAccountDrift = payeeAccountDrift;
|
|
28
28
|
exports.monthlySummary = monthlySummary;
|
|
29
29
|
exports.statusCounts = statusCounts;
|
|
30
|
+
exports.explainCharge = explainCharge;
|
|
30
31
|
const shell_1 = require("@things-factory/shell");
|
|
31
32
|
exports.EDITABLE = ['DRAFT', 'REJECTED', 'WITHDRAWN'];
|
|
32
33
|
/** A day as people write it - 2026-09-03, 2026.9.3, 2026/9/3 - in YYYY-MM-DD, or undefined when it is not one. */
|
|
@@ -63,8 +64,8 @@ function totalsOf(lines) {
|
|
|
63
64
|
}
|
|
64
65
|
return { total, payable, count: lines.length };
|
|
65
66
|
}
|
|
66
|
-
/** Why a request cannot be submitted, or undefined when it can. */
|
|
67
|
-
function submitRefusal(head, lines) {
|
|
67
|
+
/** Why a request cannot be submitted, or undefined when it can. Once the domain has a card, a card line names its charge. */
|
|
68
|
+
function submitRefusal(head, lines, cardsRegistered = false) {
|
|
68
69
|
const { kind, status, month } = head;
|
|
69
70
|
if (!canEdit(status))
|
|
70
71
|
return new shell_1.Refusal('expenditure-submitted-already', { status: status.toLowerCase() }, `a ${status.toLowerCase()} request cannot be submitted again`);
|
|
@@ -84,6 +85,8 @@ function submitRefusal(head, lines) {
|
|
|
84
85
|
return new shell_1.Refusal('expenditure-line-day-missing', {}, 'every line needs its day');
|
|
85
86
|
if (lines.some(line => !line.category && !line.accountId))
|
|
86
87
|
return new shell_1.Refusal('expenditure-line-category-missing', {}, 'every line needs a category or an account');
|
|
88
|
+
if (cardsRegistered && lines.some(line => line.paymentMethod === 'CORPORATE_CARD' && !line.cardTransactionId))
|
|
89
|
+
return new shell_1.Refusal('expenditure-card-charge-missing', {}, 'a corporate-card line names the charge it explains');
|
|
87
90
|
if (month && lines.some(line => line.spentDate && line.spentDate.slice(0, 7) > month))
|
|
88
91
|
return new shell_1.Refusal('expenditure-line-after-month', { month }, 'a line is dated after the month the request is for');
|
|
89
92
|
return undefined;
|
|
@@ -180,4 +183,18 @@ function statusCounts(requests) {
|
|
|
180
183
|
counts[request.status]++;
|
|
181
184
|
return counts;
|
|
182
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Whether the caller may explain this charge, and what the line takes from it - its day and amount, and its shop unless
|
|
188
|
+
* the line names one. The card's holder explains its charges; a card the office shares, or another's card, only someone
|
|
189
|
+
* who keeps the bank book (`mayAny`). A refund is not explained - it takes back a charge. Pure.
|
|
190
|
+
*/
|
|
191
|
+
function explainCharge(charge, caller, mayAny) {
|
|
192
|
+
if (charge.category !== 'CARD')
|
|
193
|
+
throw new shell_1.Refusal('expenditure-card-charge-not-card', {}, 'the line is not a charge on a corporate card');
|
|
194
|
+
if (charge.type !== 'EXPENSE')
|
|
195
|
+
throw new shell_1.Refusal('expenditure-card-charge-refund', {}, 'a refund takes back a charge - explain the charge');
|
|
196
|
+
if (!mayAny && charge.holderId !== caller.id)
|
|
197
|
+
throw new shell_1.Refusal('expenditure-card-charge-not-yours', {}, "the charge is on a card you do not hold - finance explains it");
|
|
198
|
+
return { spentDate: charge.transactionDate ?? null, amount: Math.abs(Number(charge.amount)), merchant: charge.merchant ?? null };
|
|
199
|
+
}
|
|
183
200
|
//# sourceMappingURL=expenditure-request-rules.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expenditure-request-rules.js","sourceRoot":"","sources":["../../../server/service/expenditure-request/expenditure-request-rules.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The rules of an expenditure request (ADR-0135) - who may change it when, what it pays, what a decision means, and\n * the monthly summary.\n *\n * ── The life of a request ──────────────────────────────────────────────────────────────────────\n * DRAFT ──submit──▶ SUBMITTED ──approved──▶ APPROVED ──bank lines pay it──▶ (paid, then reconciled)\n * ▲ │ │\n * │ │ └─rejected──▶ REJECTED ──edit & submit again──▶ SUBMITTED\n * └────withdraw──────┘ (WITHDRAWN, editable again)\n *\n * Only the requester edits, and only while nobody is judging it (DRAFT, REJECTED, WITHDRAWN). What approvers saw is\n * what is paid: once submitted the lines and the payee's account are frozen.\n *\n * Paid and reconciled are not states stored here; they are read from the payments (§`paymentStateFrom`).\n *\n * Pure.\n */\n\nimport { Refusal } from '@things-factory/shell'\n\nexport type RequestStatus = 'DRAFT' | 'SUBMITTED' | 'APPROVED' | 'REJECTED' | 'WITHDRAWN'\nexport type RequestKind = 'VENDOR' | 'REIMBURSEMENT'\n\nexport const EDITABLE: readonly RequestStatus[] = ['DRAFT', 'REJECTED', 'WITHDRAWN']\n\nexport interface RequestLine {\n amount: number\n vatAmount?: number | null\n paymentMethod?: string | null\n category?: string | null\n accountId?: string | null\n spentDate?: string | null\n taxInvoiceId?: string | null\n}\n\n/** A day as people write it - 2026-09-03, 2026.9.3, 2026/9/3 - in YYYY-MM-DD, or undefined when it is not one. */\nexport function dayOf(value: unknown): string | undefined {\n const match = String(value ?? '').trim().match(/^(\\d{4})[-./](\\d{1,2})[-./](\\d{1,2})\\.?$/)\n if (!match) return undefined\n const [year, month, day] = match.slice(1).map(Number)\n const date = new Date(Date.UTC(year, month - 1, day))\n if (date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) return undefined\n return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`\n}\n\n/** A month as YYYY-MM, or undefined. */\nexport const monthOf = (value: unknown): string | undefined => (/^\\d{4}-(0[1-9]|1[0-2])$/.test(String(value ?? '')) ? String(value) : undefined)\n\nexport function canEdit(status: RequestStatus): boolean {\n return EDITABLE.includes(status)\n}\n\n/** A line the company pays - everything but what the corporate card already paid. */\nexport const isPayable = (line: Pick<RequestLine, 'paymentMethod'>) => line.paymentMethod !== 'CORPORATE_CARD'\n\n/**\n * What a request totals, and what of it the company is to pay. **The one place this is decided** - the request's\n * stored totals, the payment queue and every reader take it from here.\n */\nexport function totalsOf(lines: readonly RequestLine[]): { total: number; payable: number; count: number } {\n let total = 0\n let payable = 0\n for (const line of lines) {\n total += line.amount || 0\n if (isPayable(line)) payable += line.amount || 0\n }\n return { total, payable, count: lines.length }\n}\n\nexport interface SubmittedHead {\n kind: RequestKind\n status: RequestStatus\n month?: string | null\n dueDate?: string | null\n counterpartyId?: string | null\n payeeAccountId?: string | null\n}\n\n/** Why a request cannot be submitted, or undefined when it can. */\nexport function submitRefusal(head: SubmittedHead, lines: readonly RequestLine[]): Refusal | undefined {\n const { kind, status, month } = head\n if (!canEdit(status))\n return new Refusal('expenditure-submitted-already', { status: status.toLowerCase() }, `a ${status.toLowerCase()} request cannot be submitted again`)\n if (kind === 'VENDOR') {\n if (!head.counterpartyId) return new Refusal('expenditure-payee-missing', {}, 'choose who is paid')\n if (!head.payeeAccountId) return new Refusal('expenditure-payee-account-missing', {}, \"choose the counterparty's account to pay into\")\n if (!head.dueDate) return new Refusal('expenditure-due-date-missing', {}, 'set the day it is to be paid')\n }\n if (!lines.length) return new Refusal('expenditure-no-lines', {}, 'the request has no lines')\n if (lines.some(line => !(line.amount > 0))) return new Refusal('expenditure-line-amount-missing', {}, 'every line needs an amount above zero')\n if (lines.some(line => !line.spentDate)) return new Refusal('expenditure-line-day-missing', {}, 'every line needs its day')\n if (lines.some(line => !line.category && !line.accountId))\n return new Refusal('expenditure-line-category-missing', {}, 'every line needs a category or an account')\n if (month && lines.some(line => line.spentDate && line.spentDate.slice(0, 7) > month))\n return new Refusal('expenditure-line-after-month', { month }, 'a line is dated after the month the request is for')\n return undefined\n}\n\n/**\n * What an approval outcome does to the request.\n *\n * worklist calls the activity's callback on every state change, and more than once for the same one. A decision lands\n * only on the request **still waiting on that very instance** - once it has moved on (decided, or withdrawn and\n * submitted again as a new instance) a late call changes nothing.\n */\nexport function decisionOf(\n instance: { id: string; state: string },\n causedBy: readonly { state?: string }[],\n request: { status: RequestStatus; activityInstanceId?: string | null }\n): 'APPROVED' | 'REJECTED' | 'WITHDRAWN' | undefined {\n if (request.status !== 'SUBMITTED' || request.activityInstanceId !== instance.id) return undefined\n\n const state = String(instance.state).toLowerCase()\n if (state === 'ended') return 'APPROVED'\n if (state === 'aborted') return 'WITHDRAWN'\n /* A rejection does not end the instance - the rejected thread is the only sign of it. */\n if (causedBy.some(one => String(one.state).toLowerCase() === 'rejected')) return 'REJECTED'\n\n return undefined\n}\n\n/**\n * Which reimbursement a line added on the go joins - **the requester's editable one of that month.**\n *\n * The phone flow has no \"request\" step: a person snaps a receipt, types the amount and is done. The line goes to the\n * month's draft; with none, to one sent back (rejected or withdrawn) so it can go up again with the rest; with none of\n * those either, a new draft is started (undefined here). A request already with approvers or decided is never added\n * to - what they saw stays what it was.\n */\nexport function requestForLine(\n requests: readonly { id: string; status: RequestStatus; month?: string | null; updatedAt?: Date | string | null }[],\n month: string\n): string | undefined {\n const order: RequestStatus[] = ['DRAFT', 'REJECTED', 'WITHDRAWN']\n const candidates = requests\n .filter(one => one.month === month && order.includes(one.status))\n .sort((a, b) => order.indexOf(a.status) - order.indexOf(b.status) || String(b.updatedAt ?? '').localeCompare(String(a.updatedAt ?? '')))\n return candidates[0]?.id\n}\n\n/**\n * Whether the account approvers saw is still the one on file - `gone` when the counterparty's account was deleted\n * (deleting a counterparty deletes its accounts), `changed` when its number or holder is no longer what was written at\n * submission, or the account was moved to another counterparty. Finance is told before paying.\n */\nexport function payeeAccountDrift(\n approved: { counterpartyId?: string | null; payeeAccountNumber?: string | null; payeeHolderName?: string | null },\n current: { counterpartyId?: string | null; accountNumber?: string | null; holderName?: string | null; deletedAt?: Date | string | null } | null | undefined,\n keyOf: (value: unknown) => string\n): 'gone' | 'changed' | undefined {\n if (!current || current.deletedAt) return 'gone'\n if ((current.counterpartyId ?? null) !== (approved.counterpartyId ?? null)) return 'changed'\n if (keyOf(current.accountNumber) !== keyOf(approved.payeeAccountNumber)) return 'changed'\n if ((current.holderName ?? '').trim() !== (approved.payeeHolderName ?? '').trim()) return 'changed'\n return undefined\n}\n\nexport interface SummaryRequest {\n month?: string | null\n requesterId: string\n requesterName?: string | null\n status: RequestStatus\n items: RequestLine[]\n}\n\nexport interface MonthSummaryRow {\n month: string\n requesterId: string\n requesterName: string | null\n category: string\n total: number\n payable: number\n count: number\n}\n\n/**\n * The monthly summary - per month, requester and category.\n *\n * Only approved requests count; a draft or a rejected one is not spending the company agreed to. The caller shows the\n * others as counts by status.\n */\nexport function monthlySummary(requests: readonly SummaryRequest[]): MonthSummaryRow[] {\n const rows = new Map<string, MonthSummaryRow>()\n\n for (const request of requests) {\n if (request.status !== 'APPROVED' || !request.month) continue\n for (const item of request.items) {\n const category = item.category || '기타'\n const key = `${request.month}|${request.requesterId}|${category}`\n const row = rows.get(key) ?? {\n month: request.month,\n requesterId: request.requesterId,\n requesterName: request.requesterName ?? null,\n category,\n total: 0,\n payable: 0,\n count: 0\n }\n row.total += item.amount || 0\n if (isPayable(item)) row.payable += item.amount || 0\n row.count++\n rows.set(key, row)\n }\n }\n\n return [...rows.values()].sort(\n (a, b) =>\n b.month.localeCompare(a.month) ||\n String(a.requesterName ?? '').localeCompare(String(b.requesterName ?? '')) ||\n a.category.localeCompare(b.category)\n )\n}\n\n/** Requests per status - so drafts and pending ones are seen, not summed. */\nexport function statusCounts(requests: readonly { status: RequestStatus }[]): Record<RequestStatus, number> {\n const counts: Record<RequestStatus, number> = { DRAFT: 0, SUBMITTED: 0, APPROVED: 0, REJECTED: 0, WITHDRAWN: 0 }\n for (const request of requests) counts[request.status]++\n return counts\n}\n"]}
|
|
1
|
+
{"version":3,"file":"expenditure-request-rules.js","sourceRoot":"","sources":["../../../server/service/expenditure-request/expenditure-request-rules.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The rules of an expenditure request (ADR-0135) - who may change it when, what it pays, what a decision means, and\n * the monthly summary.\n *\n * ── The life of a request ──────────────────────────────────────────────────────────────────────\n * DRAFT ──submit──▶ SUBMITTED ──approved──▶ APPROVED ──bank lines pay it──▶ (paid, then reconciled)\n * ▲ │ │\n * │ │ └─rejected──▶ REJECTED ──edit & submit again──▶ SUBMITTED\n * └────withdraw──────┘ (WITHDRAWN, editable again)\n *\n * Only the requester edits, and only while nobody is judging it (DRAFT, REJECTED, WITHDRAWN). What approvers saw is\n * what is paid: once submitted the lines and the payee's account are frozen.\n *\n * Paid and reconciled are not states stored here; they are read from the payments (§`paymentStateFrom`).\n *\n * Pure.\n */\n\nimport { Refusal } from '@things-factory/shell'\n\nexport type RequestStatus = 'DRAFT' | 'SUBMITTED' | 'APPROVED' | 'REJECTED' | 'WITHDRAWN'\nexport type RequestKind = 'VENDOR' | 'REIMBURSEMENT'\n\nexport const EDITABLE: readonly RequestStatus[] = ['DRAFT', 'REJECTED', 'WITHDRAWN']\n\nexport interface RequestLine {\n amount: number\n vatAmount?: number | null\n paymentMethod?: string | null\n category?: string | null\n accountId?: string | null\n spentDate?: string | null\n taxInvoiceId?: string | null\n cardTransactionId?: string | null\n personalUse?: boolean | null\n}\n\n/** A day as people write it - 2026-09-03, 2026.9.3, 2026/9/3 - in YYYY-MM-DD, or undefined when it is not one. */\nexport function dayOf(value: unknown): string | undefined {\n const match = String(value ?? '').trim().match(/^(\\d{4})[-./](\\d{1,2})[-./](\\d{1,2})\\.?$/)\n if (!match) return undefined\n const [year, month, day] = match.slice(1).map(Number)\n const date = new Date(Date.UTC(year, month - 1, day))\n if (date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) return undefined\n return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`\n}\n\n/** A month as YYYY-MM, or undefined. */\nexport const monthOf = (value: unknown): string | undefined => (/^\\d{4}-(0[1-9]|1[0-2])$/.test(String(value ?? '')) ? String(value) : undefined)\n\nexport function canEdit(status: RequestStatus): boolean {\n return EDITABLE.includes(status)\n}\n\n/** A line the company pays - everything but what the corporate card already paid. */\nexport const isPayable = (line: Pick<RequestLine, 'paymentMethod'>) => line.paymentMethod !== 'CORPORATE_CARD'\n\n/**\n * What a request totals, and what of it the company is to pay. **The one place this is decided** - the request's\n * stored totals, the payment queue and every reader take it from here.\n */\nexport function totalsOf(lines: readonly RequestLine[]): { total: number; payable: number; count: number } {\n let total = 0\n let payable = 0\n for (const line of lines) {\n total += line.amount || 0\n if (isPayable(line)) payable += line.amount || 0\n }\n return { total, payable, count: lines.length }\n}\n\nexport interface SubmittedHead {\n kind: RequestKind\n status: RequestStatus\n month?: string | null\n dueDate?: string | null\n counterpartyId?: string | null\n payeeAccountId?: string | null\n}\n\n/** Why a request cannot be submitted, or undefined when it can. Once the domain has a card, a card line names its charge. */\nexport function submitRefusal(head: SubmittedHead, lines: readonly RequestLine[], cardsRegistered = false): Refusal | undefined {\n const { kind, status, month } = head\n if (!canEdit(status))\n return new Refusal('expenditure-submitted-already', { status: status.toLowerCase() }, `a ${status.toLowerCase()} request cannot be submitted again`)\n if (kind === 'VENDOR') {\n if (!head.counterpartyId) return new Refusal('expenditure-payee-missing', {}, 'choose who is paid')\n if (!head.payeeAccountId) return new Refusal('expenditure-payee-account-missing', {}, \"choose the counterparty's account to pay into\")\n if (!head.dueDate) return new Refusal('expenditure-due-date-missing', {}, 'set the day it is to be paid')\n }\n if (!lines.length) return new Refusal('expenditure-no-lines', {}, 'the request has no lines')\n if (lines.some(line => !(line.amount > 0))) return new Refusal('expenditure-line-amount-missing', {}, 'every line needs an amount above zero')\n if (lines.some(line => !line.spentDate)) return new Refusal('expenditure-line-day-missing', {}, 'every line needs its day')\n if (lines.some(line => !line.category && !line.accountId))\n return new Refusal('expenditure-line-category-missing', {}, 'every line needs a category or an account')\n if (cardsRegistered && lines.some(line => line.paymentMethod === 'CORPORATE_CARD' && !line.cardTransactionId))\n return new Refusal('expenditure-card-charge-missing', {}, 'a corporate-card line names the charge it explains')\n if (month && lines.some(line => line.spentDate && line.spentDate.slice(0, 7) > month))\n return new Refusal('expenditure-line-after-month', { month }, 'a line is dated after the month the request is for')\n return undefined\n}\n\n/**\n * What an approval outcome does to the request.\n *\n * worklist calls the activity's callback on every state change, and more than once for the same one. A decision lands\n * only on the request **still waiting on that very instance** - once it has moved on (decided, or withdrawn and\n * submitted again as a new instance) a late call changes nothing.\n */\nexport function decisionOf(\n instance: { id: string; state: string },\n causedBy: readonly { state?: string }[],\n request: { status: RequestStatus; activityInstanceId?: string | null }\n): 'APPROVED' | 'REJECTED' | 'WITHDRAWN' | undefined {\n if (request.status !== 'SUBMITTED' || request.activityInstanceId !== instance.id) return undefined\n\n const state = String(instance.state).toLowerCase()\n if (state === 'ended') return 'APPROVED'\n if (state === 'aborted') return 'WITHDRAWN'\n /* A rejection does not end the instance - the rejected thread is the only sign of it. */\n if (causedBy.some(one => String(one.state).toLowerCase() === 'rejected')) return 'REJECTED'\n\n return undefined\n}\n\n/**\n * Which reimbursement a line added on the go joins - **the requester's editable one of that month.**\n *\n * The phone flow has no \"request\" step: a person snaps a receipt, types the amount and is done. The line goes to the\n * month's draft; with none, to one sent back (rejected or withdrawn) so it can go up again with the rest; with none of\n * those either, a new draft is started (undefined here). A request already with approvers or decided is never added\n * to - what they saw stays what it was.\n */\nexport function requestForLine(\n requests: readonly { id: string; status: RequestStatus; month?: string | null; updatedAt?: Date | string | null }[],\n month: string\n): string | undefined {\n const order: RequestStatus[] = ['DRAFT', 'REJECTED', 'WITHDRAWN']\n const candidates = requests\n .filter(one => one.month === month && order.includes(one.status))\n .sort((a, b) => order.indexOf(a.status) - order.indexOf(b.status) || String(b.updatedAt ?? '').localeCompare(String(a.updatedAt ?? '')))\n return candidates[0]?.id\n}\n\n/**\n * Whether the account approvers saw is still the one on file - `gone` when the counterparty's account was deleted\n * (deleting a counterparty deletes its accounts), `changed` when its number or holder is no longer what was written at\n * submission, or the account was moved to another counterparty. Finance is told before paying.\n */\nexport function payeeAccountDrift(\n approved: { counterpartyId?: string | null; payeeAccountNumber?: string | null; payeeHolderName?: string | null },\n current: { counterpartyId?: string | null; accountNumber?: string | null; holderName?: string | null; deletedAt?: Date | string | null } | null | undefined,\n keyOf: (value: unknown) => string\n): 'gone' | 'changed' | undefined {\n if (!current || current.deletedAt) return 'gone'\n if ((current.counterpartyId ?? null) !== (approved.counterpartyId ?? null)) return 'changed'\n if (keyOf(current.accountNumber) !== keyOf(approved.payeeAccountNumber)) return 'changed'\n if ((current.holderName ?? '').trim() !== (approved.payeeHolderName ?? '').trim()) return 'changed'\n return undefined\n}\n\nexport interface SummaryRequest {\n month?: string | null\n requesterId: string\n requesterName?: string | null\n status: RequestStatus\n items: RequestLine[]\n}\n\nexport interface MonthSummaryRow {\n month: string\n requesterId: string\n requesterName: string | null\n category: string\n total: number\n payable: number\n count: number\n}\n\n/**\n * The monthly summary - per month, requester and category.\n *\n * Only approved requests count; a draft or a rejected one is not spending the company agreed to. The caller shows the\n * others as counts by status.\n */\nexport function monthlySummary(requests: readonly SummaryRequest[]): MonthSummaryRow[] {\n const rows = new Map<string, MonthSummaryRow>()\n\n for (const request of requests) {\n if (request.status !== 'APPROVED' || !request.month) continue\n for (const item of request.items) {\n const category = item.category || '기타'\n const key = `${request.month}|${request.requesterId}|${category}`\n const row = rows.get(key) ?? {\n month: request.month,\n requesterId: request.requesterId,\n requesterName: request.requesterName ?? null,\n category,\n total: 0,\n payable: 0,\n count: 0\n }\n row.total += item.amount || 0\n if (isPayable(item)) row.payable += item.amount || 0\n row.count++\n rows.set(key, row)\n }\n }\n\n return [...rows.values()].sort(\n (a, b) =>\n b.month.localeCompare(a.month) ||\n String(a.requesterName ?? '').localeCompare(String(b.requesterName ?? '')) ||\n a.category.localeCompare(b.category)\n )\n}\n\n/** Requests per status - so drafts and pending ones are seen, not summed. */\nexport function statusCounts(requests: readonly { status: RequestStatus }[]): Record<RequestStatus, number> {\n const counts: Record<RequestStatus, number> = { DRAFT: 0, SUBMITTED: 0, APPROVED: 0, REJECTED: 0, WITHDRAWN: 0 }\n for (const request of requests) counts[request.status]++\n return counts\n}\n\n/** A charge on a corporate card, as a line that explains it sees it (ADR-0178 결정 5). */\nexport interface CardCharge {\n category: string\n type: string\n transactionDate?: string | null\n amount: number\n merchant?: string | null\n /** The user the card's holder signs in as. */\n holderId?: string | null\n}\n\n/**\n * Whether the caller may explain this charge, and what the line takes from it - its day and amount, and its shop unless\n * the line names one. The card's holder explains its charges; a card the office shares, or another's card, only someone\n * who keeps the bank book (`mayAny`). A refund is not explained - it takes back a charge. Pure.\n */\nexport function explainCharge(\n charge: CardCharge,\n caller: { id: string },\n mayAny: boolean\n): { spentDate: string | null; amount: number; merchant: string | null } {\n if (charge.category !== 'CARD') throw new Refusal('expenditure-card-charge-not-card', {}, 'the line is not a charge on a corporate card')\n if (charge.type !== 'EXPENSE') throw new Refusal('expenditure-card-charge-refund', {}, 'a refund takes back a charge - explain the charge')\n if (!mayAny && charge.holderId !== caller.id)\n throw new Refusal('expenditure-card-charge-not-yours', {}, \"the charge is on a card you do not hold - finance explains it\")\n return { spentDate: charge.transactionDate ?? null, amount: Math.abs(Number(charge.amount)), merchant: charge.merchant ?? null }\n}\n"]}
|
|
@@ -34,6 +34,10 @@ export interface ItemPatch {
|
|
|
34
34
|
taxInvoice?: {
|
|
35
35
|
id: string;
|
|
36
36
|
} | null;
|
|
37
|
+
cardTransaction?: {
|
|
38
|
+
id: string;
|
|
39
|
+
} | null;
|
|
40
|
+
personalUse?: boolean;
|
|
37
41
|
}
|
|
38
42
|
/** A request somebody is judging (or that is decided) - it changes only after it is withdrawn. */
|
|
39
43
|
export declare const lockedRequest: (status: string) => Refusal;
|
|
@@ -52,6 +56,8 @@ export interface Citing {
|
|
|
52
56
|
taxInvoices: boolean;
|
|
53
57
|
/** Bulk preparation refuses partial/complete payments and amended evidence for individual review. */
|
|
54
58
|
unpaidInvoicesOnly?: boolean;
|
|
59
|
+
/** Whoever keeps the bank book explains any card's charges; a holder only their own card's (ADR-0178 결정 5). */
|
|
60
|
+
anyCard?: boolean;
|
|
55
61
|
}
|
|
56
62
|
/**
|
|
57
63
|
* Save a request and its lines in one go - the head, then the lines. A new request belongs to the caller; an existing
|
|
@@ -164,6 +164,38 @@ async function saveItems(tx, domain, user, request, patches, citing) {
|
|
|
164
164
|
}
|
|
165
165
|
row.taxInvoice = invoice;
|
|
166
166
|
}
|
|
167
|
+
if ('cardTransaction' in patch) {
|
|
168
|
+
row.cardTransaction = null;
|
|
169
|
+
if (patch.cardTransaction?.id) {
|
|
170
|
+
const charge = await tx.getRepository(transaction_1.Transaction).findOne({ where: { domain: { id: domain.id }, id: patch.cardTransaction.id } });
|
|
171
|
+
if (!charge)
|
|
172
|
+
throw new shell_1.Refusal('expenditure-reference-not-found', { field: 'cardTransaction' }, 'cardTransaction was not found - reload and try again');
|
|
173
|
+
const card = charge.bankAccountId ? await tx.getRepository(bank_account_1.BankAccount).findOne({ where: { id: charge.bankAccountId }, relations: ['holder'] }) : null;
|
|
174
|
+
const taken = (0, expenditure_request_rules_1.explainCharge)({ ...charge, merchant: charge.counterpartyName ?? charge.bankCounterpartyName ?? charge.bankDescription, holderId: card?.holder?.userId }, user, !!citing.anyCard);
|
|
175
|
+
const elsewhere = await repo.find({ where: { domain: { id: domain.id }, cardTransaction: { id: charge.id } }, relations: ['request'] });
|
|
176
|
+
if (elsewhere.some(one => one.id !== current?.id && one.request && !one.request.deletedAt))
|
|
177
|
+
throw new shell_1.Refusal('expenditure-card-charge-explained-twice', { day: charge.transactionDate ?? '' }, 'another line already explains this charge');
|
|
178
|
+
if ('amount' in patch && Math.abs(Number(patch.amount) - taken.amount) > 0.005)
|
|
179
|
+
throw new shell_1.Refusal('expenditure-card-charge-amount', {}, 'a card line is the amount of its charge');
|
|
180
|
+
row.cardTransaction = charge;
|
|
181
|
+
row.paymentMethod = expenditure_request_item_1.ExpenditurePaymentMethod.CORPORATE_CARD;
|
|
182
|
+
row.amount = taken.amount;
|
|
183
|
+
row.spentDate = taken.spentDate;
|
|
184
|
+
if (!('merchant' in patch) || !row.merchant)
|
|
185
|
+
row.merchant = taken.merchant;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if ('personalUse' in patch)
|
|
189
|
+
row.personalUse = !!patch.personalUse;
|
|
190
|
+
/* A card's charge and its personal use belong to a corporate-card line only. */
|
|
191
|
+
const method = row.paymentMethod ?? current?.paymentMethod;
|
|
192
|
+
const charged = 'cardTransaction' in row ? row.cardTransaction?.id : current?.cardTransactionId;
|
|
193
|
+
if (charged && method !== expenditure_request_item_1.ExpenditurePaymentMethod.CORPORATE_CARD)
|
|
194
|
+
throw new shell_1.Refusal('expenditure-card-charge-method', {}, 'a line that explains a card charge was paid with the corporate card');
|
|
195
|
+
if (charged && !('cardTransaction' in patch) && 'amount' in row && current && Math.abs(Number(row.amount) - Number(current.amount)) > 0.005)
|
|
196
|
+
throw new shell_1.Refusal('expenditure-card-charge-amount', {}, 'a card line is the amount of its charge');
|
|
197
|
+
if ((row.personalUse ?? current?.personalUse) && method !== expenditure_request_item_1.ExpenditurePaymentMethod.CORPORATE_CARD)
|
|
198
|
+
throw new shell_1.Refusal('expenditure-personal-not-card', {}, 'personal use is a corporate-card line');
|
|
167
199
|
/* What a line says of its money holds whenever it is written, not only when it is submitted. */
|
|
168
200
|
const amount = 'amount' in row ? row.amount : Number(current?.amount ?? 0);
|
|
169
201
|
if ('amount' in row && !(row.amount > 0))
|
|
@@ -276,7 +308,7 @@ async function submitRequest(tx, domain, user, id, context) {
|
|
|
276
308
|
dueDate: request.dueDate,
|
|
277
309
|
counterpartyId: request.counterpartyId,
|
|
278
310
|
payeeAccountId: request.payeeAccountId
|
|
279
|
-
}, items);
|
|
311
|
+
}, items, (await tx.getRepository(bank_account_1.BankAccount).count({ where: { domain: { id: domain.id }, accountType: bank_account_1.AccountType.CARD } })) > 0);
|
|
280
312
|
if (refusal)
|
|
281
313
|
throw refusal;
|
|
282
314
|
Object.assign(request, await payeeSnapshot(tx, domain, request, user));
|
|
@@ -478,6 +510,8 @@ async function payRequests(tx, domain, user, ids, record) {
|
|
|
478
510
|
if (!paidDate)
|
|
479
511
|
throw new shell_1.Refusal('expenditure-paid-date-missing', {}, 'paidDate: the day the money went out');
|
|
480
512
|
const bankAccount = await inDomain(tx, bank_account_1.BankAccount, domain, record.bankAccount, 'bankAccount');
|
|
513
|
+
if (bankAccount?.accountType === bank_account_1.AccountType.CARD)
|
|
514
|
+
throw new shell_1.Refusal('expenditure-paid-from-card', {}, 'a request is paid from a bank account, not a card');
|
|
481
515
|
const { requests, states } = await approvedWithStates(tx, domain, [...new Set(ids)]);
|
|
482
516
|
if (requests.length !== new Set(ids).size) {
|
|
483
517
|
const found = await tx.getRepository(expenditure_request_1.ExpenditureRequest).find({ where: { domain: { id: domain.id }, id: (0, typeorm_1.In)(ids) } });
|