@zeniai/client-epic-state 5.0.69-betaAR2 → 5.0.69-betaAR4
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/lib/entity/account/accountSelector.d.ts +0 -26
- package/lib/entity/account/accountSelector.js +1 -47
- package/lib/entity/account/accountState.d.ts +0 -5
- package/lib/entity/account/accountState.js +4 -11
- package/lib/entity/approvalRule/approvalRulePayload.d.ts +27 -5
- package/lib/entity/approvalRule/approvalRulePayload.js +131 -10
- package/lib/entity/approvalRule/approvalRuleSelector.d.ts +25 -1
- package/lib/entity/approvalRule/approvalRuleSelector.js +40 -0
- package/lib/entity/approvalRule/approvalRuleState.d.ts +48 -7
- package/lib/entity/class/classSelector.d.ts +0 -35
- package/lib/entity/class/classSelector.js +0 -50
- package/lib/esm/entity/account/accountSelector.js +1 -46
- package/lib/esm/entity/account/accountState.js +1 -7
- package/lib/esm/entity/approvalRule/approvalRulePayload.js +131 -10
- package/lib/esm/entity/approvalRule/approvalRuleSelector.js +35 -0
- package/lib/esm/entity/class/classSelector.js +0 -48
- package/lib/esm/index.js +6 -11
- package/lib/esm/view/companyHealthMetricView/companyHealthMetricViewSelector.js +1 -1
- package/lib/esm/view/companyView/helpers/cockpitHelpers.js +1 -1
- package/lib/esm/view/companyView/selector/companyManagementViewSelector.js +1 -1
- package/lib/esm/view/companyView/selector/companyPortfolioViewSelector.js +1 -1
- package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +1 -11
- package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +12 -69
- package/lib/esm/view/spendManagement/billPay/billPaySetupApproverView/epic/initializeBillPaySetupApproverViewUpdateDataEpic.js +11 -7
- package/lib/esm/view/spendManagement/billPay/billPaySetupApproverView/types/commonPayload.js +61 -16
- package/lib/esm/view/spendManagement/billPay/editBillView/editBillViewSelector.js +7 -2
- package/lib/esm/view/spendManagement/reimbursement/remiSetupApproverView/epic/initializeRemiSetupApproverViewUpdateDataEpic.js +11 -7
- package/lib/esm/view/spendManagement/spendManagementFilterHelpers.js +2 -4
- package/lib/esm/view/subscriptionView/subscriptionViewSelector.js +1 -1
- package/lib/esm/view/taskManager/taskListView/taskListReducer.js +1 -1
- package/lib/index.d.ts +8 -9
- package/lib/index.js +38 -41
- package/lib/view/companyHealthMetricView/companyHealthMetricViewSelector.js +1 -1
- package/lib/view/companyTaskManagerView/companyTaskManagerViewReducer.d.ts +2 -2
- package/lib/view/companyView/helpers/cockpitHelpers.js +1 -1
- package/lib/view/companyView/selector/companyManagementViewSelector.js +1 -1
- package/lib/view/companyView/selector/companyPortfolioViewSelector.js +1 -1
- package/lib/view/companyView/types/cockpitTypes.d.ts +2 -2
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +1 -5
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +2 -12
- package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.d.ts +1 -4
- package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +12 -71
- package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +0 -2
- package/lib/view/people/peopleTypes.d.ts +1 -1
- package/lib/view/spendManagement/billPay/billList/billListState.d.ts +1 -1
- package/lib/view/spendManagement/billPay/billPaySetupApproverView/epic/initializeBillPaySetupApproverViewUpdateDataEpic.js +10 -6
- package/lib/view/spendManagement/billPay/billPaySetupApproverView/types/commonPayload.d.ts +9 -1
- package/lib/view/spendManagement/billPay/billPaySetupApproverView/types/commonPayload.js +61 -16
- package/lib/view/spendManagement/billPay/billPaySetupApproverView/types/commonState.d.ts +31 -5
- package/lib/view/spendManagement/billPay/editBillView/editBillViewSelector.js +7 -2
- package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardList.d.ts +1 -1
- package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistory.d.ts +1 -1
- package/lib/view/spendManagement/reimbursement/remiListView/remiListState.d.ts +1 -1
- package/lib/view/spendManagement/reimbursement/remiSetupApproverView/epic/initializeRemiSetupApproverViewUpdateDataEpic.js +10 -6
- package/lib/view/spendManagement/spendManagementFilterHelpers.d.ts +1 -1
- package/lib/view/spendManagement/spendManagementFilterHelpers.js +2 -4
- package/lib/view/subscriptionView/subscriptionViewSelector.js +1 -1
- package/lib/view/taskManager/taskListView/taskList.d.ts +4 -4
- package/lib/view/taskManager/taskListView/taskListReducer.js +1 -1
- package/package.json +1 -1
- package/lib/esm/view/expenseAutomationView/transactionFilterHelpers.js +0 -228
- package/lib/view/expenseAutomationView/transactionFilterHelpers.d.ts +0 -65
- package/lib/view/expenseAutomationView/transactionFilterHelpers.js +0 -232
|
@@ -123,29 +123,3 @@ export declare const getAccountsOrdered: (filter: COABalancesFilter, accountStat
|
|
|
123
123
|
export declare const getAccountsByType: (accountState: AccountState, accountTypes: AccountType[]) => Partial<Record<AccountType, Account[]>>;
|
|
124
124
|
export declare const getAccountIdsForTypes: (accountState: AccountState, accountTypes: AccountType[]) => ID[];
|
|
125
125
|
export declare const getAccountIdsForLabels: (accountState: AccountState, labels: string[]) => ID[];
|
|
126
|
-
/**
|
|
127
|
-
* Returns all accounts currently in state (single pass over accountsByKey).
|
|
128
|
-
* Use for dropdowns that need account names and types; map to
|
|
129
|
-
* { accountId, accountName, accountType } as needed.
|
|
130
|
-
*/
|
|
131
|
-
export declare const getAllAccounts: (accountState: AccountState) => Account[];
|
|
132
|
-
export interface AccountFilterOption {
|
|
133
|
-
accountId: ID;
|
|
134
|
-
accountName: string;
|
|
135
|
-
accountType: AccountType;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Partitions accounts for transaction filter dropdowns: bank/credit_card → paymentAccountNames,
|
|
139
|
-
* all other account types → categoryOptions. Use for payment_account_name and category filter options.
|
|
140
|
-
*
|
|
141
|
-
* Memoised via `createSelector` so the composite `{categoryOptions,
|
|
142
|
-
* paymentAccountNames}` keeps a stable identity across Redux dispatches that
|
|
143
|
-
* don't change account data. Without this, every consumer using
|
|
144
|
-
* `useSelector(state => getTransactionFilterAccountOptions(state.accountState))`
|
|
145
|
-
* would re-render on every dispatch (the inline call returns a fresh outer
|
|
146
|
-
* object each time, defeating reference equality).
|
|
147
|
-
*/
|
|
148
|
-
export declare const getTransactionFilterAccountOptions: (accountState: AccountState) => {
|
|
149
|
-
categoryOptions: AccountFilterOption[];
|
|
150
|
-
paymentAccountNames: AccountFilterOption[];
|
|
151
|
-
};
|
|
@@ -3,10 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.getAccountIdsForLabels = exports.getAccountIdsForTypes = exports.getAccountsByType = exports.getAccountsOrdered = exports.getAccountBase = void 0;
|
|
7
7
|
exports.getAccount = getAccount;
|
|
8
8
|
exports.getAccountWithBalance = getAccountWithBalance;
|
|
9
|
-
const toolkit_1 = require("@reduxjs/toolkit");
|
|
10
9
|
const flatMap_1 = __importDefault(require("lodash/flatMap"));
|
|
11
10
|
const coaBalance_1 = require("../../commonStateTypes/coaBalance/coaBalance");
|
|
12
11
|
const sortBalancesByFreeRangeTotal_1 = require("../../commonStateTypes/coaBalance/sortBalancesByFreeRangeTotal");
|
|
@@ -193,48 +192,3 @@ const getAccountIdsForLabels = (accountState, labels) => {
|
|
|
193
192
|
return allAccountIdsForLabels;
|
|
194
193
|
};
|
|
195
194
|
exports.getAccountIdsForLabels = getAccountIdsForLabels;
|
|
196
|
-
/**
|
|
197
|
-
* Returns all accounts currently in state (single pass over accountsByKey).
|
|
198
|
-
* Use for dropdowns that need account names and types; map to
|
|
199
|
-
* { accountId, accountName, accountType } as needed.
|
|
200
|
-
*/
|
|
201
|
-
const getAllAccounts = (accountState) => Object.values(accountState.accountsByKey);
|
|
202
|
-
exports.getAllAccounts = getAllAccounts;
|
|
203
|
-
/**
|
|
204
|
-
* Partitions accounts for transaction filter dropdowns: bank/credit_card → paymentAccountNames,
|
|
205
|
-
* all other account types → categoryOptions. Use for payment_account_name and category filter options.
|
|
206
|
-
*
|
|
207
|
-
* Memoised via `createSelector` so the composite `{categoryOptions,
|
|
208
|
-
* paymentAccountNames}` keeps a stable identity across Redux dispatches that
|
|
209
|
-
* don't change account data. Without this, every consumer using
|
|
210
|
-
* `useSelector(state => getTransactionFilterAccountOptions(state.accountState))`
|
|
211
|
-
* would re-render on every dispatch (the inline call returns a fresh outer
|
|
212
|
-
* object each time, defeating reference equality).
|
|
213
|
-
*/
|
|
214
|
-
exports.getTransactionFilterAccountOptions = (0, toolkit_1.createSelector)(
|
|
215
|
-
// Key the memo on the underlying `accountsByKey` record rather than on
|
|
216
|
-
// `getAllAccounts` output: the latter returns a fresh `Object.values(...)`
|
|
217
|
-
// array on every call, which would defeat reselect's reference-equality
|
|
218
|
-
// check and re-run the projection on every dispatch.
|
|
219
|
-
[(accountState) => accountState.accountsByKey], (accountsByKey) => {
|
|
220
|
-
const accounts = Object.values(accountsByKey);
|
|
221
|
-
const paymentAccountNames = [];
|
|
222
|
-
const categoryOptions = [];
|
|
223
|
-
for (const account of accounts) {
|
|
224
|
-
if (account.accountType == null) {
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
const option = {
|
|
228
|
-
accountId: account.accountId,
|
|
229
|
-
accountName: account.accountName,
|
|
230
|
-
accountType: account.accountType,
|
|
231
|
-
};
|
|
232
|
-
if ((0, accountState_1.isPaymentAccountType)(account.accountType)) {
|
|
233
|
-
paymentAccountNames.push(option);
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
categoryOptions.push(option);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return { categoryOptions, paymentAccountNames };
|
|
240
|
-
});
|
|
@@ -8,14 +8,9 @@ import { RecommendationBase } from '../../commonStateTypes/recommendationBase';
|
|
|
8
8
|
import { Status } from '../../commonStateTypes/status';
|
|
9
9
|
import { ReportIDPlusForecastID } from '../../commonStateTypes/viewAndReport/viewAndReport';
|
|
10
10
|
import { ZeniUrl } from '../../zeniUrl';
|
|
11
|
-
export declare const ALL_ACCOUNT_TYPES: readonly ["bank", "credit_card", "expenses", "cogs", "income", "cash_in", "cash_out", "cash_position", "other_income", "other_expense", "accounts_receivable", "other_current_assets", "fixed_assets", "other_assets", "accounts_payable", "other_current_liabilities", "long_term_liabilities", "equity", "current_liabilities", "current_assets"];
|
|
12
11
|
export declare const toAccountType: (v: string) => "cash_position" | "fixed_assets" | "bank" | "credit_card" | "expenses" | "cogs" | "income" | "cash_in" | "cash_out" | "other_income" | "other_expense" | "accounts_receivable" | "other_current_assets" | "other_assets" | "accounts_payable" | "other_current_liabilities" | "long_term_liabilities" | "equity" | "current_liabilities" | "current_assets";
|
|
13
12
|
export type AccountType = ReturnType<typeof toAccountType>;
|
|
14
13
|
export declare const toAccountTypeStrict: (v: string | null | undefined) => AccountType | undefined;
|
|
15
|
-
/** Account types used for payment-account filters (e.g. transaction filter "payment account name"). */
|
|
16
|
-
export declare const PAYMENT_ACCOUNT_TYPES: readonly ["bank", "credit_card"];
|
|
17
|
-
export type PaymentAccountType = (typeof PAYMENT_ACCOUNT_TYPES)[number];
|
|
18
|
-
export declare function isPaymentAccountType(accountType: AccountType | undefined): accountType is PaymentAccountType;
|
|
19
14
|
export type UncategorizedAccountTypes = 'expense' | 'income';
|
|
20
15
|
declare const toAccountLabel: (v: string) => "prepaid_expenses" | "fixed_assets" | "uncategorized_income" | "uncategorized_expense" | "bank_charges_and_fees" | "travel_transportation" | "accrued_expenses";
|
|
21
16
|
export type AccountLabel = ReturnType<typeof toAccountLabel>;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toReconciliationAccountSourceStrict = exports.toReconciliationAccountSource = exports.toAccountLabelStrict = exports.
|
|
4
|
-
exports.isPaymentAccountType = isPaymentAccountType;
|
|
3
|
+
exports.toReconciliationAccountSourceStrict = exports.toReconciliationAccountSource = exports.toAccountLabelStrict = exports.toAccountTypeStrict = exports.toAccountType = void 0;
|
|
5
4
|
exports.getAccountKey = getAccountKey;
|
|
6
5
|
const nestedAccountID_1 = require("../../commonStateTypes/accountView/nestedAccountID");
|
|
7
6
|
const nestedClassID_1 = require("../../commonStateTypes/classesView/nestedClassID");
|
|
8
7
|
const stringToUnion_1 = require("../../commonStateTypes/stringToUnion");
|
|
9
|
-
|
|
8
|
+
const ALL_ACCOUNT_TYPES = [
|
|
10
9
|
'bank',
|
|
11
10
|
'credit_card',
|
|
12
11
|
'expenses',
|
|
@@ -28,16 +27,10 @@ exports.ALL_ACCOUNT_TYPES = [
|
|
|
28
27
|
'current_liabilities',
|
|
29
28
|
'current_assets',
|
|
30
29
|
];
|
|
31
|
-
const toAccountType = (v) => (0, stringToUnion_1.stringToUnion)(v,
|
|
30
|
+
const toAccountType = (v) => (0, stringToUnion_1.stringToUnion)(v, ALL_ACCOUNT_TYPES);
|
|
32
31
|
exports.toAccountType = toAccountType;
|
|
33
|
-
const toAccountTypeStrict = (v) => (0, stringToUnion_1.stringToUnionStrict)(v ?? '',
|
|
32
|
+
const toAccountTypeStrict = (v) => (0, stringToUnion_1.stringToUnionStrict)(v ?? '', ALL_ACCOUNT_TYPES);
|
|
34
33
|
exports.toAccountTypeStrict = toAccountTypeStrict;
|
|
35
|
-
/** Account types used for payment-account filters (e.g. transaction filter "payment account name"). */
|
|
36
|
-
exports.PAYMENT_ACCOUNT_TYPES = ['bank', 'credit_card'];
|
|
37
|
-
function isPaymentAccountType(accountType) {
|
|
38
|
-
return (accountType != null &&
|
|
39
|
-
exports.PAYMENT_ACCOUNT_TYPES.includes(accountType));
|
|
40
|
-
}
|
|
41
34
|
const ALL_ACCOUNT_LABELS = [
|
|
42
35
|
'uncategorized_income',
|
|
43
36
|
'uncategorized_expense',
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { ApprovalRule } from './approvalRuleState';
|
|
2
|
+
/**
|
|
3
|
+
* Condition Payload — one entry inside criteria.conditions[].
|
|
4
|
+
*
|
|
5
|
+
* field examples: 'amount' | 'vendor_id' | 'department_id'
|
|
6
|
+
* type examples: 'gte' | 'lte' | 'eq' | 'in' | 'not_in'
|
|
7
|
+
* value: number for amount comparisons; string[] for in / not_in lookups.
|
|
8
|
+
*/
|
|
9
|
+
export interface ConditionPayload {
|
|
10
|
+
field: string;
|
|
11
|
+
type: string;
|
|
12
|
+
value: number | string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Criteria Payload — list of conditions joined by `criteria_operator`.
|
|
16
|
+
*
|
|
17
|
+
* `currency_code` / `currency_symbol` apply to amount conditions inside
|
|
18
|
+
* `conditions[]`. Kept here pending confirmation that they should remain.
|
|
19
|
+
*/
|
|
2
20
|
export interface CriteriaPayload {
|
|
21
|
+
conditions: ConditionPayload[];
|
|
22
|
+
criteria_operator: string;
|
|
3
23
|
currency_code: string;
|
|
4
24
|
currency_symbol: string;
|
|
5
|
-
min: number;
|
|
6
|
-
range_entity: string;
|
|
7
|
-
type: string;
|
|
8
|
-
max?: number | null;
|
|
9
25
|
}
|
|
10
26
|
export interface ActorPayload {
|
|
11
27
|
type: string;
|
|
@@ -25,11 +41,17 @@ export interface StepPayload {
|
|
|
25
41
|
export interface ApprovalRulePayload {
|
|
26
42
|
approval_rule_id: string;
|
|
27
43
|
create_time: string;
|
|
28
|
-
criteria: CriteriaPayload
|
|
44
|
+
criteria: CriteriaPayload;
|
|
29
45
|
entity_type: string;
|
|
30
46
|
name: string | null;
|
|
31
47
|
steps: StepPayload[];
|
|
32
48
|
update_time: string;
|
|
49
|
+
are_approvals_serialized?: boolean;
|
|
50
|
+
description?: string | null;
|
|
51
|
+
is_fallback?: boolean;
|
|
52
|
+
pending_approvals_count?: number;
|
|
53
|
+
pending_entity_approval_update_status?: string;
|
|
54
|
+
priority?: number;
|
|
33
55
|
version?: number | string;
|
|
34
56
|
}
|
|
35
57
|
/**
|
|
@@ -17,19 +17,140 @@ const toApprovalRule = (payload) => ({
|
|
|
17
17
|
version: payload.version,
|
|
18
18
|
criteria: toApprovalCriteria(payload.criteria),
|
|
19
19
|
steps: toApprovalSteps(payload.steps),
|
|
20
|
+
description: payload.description ?? undefined,
|
|
21
|
+
priority: payload.priority,
|
|
22
|
+
isFallback: payload.is_fallback ?? false,
|
|
23
|
+
// separationOfDuties: backend doesn't ship this field yet; left undefined
|
|
24
|
+
// until the spec is finalized.
|
|
25
|
+
areApprovalsSerialized: payload.are_approvals_serialized,
|
|
26
|
+
pendingApprovalsCount: payload.pending_approvals_count,
|
|
27
|
+
pendingEntityApprovalUpdateStatus: payload.pending_entity_approval_update_status,
|
|
20
28
|
});
|
|
21
29
|
exports.toApprovalRule = toApprovalRule;
|
|
30
|
+
/**
|
|
31
|
+
* Maps the wire-level criteria block into the state-side discriminated
|
|
32
|
+
* `Criteria[]` union.
|
|
33
|
+
*
|
|
34
|
+
* The wire keeps each condition as a flat row inside `conditions[]`.
|
|
35
|
+
* Amount comparisons can show up as a `gte` only, a `lte` only, or a
|
|
36
|
+
* `gte` + `lte` pair — those collapse into a single AmountCriteria whose
|
|
37
|
+
* `comparator` is `greater_than`, `less_than`, or `range` respectively.
|
|
38
|
+
*
|
|
39
|
+
* Vendor and department conditions map one-to-one: `in` becomes `is`,
|
|
40
|
+
* `not_in` becomes `is_not`.
|
|
41
|
+
*
|
|
42
|
+
* Resilient to two forms of upstream drift:
|
|
43
|
+
* - Missing / null criteria → returns [].
|
|
44
|
+
* - Legacy criteria shape (array of amount-range objects, the pre-3.0
|
|
45
|
+
* wire) → converted to an AmountCriteria so existing rules in production
|
|
46
|
+
* still render while the backend rolls out.
|
|
47
|
+
*/
|
|
22
48
|
const toApprovalCriteria = (payload) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
49
|
+
if (payload == null) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(payload)) {
|
|
53
|
+
// Legacy pre-3.0 wire: an array of amount-range entries.
|
|
54
|
+
return legacyAmountRangeToCriteria(payload);
|
|
55
|
+
}
|
|
56
|
+
if (!Array.isArray(payload.conditions)) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
const results = [];
|
|
60
|
+
const amountCriteria = toAmountCriteria(payload);
|
|
61
|
+
if (amountCriteria != null) {
|
|
62
|
+
results.push(amountCriteria);
|
|
63
|
+
}
|
|
64
|
+
payload.conditions
|
|
65
|
+
.filter((condition) => condition.field === 'vendor_id')
|
|
66
|
+
.forEach((condition) => {
|
|
67
|
+
const vendor = toVendorCriteria(condition);
|
|
68
|
+
if (vendor != null) {
|
|
69
|
+
results.push(vendor);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
payload.conditions
|
|
73
|
+
.filter((condition) => condition.field === 'department_id')
|
|
74
|
+
.forEach((condition) => {
|
|
75
|
+
const department = toDepartmentCriteria(condition);
|
|
76
|
+
if (department != null) {
|
|
77
|
+
results.push(department);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return results;
|
|
81
|
+
};
|
|
82
|
+
const legacyAmountRangeToCriteria = (legacy) => {
|
|
83
|
+
if (legacy.length === 0) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const first = legacy[0] ?? {};
|
|
87
|
+
const currency = first.currency_code ?? 'USD';
|
|
88
|
+
const symbol = first.currency_symbol ?? '$';
|
|
89
|
+
const min = typeof first.min === 'number'
|
|
90
|
+
? (0, amount_1.toAmount)(first.min, currency, symbol)
|
|
91
|
+
: undefined;
|
|
92
|
+
const max = typeof first.max === 'number'
|
|
93
|
+
? (0, amount_1.toAmount)(first.max, currency, symbol)
|
|
94
|
+
: undefined;
|
|
95
|
+
if (min == null && max == null) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
const comparator = min != null && max != null
|
|
99
|
+
? 'range'
|
|
100
|
+
: min != null
|
|
101
|
+
? 'greater_than'
|
|
102
|
+
: 'less_than';
|
|
103
|
+
return [{ type: 'amount', comparator, min, max }];
|
|
104
|
+
};
|
|
105
|
+
const toAmountCriteria = (payload) => {
|
|
106
|
+
if (!Array.isArray(payload.conditions)) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const amountConditions = payload.conditions.filter((condition) => condition.field === 'amount');
|
|
110
|
+
const gteCondition = amountConditions.find((condition) => condition.type === 'gte' || condition.type === 'eq');
|
|
111
|
+
const lteCondition = amountConditions.find((condition) => condition.type === 'lte');
|
|
112
|
+
if (gteCondition == null && lteCondition == null) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
const currencyCode = payload.currency_code ?? 'USD';
|
|
116
|
+
const currencySymbol = payload.currency_symbol ?? '$';
|
|
117
|
+
const min = typeof gteCondition?.value === 'number'
|
|
118
|
+
? (0, amount_1.toAmount)(gteCondition.value, currencyCode, currencySymbol)
|
|
119
|
+
: undefined;
|
|
120
|
+
const max = typeof lteCondition?.value === 'number'
|
|
121
|
+
? (0, amount_1.toAmount)(lteCondition.value, currencyCode, currencySymbol)
|
|
122
|
+
: undefined;
|
|
123
|
+
let comparator;
|
|
124
|
+
if (min != null && max != null) {
|
|
125
|
+
comparator = 'range';
|
|
126
|
+
}
|
|
127
|
+
else if (min != null) {
|
|
128
|
+
comparator = 'greater_than';
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
comparator = 'less_than';
|
|
132
|
+
}
|
|
133
|
+
return { type: 'amount', comparator, min, max };
|
|
134
|
+
};
|
|
135
|
+
const toVendorCriteria = (condition) => {
|
|
136
|
+
if (!Array.isArray(condition.value)) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
type: 'vendor',
|
|
141
|
+
operator: condition.type === 'not_in' ? 'is_not' : 'is',
|
|
142
|
+
vendorIds: condition.value,
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
const toDepartmentCriteria = (condition) => {
|
|
146
|
+
if (!Array.isArray(condition.value)) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
type: 'department',
|
|
151
|
+
operator: condition.type === 'not_in' ? 'is_not' : 'is',
|
|
152
|
+
departmentIds: condition.value,
|
|
153
|
+
};
|
|
33
154
|
};
|
|
34
155
|
const toApprovalSteps = (payload) => {
|
|
35
156
|
return payload.map((step) => ({
|
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
import { ID } from '../../commonStateTypes/common';
|
|
2
|
-
import {
|
|
2
|
+
import { EntityType } from '../entityApprovalStatus/entityApprovalStatusState';
|
|
3
|
+
import { AmountCriteria, ApprovalRule, ApprovalRuleState, Criteria, DepartmentCriteria, VendorCriteria } from './approvalRuleState';
|
|
3
4
|
export declare function getApprovalRuleById(approvalRuleState: ApprovalRuleState, approvalRuleId: ID): ApprovalRule | undefined;
|
|
4
5
|
export declare function getApprovalRulesByIds(approvalRuleState: ApprovalRuleState, approvalRuleIds: ID[]): ApprovalRule[];
|
|
6
|
+
/**
|
|
7
|
+
* Returns the amount-range portion of a rule's criteria, if any.
|
|
8
|
+
*
|
|
9
|
+
* Use this whenever a caller needs to read amount min/max from the new
|
|
10
|
+
* discriminated `Criteria` union without inlining a type guard.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getAmountCriteria(criteria: Criteria[]): AmountCriteria | undefined;
|
|
13
|
+
export declare function getVendorCriteria(criteria: Criteria[]): VendorCriteria | undefined;
|
|
14
|
+
export declare function getDepartmentCriteria(criteria: Criteria[]): DepartmentCriteria | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Returns all rules of the given entityType, sorted by `priority` ascending.
|
|
17
|
+
*
|
|
18
|
+
* Rules without a `priority` value sort to the end (treated as Infinity).
|
|
19
|
+
* Use this for the list page where rules render in evaluation order.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getApprovalRulesByEntityType(approvalRuleState: ApprovalRuleState, entityType: EntityType): ApprovalRule[];
|
|
22
|
+
/**
|
|
23
|
+
* Returns the tenant's fallback rule for the given entityType, if one exists.
|
|
24
|
+
*
|
|
25
|
+
* The fallback rule is shown by the "fallback engine active" banner on the
|
|
26
|
+
* list page and is used to handle bills/reimbursements that match no other rule.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getFallbackApprovalRule(approvalRuleState: ApprovalRuleState, entityType: EntityType): ApprovalRule | undefined;
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getApprovalRuleById = getApprovalRuleById;
|
|
4
4
|
exports.getApprovalRulesByIds = getApprovalRulesByIds;
|
|
5
|
+
exports.getAmountCriteria = getAmountCriteria;
|
|
6
|
+
exports.getVendorCriteria = getVendorCriteria;
|
|
7
|
+
exports.getDepartmentCriteria = getDepartmentCriteria;
|
|
8
|
+
exports.getApprovalRulesByEntityType = getApprovalRulesByEntityType;
|
|
9
|
+
exports.getFallbackApprovalRule = getFallbackApprovalRule;
|
|
5
10
|
function getApprovalRuleById(approvalRuleState, approvalRuleId) {
|
|
6
11
|
return approvalRuleState.approvalRuleById[approvalRuleId] ?? undefined;
|
|
7
12
|
}
|
|
@@ -11,3 +16,38 @@ function getApprovalRulesByIds(approvalRuleState, approvalRuleIds) {
|
|
|
11
16
|
.filter((value) => value != null);
|
|
12
17
|
return approvalRulesByIds;
|
|
13
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Returns the amount-range portion of a rule's criteria, if any.
|
|
21
|
+
*
|
|
22
|
+
* Use this whenever a caller needs to read amount min/max from the new
|
|
23
|
+
* discriminated `Criteria` union without inlining a type guard.
|
|
24
|
+
*/
|
|
25
|
+
function getAmountCriteria(criteria) {
|
|
26
|
+
return criteria.find((c) => c.type === 'amount');
|
|
27
|
+
}
|
|
28
|
+
function getVendorCriteria(criteria) {
|
|
29
|
+
return criteria.find((c) => c.type === 'vendor');
|
|
30
|
+
}
|
|
31
|
+
function getDepartmentCriteria(criteria) {
|
|
32
|
+
return criteria.find((c) => c.type === 'department');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Returns all rules of the given entityType, sorted by `priority` ascending.
|
|
36
|
+
*
|
|
37
|
+
* Rules without a `priority` value sort to the end (treated as Infinity).
|
|
38
|
+
* Use this for the list page where rules render in evaluation order.
|
|
39
|
+
*/
|
|
40
|
+
function getApprovalRulesByEntityType(approvalRuleState, entityType) {
|
|
41
|
+
return Object.values(approvalRuleState.approvalRuleById)
|
|
42
|
+
.filter((rule) => rule.entityType === entityType)
|
|
43
|
+
.sort((a, b) => (a.priority ?? Infinity) - (b.priority ?? Infinity));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Returns the tenant's fallback rule for the given entityType, if one exists.
|
|
47
|
+
*
|
|
48
|
+
* The fallback rule is shown by the "fallback engine active" banner on the
|
|
49
|
+
* list page and is used to handle bills/reimbursements that match no other rule.
|
|
50
|
+
*/
|
|
51
|
+
function getFallbackApprovalRule(approvalRuleState, entityType) {
|
|
52
|
+
return Object.values(approvalRuleState.approvalRuleById).find((rule) => rule.entityType === entityType && rule.isFallback === true);
|
|
53
|
+
}
|
|
@@ -25,14 +25,37 @@ export interface Actor {
|
|
|
25
25
|
subType?: AttributeType | RoleType;
|
|
26
26
|
userId?: ID;
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Approval rule criteria — discriminated union over the three condition
|
|
30
|
+
* types the wire format supports: amount, vendor, and department.
|
|
31
|
+
*
|
|
32
|
+
* State-side, each rule carries a `Criteria[]` with at most one variant of
|
|
33
|
+
* each kind (e.g. one AmountCriteria + one VendorCriteria + one DepartmentCriteria).
|
|
34
|
+
*
|
|
35
|
+
* Form-side, the rule create/edit form uses `ApprovalRuleFormCriteria`
|
|
36
|
+
* (defined in commonState) — a structured object with named slots for
|
|
37
|
+
* amount/vendor/department. Epics translate between the two.
|
|
38
|
+
*/
|
|
39
|
+
export type AmountComparator = 'greater_than' | 'less_than' | 'range';
|
|
40
|
+
export interface AmountCriteria {
|
|
41
|
+
comparator: AmountComparator;
|
|
42
|
+
type: 'amount';
|
|
43
|
+
/** Upper bound. Present for `less_than` and `range`. */
|
|
44
|
+
max?: Amount;
|
|
45
|
+
/** Lower bound. Present for `greater_than` and `range`. */
|
|
46
|
+
min?: Amount;
|
|
47
|
+
}
|
|
48
|
+
export interface VendorCriteria {
|
|
49
|
+
operator: 'is' | 'is_not';
|
|
50
|
+
type: 'vendor';
|
|
51
|
+
vendorIds: ID[];
|
|
52
|
+
}
|
|
53
|
+
export interface DepartmentCriteria {
|
|
54
|
+
departmentIds: ID[];
|
|
55
|
+
operator: 'is' | 'is_not';
|
|
56
|
+
type: 'department';
|
|
35
57
|
}
|
|
58
|
+
export type Criteria = AmountCriteria | VendorCriteria | DepartmentCriteria;
|
|
36
59
|
export interface Step {
|
|
37
60
|
actors: Actor[];
|
|
38
61
|
operator: StepOperatorType;
|
|
@@ -51,7 +74,25 @@ export interface ApprovalRule {
|
|
|
51
74
|
entityType: EntityType;
|
|
52
75
|
steps: Step[];
|
|
53
76
|
updateTime: ZeniDate;
|
|
77
|
+
/** Backend bookkeeping: whether approval steps must run serialized. */
|
|
78
|
+
areApprovalsSerialized?: boolean;
|
|
79
|
+
/** Free-text description shown beneath the rule name on the list page. */
|
|
80
|
+
description?: string;
|
|
81
|
+
/** True when this rule is the tenant's default fallback rule. */
|
|
82
|
+
isFallback?: boolean;
|
|
54
83
|
name?: string;
|
|
84
|
+
/** Backend bookkeeping: number of entities currently pending against this rule. */
|
|
85
|
+
pendingApprovalsCount?: number;
|
|
86
|
+
/** Backend bookkeeping: status of background updates against this rule. */
|
|
87
|
+
pendingEntityApprovalUpdateStatus?: string;
|
|
88
|
+
/** Rule's position in the evaluation order; lower number = higher priority. */
|
|
89
|
+
priority?: number;
|
|
90
|
+
/**
|
|
91
|
+
* When on, the creator of a bill/reimbursement cannot auto-approve
|
|
92
|
+
* their own request even if they are also an approver. Backend spec
|
|
93
|
+
* is still pending — kept optional for now.
|
|
94
|
+
*/
|
|
95
|
+
separationOfDuties?: boolean;
|
|
55
96
|
version?: number | string;
|
|
56
97
|
}
|
|
57
98
|
export interface Approvers {
|
|
@@ -5,16 +5,6 @@ import { ReportID } from '../../commonStateTypes/viewAndReport/viewAndReport';
|
|
|
5
5
|
import { AccountState } from '../account/accountState';
|
|
6
6
|
import { ClassReport } from './classSelectorTypes';
|
|
7
7
|
import { Class, ClassState } from './classState';
|
|
8
|
-
/**
|
|
9
|
-
* Minimal class shape consumed by filter dropdowns. Only the identity and
|
|
10
|
-
* the display label are needed — payload size and prop-type breadth are
|
|
11
|
-
* kept small so consumers can't accidentally rely on `Class` fields that
|
|
12
|
-
* the projection does not carry.
|
|
13
|
-
*/
|
|
14
|
-
export interface ClassFilterOption {
|
|
15
|
-
classId: ID;
|
|
16
|
-
className: string;
|
|
17
|
-
}
|
|
18
8
|
export declare function getClassesByIds(classState: ClassState, id: {
|
|
19
9
|
classIds: ID[];
|
|
20
10
|
reportId: ReportID;
|
|
@@ -30,28 +20,3 @@ export declare function getClassReport(classState: ClassState, accountState: Acc
|
|
|
30
20
|
reportId: ReportID;
|
|
31
21
|
classesViewParentId?: ClassesViewParentID;
|
|
32
22
|
}, filter: COABalancesFilter): ClassReport | undefined;
|
|
33
|
-
/**
|
|
34
|
-
* Returns all classes currently in state (single pass over classesByKey).
|
|
35
|
-
* Use for dropdowns that need class names; map to { classId, className } as needed.
|
|
36
|
-
*/
|
|
37
|
-
export declare function getAllClasses(classState: ClassState): Class[];
|
|
38
|
-
/**
|
|
39
|
-
* Memoised projection of `getAllClasses` to the `{classId, className}` shape
|
|
40
|
-
* expected by filter dropdowns. Consumers should prefer this over inlining a
|
|
41
|
-
* `getAllClasses(...).map(...)` inside `useSelector`: the inline map allocates
|
|
42
|
-
* a fresh array on every Redux dispatch, breaking `useSelector`'s
|
|
43
|
-
* reference-equality optimisation and triggering re-renders on every state
|
|
44
|
-
* change unrelated to class data.
|
|
45
|
-
*
|
|
46
|
-
* Two cleanups happen here, intentionally inside the selector so every
|
|
47
|
-
* consumer benefits uniformly:
|
|
48
|
-
* 1. **Drop entries with a blank `className`.** Soft-deleted classes can
|
|
49
|
-
* retain their classId in state while the human name is blanked, which
|
|
50
|
-
* would otherwise render as an empty row in the filter dropdown.
|
|
51
|
-
* 2. **Dedupe by `classId`.** `classesByKey` is keyed by
|
|
52
|
-
* `${classId}-${reportId}` (see `getClassKey`), so a class loaded under
|
|
53
|
-
* multiple report contexts appears as multiple entries from
|
|
54
|
-
* `Object.values(classesByKey)`. The first non-blank entry per
|
|
55
|
-
* `classId` wins.
|
|
56
|
-
*/
|
|
57
|
-
export declare const getClassFilterOptions: (classState: ClassState) => ClassFilterOption[];
|
|
@@ -3,12 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getClassFilterOptions = void 0;
|
|
7
6
|
exports.getClassesByIds = getClassesByIds;
|
|
8
7
|
exports.getClassById = getClassById;
|
|
9
8
|
exports.getClassReport = getClassReport;
|
|
10
|
-
exports.getAllClasses = getAllClasses;
|
|
11
|
-
const toolkit_1 = require("@reduxjs/toolkit");
|
|
12
9
|
const get_1 = __importDefault(require("lodash/get"));
|
|
13
10
|
const coaBalance_1 = require("../../commonStateTypes/coaBalance/coaBalance");
|
|
14
11
|
const accountSelector_1 = require("../account/accountSelector");
|
|
@@ -61,50 +58,3 @@ function getClassReport(classState, accountState, id, filter) {
|
|
|
61
58
|
accounts: accountReport,
|
|
62
59
|
};
|
|
63
60
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Returns all classes currently in state (single pass over classesByKey).
|
|
66
|
-
* Use for dropdowns that need class names; map to { classId, className } as needed.
|
|
67
|
-
*/
|
|
68
|
-
function getAllClasses(classState) {
|
|
69
|
-
return Object.values(classState.classesByKey);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Memoised projection of `getAllClasses` to the `{classId, className}` shape
|
|
73
|
-
* expected by filter dropdowns. Consumers should prefer this over inlining a
|
|
74
|
-
* `getAllClasses(...).map(...)` inside `useSelector`: the inline map allocates
|
|
75
|
-
* a fresh array on every Redux dispatch, breaking `useSelector`'s
|
|
76
|
-
* reference-equality optimisation and triggering re-renders on every state
|
|
77
|
-
* change unrelated to class data.
|
|
78
|
-
*
|
|
79
|
-
* Two cleanups happen here, intentionally inside the selector so every
|
|
80
|
-
* consumer benefits uniformly:
|
|
81
|
-
* 1. **Drop entries with a blank `className`.** Soft-deleted classes can
|
|
82
|
-
* retain their classId in state while the human name is blanked, which
|
|
83
|
-
* would otherwise render as an empty row in the filter dropdown.
|
|
84
|
-
* 2. **Dedupe by `classId`.** `classesByKey` is keyed by
|
|
85
|
-
* `${classId}-${reportId}` (see `getClassKey`), so a class loaded under
|
|
86
|
-
* multiple report contexts appears as multiple entries from
|
|
87
|
-
* `Object.values(classesByKey)`. The first non-blank entry per
|
|
88
|
-
* `classId` wins.
|
|
89
|
-
*/
|
|
90
|
-
exports.getClassFilterOptions = (0, toolkit_1.createSelector)(
|
|
91
|
-
// Key the memo on the underlying `classesByKey` record rather than on
|
|
92
|
-
// `getAllClasses` output: the latter returns a fresh `Object.values(...)`
|
|
93
|
-
// array on every call, which would defeat reselect's reference-equality
|
|
94
|
-
// check and re-run the projection on every dispatch.
|
|
95
|
-
[(classState) => classState.classesByKey], (classesByKey) => {
|
|
96
|
-
const classes = Object.values(classesByKey);
|
|
97
|
-
const seenClassIds = new Set();
|
|
98
|
-
const options = [];
|
|
99
|
-
for (const c of classes) {
|
|
100
|
-
if (c.classId == null || seenClassIds.has(c.classId)) {
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
if (c.className == null || c.className.trim() === '') {
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
seenClassIds.add(c.classId);
|
|
107
|
-
options.push({ classId: c.classId, className: c.className });
|
|
108
|
-
}
|
|
109
|
-
return options;
|
|
110
|
-
});
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { createSelector } from '@reduxjs/toolkit';
|
|
2
1
|
import flatMap from 'lodash/flatMap';
|
|
3
2
|
import { getCOABalances } from '../../commonStateTypes/coaBalance/coaBalance';
|
|
4
3
|
import { sortComparator } from '../../commonStateTypes/coaBalance/sortBalancesByFreeRangeTotal';
|
|
5
|
-
import { getAccountKey,
|
|
4
|
+
import { getAccountKey, } from './accountState';
|
|
6
5
|
function getCommonAccountFields(accountState, id) {
|
|
7
6
|
const key = getAccountKey(id.reportId, id.accountId, id.classesViewParentId, id.accountsViewParentId, id.projectsViewParentId);
|
|
8
7
|
const account = accountState.accountsByKey[key];
|
|
@@ -180,47 +179,3 @@ export const getAccountIdsForLabels = (accountState, labels) => {
|
|
|
180
179
|
});
|
|
181
180
|
return allAccountIdsForLabels;
|
|
182
181
|
};
|
|
183
|
-
/**
|
|
184
|
-
* Returns all accounts currently in state (single pass over accountsByKey).
|
|
185
|
-
* Use for dropdowns that need account names and types; map to
|
|
186
|
-
* { accountId, accountName, accountType } as needed.
|
|
187
|
-
*/
|
|
188
|
-
export const getAllAccounts = (accountState) => Object.values(accountState.accountsByKey);
|
|
189
|
-
/**
|
|
190
|
-
* Partitions accounts for transaction filter dropdowns: bank/credit_card → paymentAccountNames,
|
|
191
|
-
* all other account types → categoryOptions. Use for payment_account_name and category filter options.
|
|
192
|
-
*
|
|
193
|
-
* Memoised via `createSelector` so the composite `{categoryOptions,
|
|
194
|
-
* paymentAccountNames}` keeps a stable identity across Redux dispatches that
|
|
195
|
-
* don't change account data. Without this, every consumer using
|
|
196
|
-
* `useSelector(state => getTransactionFilterAccountOptions(state.accountState))`
|
|
197
|
-
* would re-render on every dispatch (the inline call returns a fresh outer
|
|
198
|
-
* object each time, defeating reference equality).
|
|
199
|
-
*/
|
|
200
|
-
export const getTransactionFilterAccountOptions = createSelector(
|
|
201
|
-
// Key the memo on the underlying `accountsByKey` record rather than on
|
|
202
|
-
// `getAllAccounts` output: the latter returns a fresh `Object.values(...)`
|
|
203
|
-
// array on every call, which would defeat reselect's reference-equality
|
|
204
|
-
// check and re-run the projection on every dispatch.
|
|
205
|
-
[(accountState) => accountState.accountsByKey], (accountsByKey) => {
|
|
206
|
-
const accounts = Object.values(accountsByKey);
|
|
207
|
-
const paymentAccountNames = [];
|
|
208
|
-
const categoryOptions = [];
|
|
209
|
-
for (const account of accounts) {
|
|
210
|
-
if (account.accountType == null) {
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
const option = {
|
|
214
|
-
accountId: account.accountId,
|
|
215
|
-
accountName: account.accountName,
|
|
216
|
-
accountType: account.accountType,
|
|
217
|
-
};
|
|
218
|
-
if (isPaymentAccountType(account.accountType)) {
|
|
219
|
-
paymentAccountNames.push(option);
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
categoryOptions.push(option);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return { categoryOptions, paymentAccountNames };
|
|
226
|
-
});
|