@zeniai/client-epic-state 5.2.7 → 5.2.8-betaAK0

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.
Files changed (50) hide show
  1. package/lib/entity/invoicing/invoicingCommonPayload.d.ts +36 -2
  2. package/lib/entity/invoicing/invoicingCommonPayload.js +89 -11
  3. package/lib/entity/invoicing/product/productPayload.d.ts +10 -0
  4. package/lib/entity/invoicing/product/productPayload.js +14 -2
  5. package/lib/esm/entity/invoicing/invoicingCommonPayload.js +86 -10
  6. package/lib/esm/entity/invoicing/product/productPayload.js +14 -2
  7. package/lib/esm/index.js +3 -2
  8. package/lib/esm/view/accountList/accountListReducer.js +0 -1
  9. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/backgroundRefetchReviewTabEpic.js +1 -0
  10. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +1 -0
  11. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +56 -11
  12. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +2 -1
  13. package/lib/esm/view/expenseAutomationView/types/transactionGroup.js +21 -0
  14. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +24 -9
  15. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +2 -1
  16. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +6 -1
  17. package/lib/esm/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +2 -0
  18. package/lib/esm/view/invoicing/invoicingQboView/epics/fetchInvoicingAccountingPickersEpic.js +10 -5
  19. package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +47 -3
  20. package/lib/esm/view/invoicing/settingsView/settingsViewEpics.js +17 -2
  21. package/lib/esm/view/invoicing/settingsView/settingsViewSelector.js +7 -3
  22. package/lib/index.d.ts +3 -2
  23. package/lib/index.js +52 -47
  24. package/lib/view/accountList/accountListReducer.js +0 -1
  25. package/lib/view/accountList/accountListState.d.ts +1 -1
  26. package/lib/view/expenseAutomationView/epics/transactionCategorization/backgroundRefetchReviewTabEpic.js +1 -0
  27. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +1 -0
  28. package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +11 -0
  29. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +4 -1
  30. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +57 -12
  31. package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +2 -0
  32. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +2 -1
  33. package/lib/view/expenseAutomationView/types/transactionGroup.d.ts +21 -0
  34. package/lib/view/expenseAutomationView/types/transactionGroup.js +25 -0
  35. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +11 -0
  36. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.d.ts +12 -1
  37. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +24 -9
  38. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.d.ts +5 -0
  39. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +1 -0
  40. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +6 -1
  41. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.d.ts +1 -0
  42. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +2 -0
  43. package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingAccountingPickersEpic.js +10 -5
  44. package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.d.ts +19 -2
  45. package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +52 -4
  46. package/lib/view/invoicing/settingsView/settingsViewEpics.d.ts +1 -1
  47. package/lib/view/invoicing/settingsView/settingsViewEpics.js +17 -2
  48. package/lib/view/invoicing/settingsView/settingsViewSelector.d.ts +11 -4
  49. package/lib/view/invoicing/settingsView/settingsViewSelector.js +7 -3
  50. package/package.json +1 -1
@@ -1,3 +1,5 @@
1
+ import { AccountBasePayload } from '../account/accountPayload';
2
+ import { AccountBase } from '../account/accountState';
1
3
  import { AddressPayload } from '../address/addressPayload';
2
4
  export type InvoicingPriceType = 'tax_exclusive' | 'tax_inclusive';
3
5
  export declare const INVOICING_DUNNING_ACTIONS: readonly ["email_only", "email_and_retry", "do_nothing", "retry_only"];
@@ -88,23 +90,55 @@ export interface InvoicingStripeAccountPayload {
88
90
  export declare const INVOICING_QBO_CUSTOMER_MATCHING_MODES: readonly ["link_existing", "always_create"];
89
91
  export type InvoicingQboCustomerMatchingMode = (typeof INVOICING_QBO_CUSTOMER_MATCHING_MODES)[number];
90
92
  export declare function toInvoicingQboCustomerMatchingMode(value: string): InvoicingQboCustomerMatchingMode;
93
+ /**
94
+ * A mapped account as the newer settings payload returns it: the platform id,
95
+ * plus hydrated fields the client only displays.
96
+ */
97
+ /**
98
+ * Account mappings arrive in two shapes. The original sends a bare id per slot
99
+ * (`deposit_account_id`); the newer one sends the account itself
100
+ * (`deposit_account`). Both are declared because a tenant can be served either
101
+ * depending on when its backend rolled, and a page that understands only one
102
+ * renders an empty picker over data that exists. `Partial` because a read may
103
+ * carry only the ids, while a write sends a whole `AccountBasePayload`.
104
+ */
91
105
  export interface InvoicingQboAccountMappingsPayload {
106
+ default_income_account?: Partial<AccountBasePayload> | null;
92
107
  default_income_account_id?: string | null;
108
+ deposit_account?: Partial<AccountBasePayload> | null;
93
109
  deposit_account_id?: string | null;
94
110
  deposit_account_overrides?: Record<string, string> | null;
111
+ discounts_account?: Partial<AccountBasePayload> | null;
95
112
  discounts_account_id?: string | null;
113
+ rounding_account?: Partial<AccountBasePayload> | null;
96
114
  rounding_account_id?: string | null;
115
+ write_off_account?: Partial<AccountBasePayload> | null;
97
116
  write_off_account_id?: string | null;
98
117
  }
118
+ /**
119
+ * The id for one mapping slot, from whichever shape the backend sent. The object
120
+ * form wins when present; a blank id in either is treated as unset so the picker
121
+ * shows its placeholder rather than selecting nothing.
122
+ */
123
+ /**
124
+ * The first id a picker can actually select. Callers list the QuickBooks id
125
+ * first: the options are keyed on it, so an internal id selects nothing even
126
+ * when it names the same record. Blank counts as absent, so a cleared field
127
+ * falls through to the next candidate rather than selecting an empty option.
128
+ */
129
+ export declare const firstSelectableInvoicingId: (candidates: readonly (string | null | undefined)[]) => string | undefined;
99
130
  /**
100
131
  * Read one account-mapping dropdown value by its config `code` (the codes in
101
132
  * `qbo_account_mapping_kind_options` match the `account_mappings` field names).
102
133
  */
103
134
  export declare function getInvoicingQboAccountMappingId(accountMappings: InvoicingQboAccountMappingsPayload | undefined, code: string): string | undefined;
104
135
  /**
105
- * Return a copy of the account mappings with the dropdown slot named by
106
- * `code` set to `accountId`. Unknown codes leave the mappings unchanged.
136
+ * Expand each mapping from the id the picker submitted to the whole account, the
137
+ * shape the service stores. Written at the request boundary rather than in the
138
+ * page's draft so the UI keeps working in ids, and an id that no longer resolves
139
+ * is left out — a mapping the service cannot expand is worse than none.
107
140
  */
141
+ export declare const withInvoicingQboMappedAccounts: (accountMappings: InvoicingQboAccountMappingsPayload | undefined, accountByQboId: (qboId: string) => AccountBase | undefined) => InvoicingQboAccountMappingsPayload | undefined;
108
142
  export declare function withInvoicingQboAccountMappingId(accountMappings: InvoicingQboAccountMappingsPayload | undefined, code: string, accountId: string): InvoicingQboAccountMappingsPayload;
109
143
  export interface InvoicingQboSettingsPayload {
110
144
  account_mappings?: InvoicingQboAccountMappingsPayload | null;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ALL_INVOICING_FIELD_HELP_GROUPS = exports.INVOICING_QBO_CUSTOMER_MATCHING_MODES = exports.INVOICING_DISCOUNT_LIFECYCLE_ACTIONS = exports.INVOICING_CATALOG_PRODUCT_LIFECYCLE_ACTIONS = exports.INVOICING_CATALOG_ITEM_LIFECYCLE_ACTIONS = exports.INVOICING_SUBSCRIPTION_LIFECYCLE_ACTIONS = exports.INVOICING_DUNNING_ACTIONS = void 0;
3
+ exports.ALL_INVOICING_FIELD_HELP_GROUPS = exports.withInvoicingQboMappedAccounts = exports.firstSelectableInvoicingId = exports.INVOICING_QBO_CUSTOMER_MATCHING_MODES = exports.INVOICING_DISCOUNT_LIFECYCLE_ACTIONS = exports.INVOICING_CATALOG_PRODUCT_LIFECYCLE_ACTIONS = exports.INVOICING_CATALOG_ITEM_LIFECYCLE_ACTIONS = exports.INVOICING_SUBSCRIPTION_LIFECYCLE_ACTIONS = exports.INVOICING_DUNNING_ACTIONS = void 0;
4
4
  exports.toInvoicingDunningAction = toInvoicingDunningAction;
5
5
  exports.toInvoicingSubscriptionLifecycleAction = toInvoicingSubscriptionLifecycleAction;
6
6
  exports.toInvoicingSubscriptionLifecycleActionOption = toInvoicingSubscriptionLifecycleActionOption;
@@ -23,6 +23,7 @@ exports.toInvoicingSubscriptionEditFieldOption = toInvoicingSubscriptionEditFiel
23
23
  exports.toAuditLogEntityRouteOption = toAuditLogEntityRouteOption;
24
24
  exports.buildInvoicingEntityPath = buildInvoicingEntityPath;
25
25
  exports.isInvoicingEntityLinkable = isInvoicingEntityLinkable;
26
+ const accountPayload_1 = require("../account/accountPayload");
26
27
  exports.INVOICING_DUNNING_ACTIONS = [
27
28
  'email_only',
28
29
  'email_and_retry',
@@ -153,6 +154,28 @@ function toInvoicingQboCustomerMatchingMode(value) {
153
154
  throw new Error(`Unknown InvoicingQboCustomerMatchingMode: ${value}`);
154
155
  }
155
156
  }
157
+ /**
158
+ * The id for one mapping slot, from whichever shape the backend sent. The object
159
+ * form wins when present; a blank id in either is treated as unset so the picker
160
+ * shows its placeholder rather than selecting nothing.
161
+ */
162
+ /**
163
+ * The first id a picker can actually select. Callers list the QuickBooks id
164
+ * first: the options are keyed on it, so an internal id selects nothing even
165
+ * when it names the same record. Blank counts as absent, so a cleared field
166
+ * falls through to the next candidate rather than selecting an empty option.
167
+ */
168
+ const firstSelectableInvoicingId = (candidates) => candidates.find((candidate) => candidate != null && candidate !== '');
169
+ exports.firstSelectableInvoicingId = firstSelectableInvoicingId;
170
+ function readInvoicingQboMappedAccountId(mappedAccount, legacyAccountId) {
171
+ // Both shapes are read: the mapping arrives either way depending on how it
172
+ // was saved.
173
+ return (0, exports.firstSelectableInvoicingId)([
174
+ mappedAccount?.qbo_id,
175
+ mappedAccount?.account_id,
176
+ legacyAccountId,
177
+ ]);
178
+ }
156
179
  /**
157
180
  * Read one account-mapping dropdown value by its config `code` (the codes in
158
181
  * `qbo_account_mapping_kind_options` match the `account_mappings` field names).
@@ -160,15 +183,15 @@ function toInvoicingQboCustomerMatchingMode(value) {
160
183
  function getInvoicingQboAccountMappingId(accountMappings, code) {
161
184
  switch (code) {
162
185
  case 'deposit_account_id':
163
- return accountMappings?.deposit_account_id ?? undefined;
186
+ return readInvoicingQboMappedAccountId(accountMappings?.deposit_account, accountMappings?.deposit_account_id);
164
187
  case 'default_income_account_id':
165
- return accountMappings?.default_income_account_id ?? undefined;
188
+ return readInvoicingQboMappedAccountId(accountMappings?.default_income_account, accountMappings?.default_income_account_id);
166
189
  case 'discounts_account_id':
167
- return accountMappings?.discounts_account_id ?? undefined;
190
+ return readInvoicingQboMappedAccountId(accountMappings?.discounts_account, accountMappings?.discounts_account_id);
168
191
  case 'rounding_account_id':
169
- return accountMappings?.rounding_account_id ?? undefined;
192
+ return readInvoicingQboMappedAccountId(accountMappings?.rounding_account, accountMappings?.rounding_account_id);
170
193
  case 'write_off_account_id':
171
- return accountMappings?.write_off_account_id ?? undefined;
194
+ return readInvoicingQboMappedAccountId(accountMappings?.write_off_account, accountMappings?.write_off_account_id);
172
195
  default:
173
196
  return undefined;
174
197
  }
@@ -177,19 +200,74 @@ function getInvoicingQboAccountMappingId(accountMappings, code) {
177
200
  * Return a copy of the account mappings with the dropdown slot named by
178
201
  * `code` set to `accountId`. Unknown codes leave the mappings unchanged.
179
202
  */
203
+ /** The object key holding the mapped account for each `*_account_id` code. */
204
+ const INVOICING_QBO_MAPPED_ACCOUNT_KEY = {
205
+ default_income_account_id: 'default_income_account',
206
+ deposit_account_id: 'deposit_account',
207
+ discounts_account_id: 'discounts_account',
208
+ rounding_account_id: 'rounding_account',
209
+ write_off_account_id: 'write_off_account',
210
+ };
211
+ /**
212
+ * Expand each mapping from the id the picker submitted to the whole account, the
213
+ * shape the service stores. Written at the request boundary rather than in the
214
+ * page's draft so the UI keeps working in ids, and an id that no longer resolves
215
+ * is left out — a mapping the service cannot expand is worse than none.
216
+ */
217
+ const withInvoicingQboMappedAccounts = (accountMappings, accountByQboId) => {
218
+ if (accountMappings == null) {
219
+ return undefined;
220
+ }
221
+ const expanded = { ...accountMappings };
222
+ Object.entries(INVOICING_QBO_MAPPED_ACCOUNT_KEY).forEach(([code, objectKey]) => {
223
+ const accountId = getInvoicingQboAccountMappingId(accountMappings, code);
224
+ if (accountId == null) {
225
+ return;
226
+ }
227
+ const account = accountByQboId(accountId);
228
+ expanded[objectKey] =
229
+ account != null ? (0, accountPayload_1.toAccountPayload)(account) : undefined;
230
+ expanded[code] = undefined;
231
+ });
232
+ return expanded;
233
+ };
234
+ exports.withInvoicingQboMappedAccounts = withInvoicingQboMappedAccounts;
180
235
  function withInvoicingQboAccountMappingId(accountMappings, code, accountId) {
236
+ // Both slots are written together. Setting only the bare id would leave the
237
+ // object shape behind, and the reader prefers it — so the selection just made
238
+ // would read back as whatever was mapped before.
181
239
  const base = accountMappings ?? {};
182
240
  switch (code) {
183
241
  case 'deposit_account_id':
184
- return { ...base, deposit_account_id: accountId };
242
+ return {
243
+ ...base,
244
+ deposit_account: undefined,
245
+ deposit_account_id: accountId,
246
+ };
185
247
  case 'default_income_account_id':
186
- return { ...base, default_income_account_id: accountId };
248
+ return {
249
+ ...base,
250
+ default_income_account: undefined,
251
+ default_income_account_id: accountId,
252
+ };
187
253
  case 'discounts_account_id':
188
- return { ...base, discounts_account_id: accountId };
254
+ return {
255
+ ...base,
256
+ discounts_account: undefined,
257
+ discounts_account_id: accountId,
258
+ };
189
259
  case 'rounding_account_id':
190
- return { ...base, rounding_account_id: accountId };
260
+ return {
261
+ ...base,
262
+ rounding_account: undefined,
263
+ rounding_account_id: accountId,
264
+ };
191
265
  case 'write_off_account_id':
192
- return { ...base, write_off_account_id: accountId };
266
+ return {
267
+ ...base,
268
+ write_off_account: undefined,
269
+ write_off_account_id: accountId,
270
+ };
193
271
  default:
194
272
  return base;
195
273
  }
@@ -1,3 +1,5 @@
1
+ import { AccountBasePayload } from '../../account/accountPayload';
2
+ import { ClassBasePayload } from '../../class/classPayload';
1
3
  import { InvoicingPlanTier, InvoicingProduct } from './productState';
2
4
  export interface InvoicingPlanTierPayload {
3
5
  price: number;
@@ -7,10 +9,18 @@ export interface InvoicingPlanTierPayload {
7
9
  pricing_type?: string;
8
10
  }
9
11
  export interface InvoicingAccountingDetailPayload {
12
+ /**
13
+ * `account` and `accounting_class` hold whole records — what the service stores
14
+ * and what the catalog payload builder sends. The bare `accounting_code` /
15
+ * `accounting_category_1` are the older shape, still read so a plan saved
16
+ * before the rollover prefills.
17
+ */
18
+ account?: Partial<AccountBasePayload> | null;
10
19
  accounting_category_1?: string;
11
20
  accounting_category_2?: string;
12
21
  accounting_category_3?: string;
13
22
  accounting_category_4?: string;
23
+ accounting_class?: Partial<ClassBasePayload> | null;
14
24
  accounting_code?: string;
15
25
  sku?: string;
16
26
  }
@@ -4,6 +4,7 @@ exports.mapInvoicingProductPayloadToState = exports.mapInvoicingPlanTierPayloadT
4
4
  const amount_1 = require("../../../commonStateTypes/amount");
5
5
  const currencyHelper_1 = require("../../../commonStateTypes/currencyHelper");
6
6
  const zeniDayJS_1 = require("../../../zeniDayJS");
7
+ const invoicingCommonPayload_1 = require("../invoicingCommonPayload");
7
8
  // ── Payload → State mappers (snake_case wire shape → camelCase domain shape) ──
8
9
  const mapInvoicingPlanTierPayloadToState = (payload, currencyCode, currencySymbol) => ({
9
10
  price: (0, amount_1.toAmount)(payload.price, currencyCode, currencySymbol),
@@ -14,11 +15,22 @@ const mapInvoicingPlanTierPayloadToState = (payload, currencyCode, currencySymbo
14
15
  });
15
16
  exports.mapInvoicingPlanTierPayloadToState = mapInvoicingPlanTierPayloadToState;
16
17
  const mapInvoicingAccountingDetailPayloadToState = (payload) => ({
17
- accountingCategory1: payload.accounting_category_1,
18
+ // The QuickBooks id first in both, for the reason `firstSelectableInvoicingId`
19
+ // documents: the form's pickers are keyed on it, so reading the internal id
20
+ // would leave the field blank over a mapping that exists.
21
+ accountingCategory1: (0, invoicingCommonPayload_1.firstSelectableInvoicingId)([
22
+ payload.accounting_class?.qbo_id,
23
+ payload.accounting_class?.accounting_class_id,
24
+ payload.accounting_category_1,
25
+ ]),
18
26
  accountingCategory2: payload.accounting_category_2,
19
27
  accountingCategory3: payload.accounting_category_3,
20
28
  accountingCategory4: payload.accounting_category_4,
21
- accountingCode: payload.accounting_code,
29
+ accountingCode: (0, invoicingCommonPayload_1.firstSelectableInvoicingId)([
30
+ payload.account?.qbo_id,
31
+ payload.account?.account_id,
32
+ payload.accounting_code,
33
+ ]),
22
34
  sku: payload.sku,
23
35
  });
24
36
  const mapInvoicingProductPayloadToState = (payload) => {
@@ -1,3 +1,4 @@
1
+ import { toAccountPayload } from '../account/accountPayload';
1
2
  export const INVOICING_DUNNING_ACTIONS = [
2
3
  'email_only',
3
4
  'email_and_retry',
@@ -128,6 +129,27 @@ export function toInvoicingQboCustomerMatchingMode(value) {
128
129
  throw new Error(`Unknown InvoicingQboCustomerMatchingMode: ${value}`);
129
130
  }
130
131
  }
132
+ /**
133
+ * The id for one mapping slot, from whichever shape the backend sent. The object
134
+ * form wins when present; a blank id in either is treated as unset so the picker
135
+ * shows its placeholder rather than selecting nothing.
136
+ */
137
+ /**
138
+ * The first id a picker can actually select. Callers list the QuickBooks id
139
+ * first: the options are keyed on it, so an internal id selects nothing even
140
+ * when it names the same record. Blank counts as absent, so a cleared field
141
+ * falls through to the next candidate rather than selecting an empty option.
142
+ */
143
+ export const firstSelectableInvoicingId = (candidates) => candidates.find((candidate) => candidate != null && candidate !== '');
144
+ function readInvoicingQboMappedAccountId(mappedAccount, legacyAccountId) {
145
+ // Both shapes are read: the mapping arrives either way depending on how it
146
+ // was saved.
147
+ return firstSelectableInvoicingId([
148
+ mappedAccount?.qbo_id,
149
+ mappedAccount?.account_id,
150
+ legacyAccountId,
151
+ ]);
152
+ }
131
153
  /**
132
154
  * Read one account-mapping dropdown value by its config `code` (the codes in
133
155
  * `qbo_account_mapping_kind_options` match the `account_mappings` field names).
@@ -135,15 +157,15 @@ export function toInvoicingQboCustomerMatchingMode(value) {
135
157
  export function getInvoicingQboAccountMappingId(accountMappings, code) {
136
158
  switch (code) {
137
159
  case 'deposit_account_id':
138
- return accountMappings?.deposit_account_id ?? undefined;
160
+ return readInvoicingQboMappedAccountId(accountMappings?.deposit_account, accountMappings?.deposit_account_id);
139
161
  case 'default_income_account_id':
140
- return accountMappings?.default_income_account_id ?? undefined;
162
+ return readInvoicingQboMappedAccountId(accountMappings?.default_income_account, accountMappings?.default_income_account_id);
141
163
  case 'discounts_account_id':
142
- return accountMappings?.discounts_account_id ?? undefined;
164
+ return readInvoicingQboMappedAccountId(accountMappings?.discounts_account, accountMappings?.discounts_account_id);
143
165
  case 'rounding_account_id':
144
- return accountMappings?.rounding_account_id ?? undefined;
166
+ return readInvoicingQboMappedAccountId(accountMappings?.rounding_account, accountMappings?.rounding_account_id);
145
167
  case 'write_off_account_id':
146
- return accountMappings?.write_off_account_id ?? undefined;
168
+ return readInvoicingQboMappedAccountId(accountMappings?.write_off_account, accountMappings?.write_off_account_id);
147
169
  default:
148
170
  return undefined;
149
171
  }
@@ -152,19 +174,73 @@ export function getInvoicingQboAccountMappingId(accountMappings, code) {
152
174
  * Return a copy of the account mappings with the dropdown slot named by
153
175
  * `code` set to `accountId`. Unknown codes leave the mappings unchanged.
154
176
  */
177
+ /** The object key holding the mapped account for each `*_account_id` code. */
178
+ const INVOICING_QBO_MAPPED_ACCOUNT_KEY = {
179
+ default_income_account_id: 'default_income_account',
180
+ deposit_account_id: 'deposit_account',
181
+ discounts_account_id: 'discounts_account',
182
+ rounding_account_id: 'rounding_account',
183
+ write_off_account_id: 'write_off_account',
184
+ };
185
+ /**
186
+ * Expand each mapping from the id the picker submitted to the whole account, the
187
+ * shape the service stores. Written at the request boundary rather than in the
188
+ * page's draft so the UI keeps working in ids, and an id that no longer resolves
189
+ * is left out — a mapping the service cannot expand is worse than none.
190
+ */
191
+ export const withInvoicingQboMappedAccounts = (accountMappings, accountByQboId) => {
192
+ if (accountMappings == null) {
193
+ return undefined;
194
+ }
195
+ const expanded = { ...accountMappings };
196
+ Object.entries(INVOICING_QBO_MAPPED_ACCOUNT_KEY).forEach(([code, objectKey]) => {
197
+ const accountId = getInvoicingQboAccountMappingId(accountMappings, code);
198
+ if (accountId == null) {
199
+ return;
200
+ }
201
+ const account = accountByQboId(accountId);
202
+ expanded[objectKey] =
203
+ account != null ? toAccountPayload(account) : undefined;
204
+ expanded[code] = undefined;
205
+ });
206
+ return expanded;
207
+ };
155
208
  export function withInvoicingQboAccountMappingId(accountMappings, code, accountId) {
209
+ // Both slots are written together. Setting only the bare id would leave the
210
+ // object shape behind, and the reader prefers it — so the selection just made
211
+ // would read back as whatever was mapped before.
156
212
  const base = accountMappings ?? {};
157
213
  switch (code) {
158
214
  case 'deposit_account_id':
159
- return { ...base, deposit_account_id: accountId };
215
+ return {
216
+ ...base,
217
+ deposit_account: undefined,
218
+ deposit_account_id: accountId,
219
+ };
160
220
  case 'default_income_account_id':
161
- return { ...base, default_income_account_id: accountId };
221
+ return {
222
+ ...base,
223
+ default_income_account: undefined,
224
+ default_income_account_id: accountId,
225
+ };
162
226
  case 'discounts_account_id':
163
- return { ...base, discounts_account_id: accountId };
227
+ return {
228
+ ...base,
229
+ discounts_account: undefined,
230
+ discounts_account_id: accountId,
231
+ };
164
232
  case 'rounding_account_id':
165
- return { ...base, rounding_account_id: accountId };
233
+ return {
234
+ ...base,
235
+ rounding_account: undefined,
236
+ rounding_account_id: accountId,
237
+ };
166
238
  case 'write_off_account_id':
167
- return { ...base, write_off_account_id: accountId };
239
+ return {
240
+ ...base,
241
+ write_off_account: undefined,
242
+ write_off_account_id: accountId,
243
+ };
168
244
  default:
169
245
  return base;
170
246
  }
@@ -1,6 +1,7 @@
1
1
  import { toAmount } from '../../../commonStateTypes/amount';
2
2
  import { defaultCustomerCurrency } from '../../../commonStateTypes/currencyHelper';
3
3
  import { date } from '../../../zeniDayJS';
4
+ import { firstSelectableInvoicingId } from '../invoicingCommonPayload';
4
5
  // ── Payload → State mappers (snake_case wire shape → camelCase domain shape) ──
5
6
  export const mapInvoicingPlanTierPayloadToState = (payload, currencyCode, currencySymbol) => ({
6
7
  price: toAmount(payload.price, currencyCode, currencySymbol),
@@ -10,11 +11,22 @@ export const mapInvoicingPlanTierPayloadToState = (payload, currencyCode, curren
10
11
  pricingType: payload.pricing_type,
11
12
  });
12
13
  const mapInvoicingAccountingDetailPayloadToState = (payload) => ({
13
- accountingCategory1: payload.accounting_category_1,
14
+ // The QuickBooks id first in both, for the reason `firstSelectableInvoicingId`
15
+ // documents: the form's pickers are keyed on it, so reading the internal id
16
+ // would leave the field blank over a mapping that exists.
17
+ accountingCategory1: firstSelectableInvoicingId([
18
+ payload.accounting_class?.qbo_id,
19
+ payload.accounting_class?.accounting_class_id,
20
+ payload.accounting_category_1,
21
+ ]),
14
22
  accountingCategory2: payload.accounting_category_2,
15
23
  accountingCategory3: payload.accounting_category_3,
16
24
  accountingCategory4: payload.accounting_category_4,
17
- accountingCode: payload.accounting_code,
25
+ accountingCode: firstSelectableInvoicingId([
26
+ payload.account?.qbo_id,
27
+ payload.account?.account_id,
28
+ payload.accounting_code,
29
+ ]),
18
30
  sku: payload.sku,
19
31
  });
20
32
  export const mapInvoicingProductPayloadToState = (payload) => {
package/lib/esm/index.js CHANGED
@@ -187,13 +187,14 @@ import { clearExpenseAutomationFluxAnalysisView, fetchFluxAnalysisView, reviewFl
187
187
  import { clearJeScheduleLocalData as clearExpenseAutomationJEScheduleLocalData, clearExpenseAutomationJESchedulesView, fetchJeSchedulesPage as fetchExpenseAutomationJESchedulesPage, ignoreRecommendedJeSchedule as ignoreExpenseAutomationJESchedule, initializeAccountSettingsView as initializeJeAccountSettingsView, initializeJeScheduleLocalData, removeJeScheduleTransactionKey, retryJeSchedule as retryExpenseAutomationJESchedule, saveAccountSettings as saveJeAccountSettings, saveAccountSettingsLocalData as saveJeAccountSettingsLocalData, updateJESchedulesUIState as updateExpenseAutomationJESchedulesUIState, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, } from './view/expenseAutomationView/reducers/jeSchedulesViewReducer';
188
188
  import { acknowledgeBulkUploadConfirmMatchComplete, bulkUploadAutomatchingTimedOut, bulkUploadReceipts, bulkUploadReceiptsFailure, bulkUploadReceiptsSuccess, clearBulkUpload, clearBulkUploadBatchDetailsForScopeChange, clearManualSearchResults, clearMissingReceiptsTabNavigation, confirmBulkUploadMatch, confirmBulkUploadMatchFailure, confirmBulkUploadMatchSuccess, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsFailure, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatches, fetchBulkUploadBatchesFailure, fetchBulkUploadBatchesSuccess, fetchCompletedTransactions, fetchCompletedTransactionsFailure, fetchCompletedTransactionsSuccess, fetchMissingReceipts as fetchExpenseAutomationMissingReceipts, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, markBatchDetailRefreshAttempted, markMissingReceiptAsDone as markExpenseAutomationMissingReceiptAsDone, pusherBatchStatusUpdate, refreshBatchDetailsForBatchId, requestMissingReceiptsTabNavigation, restoreBulkUploadAutomatchingOnMount, restoreBulkUploadMatchingState, searchTransactionsForManualMatch, searchTransactionsForManualMatchFailure, searchTransactionsForManualMatchSuccess, setBulkUploadCompletedSubTab, setBulkUploadResultsTab, setBulkUploadSortConfig, setBulkUploadUploadedFileCount, storeBatchDetails, updateBulkUploadProgress, updateMissingReceiptUploadState as updateExpenseAutomationMissingReceiptUploadState, updateMissingReceiptsUIState as updateExpenseAutomationMissingReceiptsUIState, uploadMissingReceiptSuccess as uploadExpenseAutomationMissingReceiptSuccess, } from './view/expenseAutomationView/reducers/missingReceiptsViewReducer';
189
189
  import { clearStatementDateConflict, confirmStatementMapping, deleteAccountStatement, excludeAccountFromReconciliation, fetchReconciliation as fetchReconciliationView, includeAccountInReconciliation, parseCombinedStatement, parseStatement, parseStatementFailure, parseStatementSuccess, reparseStatement, reparseStatementFailure, reparseStatementSuccess, resetReparseStatementStatus, saveReconciliationDetail as saveExpenseAutomationReconciliationDetail, saveReconciliationReview as saveExpenseAutomationReconciliationReview, setConnectionInProgressForAccount as setConnectionInProgressForAccountReconciliation, setStatementParseInProgress, setStatementProcessingDismissedToBackground, submitCombinedStatementUpdate, submitStatementUpdate, updateAccountReconciliationLocalData as updateExpenseAutomationAccountReconciliationLocalData, updateSelectedAccountId as updateExpenseAutomationAccountReconciliationSelectedAccountId, updateSelectedTab as updateExpenseAutomationAccountReconciliationSelectedTab, updateReconListScrollPosition as updateExpenseAutomationReconListScrollPosition, updateReviewTabSortState as updateExpenseAutomationReconReviewTabListSortState, updateReviewTabLocalData as updateExpenseAutomationReconReviewTabLocalData, updateReconcileTabListScrollState as updateExpenseAutomationReconcileTabListScrollState, updateReconcileTabListSortState as updateExpenseAutomationReconcileTabListSortState, updateReconcileTabLocalData as updateExpenseAutomationReconcileTabLocalData, updateSelectedDrawerAccountId as updateExpenseAutomationSelectedDrawerAccountId, updateNodeCollapseState, updateParsedStatementData, updateStatementProcessingBackgroundCompletedSteps, updateStatementProcessingFailed, updateStatementUpdateLocalData, updateStatementUploadChosen, uploadAccountStatement, } from './view/expenseAutomationView/reducers/reconciliationViewReducer';
190
- import { backgroundRefetchReviewTab, clearExpenseAutomationTransactionsView, fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationView, initializeTransactionCategorizationViewLocalData, markCategoryClassRecommendationsFailureForCategorization, markTransactionAsNotMiscategorized, removeTransactionFromAllTabs, saveTransactionCategorization, saveTransactionCategorizationLocalData, setAllItemsToCategoryClassInLocalDataForCategorization, setEntityRecommendationForLineIdsForCategorization, syncTransactionCategorizationFromDetailSave, updateCurrentSelectedTransactionCategorizationTab, updateSelectedCheckboxTransactionIds, updateSelectedCustomerForTransaction, updateSelectedTransactionId, updateSelectedVendorForTransaction, updateTransactionCategorization, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, updateTransactionCategorizationUIState, updateTransactionCategorizationUploadReceiptState, updateTransactionFilters, uploadTransactionCategorizationReceiptSuccess, } from './view/expenseAutomationView/reducers/transactionsViewReducer';
190
+ import { backgroundRefetchReviewTab, clearExpenseAutomationTransactionsView, fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationView, initializeTransactionCategorizationViewLocalData, markCategoryClassRecommendationsFailureForCategorization, markTransactionAsNotMiscategorized, removeTransactionFromAllTabs, saveTransactionCategorization, saveTransactionCategorizationLocalData, setAllItemsToCategoryClassInLocalDataForCategorization, setEntityRecommendationForLineIdsForCategorization, syncTransactionCategorizationFromDetailSave, updateCurrentSelectedTransactionCategorizationTab, updateSelectedCheckboxTransactionIds, updateSelectedCustomerForTransaction, updateSelectedTransactionId, updateSelectedVendorForTransaction, updateTransactionCategorization, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, updateTransactionCategorizationUIState, updateTransactionCategorizationUploadReceiptState, updateTransactionFilters, updateTransactionGroup, uploadTransactionCategorizationReceiptSuccess, } from './view/expenseAutomationView/reducers/transactionsViewReducer';
191
191
  import { getExpenseAutomationFluxAnalysisView } from './view/expenseAutomationView/selectors/fluxAnalysisViewSelector';
192
192
  import { getExpenseAutomationReconciliationView, getLedgerAccountsForCombinedStatementMapping, getReparseStatementStatusByAccountId, getUnavailableLedgerAccountIds, isAccountReconReport, } from './view/expenseAutomationView/selectors/reconciliationViewSelector';
193
193
  import { getExpenseAutomationTransactionView, getLastTransferEntryReplacement, } from './view/expenseAutomationView/selectors/transactionCategorizationSelector';
194
194
  import { applyTransactionFilters } from './view/expenseAutomationView/transactionFilterHelpers';
195
195
  import { TRANSACTION_FILTER_CATEGORIES, } from './view/expenseAutomationView/transactionFilterTypes';
196
196
  import { DEFAULT_COMPLETED_SUB_TAB, toCompletedSubTab, } from './view/expenseAutomationView/types/completedSubTab';
197
+ import { DEFAULT_TRANSACTION_GROUP, TRANSACTION_GROUPS, toTransactionGroup, } from './view/expenseAutomationView/types/transactionGroup';
197
198
  import { toJEScheduleMainTab as toExpenseAutomationJEScheduleMainTab, toJEScheduleSortKey as toExpenseAutomationJEScheduleSortKey, } from './view/expenseAutomationView/types/jeSchedulesViewState';
198
199
  import { BATCH_FILE_STATUSES, isUnmatchedTabFileStatus, toBatchFileStatus, toBatchStatusValue, toBulkUploadPhase, toBulkUploadResultsTab, toBulkUploadSortKey, toMatchSource, toMissingReceiptsTab, } from './view/expenseAutomationView/types/missingReceiptsViewState';
199
200
  import { toMissingReceiptsSortKey as toExpenseAutomationMissingReceiptsSortKey, } from './view/expenseAutomationView/types/missingReceiptsViewState';
@@ -482,7 +483,7 @@ export { stringToUnion, stringToUnionStrict };
482
483
  export { fetchMonthEndCloseChecks, fetchMonthClosePerformanceTrend, getMonthEndCloseChecksViewByTenantId, ALL_MONTH_END_CLOSE_CHECKS_FREQUENCY, };
483
484
  export {
484
485
  // Bulk Upload Types
485
- BATCH_FILE_STATUSES, isUnmatchedTabFileStatus, DEFAULT_COMPLETED_SUB_TAB, toBatchFileStatus, toBatchStatusValue, toBulkUploadPhase, toBulkUploadResultsTab, toBulkUploadSortKey, toCompletedSubTab, toMatchSource, toMissingReceiptsTab, toExpenseAutomationJEScheduleMainTab, toExpenseAutomationJEScheduleSortKey, getExpenseAutomationView, toExpenseAutomationMissingReceiptsSortKey, toExpenseAutomationTransactionsTabKey, toExpenseAutomationViewType, uploadExpenseAutomationMissingReceiptSuccess, markExpenseAutomationMissingReceiptAsDone, fetchAllExpenseAutomationTabs, refreshExpenseAutomationCurrentTab, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, fetchExpenseAutomationMissingReceipts,
486
+ BATCH_FILE_STATUSES, isUnmatchedTabFileStatus, DEFAULT_COMPLETED_SUB_TAB, DEFAULT_TRANSACTION_GROUP, TRANSACTION_GROUPS, toTransactionGroup, toBatchFileStatus, toBatchStatusValue, toBulkUploadPhase, toBulkUploadResultsTab, toBulkUploadSortKey, toCompletedSubTab, toMatchSource, toMissingReceiptsTab, toExpenseAutomationJEScheduleMainTab, toExpenseAutomationJEScheduleSortKey, getExpenseAutomationView, toExpenseAutomationMissingReceiptsSortKey, toExpenseAutomationTransactionsTabKey, toExpenseAutomationViewType, uploadExpenseAutomationMissingReceiptSuccess, markExpenseAutomationMissingReceiptAsDone, fetchAllExpenseAutomationTabs, refreshExpenseAutomationCurrentTab, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, updateTransactionGroup, fetchExpenseAutomationMissingReceipts,
486
487
  // Bulk Upload Actions
487
488
  bulkUploadReceipts, bulkUploadAutomatchingTimedOut, bulkUploadReceiptsSuccess, bulkUploadReceiptsFailure, restoreBulkUploadAutomatchingOnMount, restoreBulkUploadMatchingState, updateBulkUploadProgress, pusherBatchStatusUpdate, requestMissingReceiptsTabNavigation, clearMissingReceiptsTabNavigation, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatchDetailsFailure, storeBatchDetails, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, fetchBulkUploadBatches, fetchBulkUploadBatchesSuccess, fetchBulkUploadBatchesFailure, clearBulkUploadBatchDetailsForScopeChange, markBatchDetailRefreshAttempted, refreshBatchDetailsForBatchId, confirmBulkUploadMatch, confirmBulkUploadMatchSuccess, confirmBulkUploadMatchFailure, setBulkUploadResultsTab, setBulkUploadCompletedSubTab, setBulkUploadSortConfig, setBulkUploadUploadedFileCount, clearBulkUpload, searchTransactionsForManualMatch, searchTransactionsForManualMatchSuccess, searchTransactionsForManualMatchFailure, clearManualSearchResults, acknowledgeBulkUploadConfirmMatchComplete, fetchCompletedTransactions, fetchCompletedTransactionsSuccess, fetchCompletedTransactionsFailure, fetchFluxAnalysisView, clearExpenseAutomationFluxAnalysisView, updateOperatingExpensesIdsForReview as updateFluxOperatingExpensesIdsForReview, updateSelectedSectionIdsForReview as updateFluxAnalysisSelectedSectionIdsForReview, reviewFluxAnalysisView, updateExpenseAutomationMissingReceiptUploadState, updateExpenseAutomationMissingReceiptsUIState, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, getExpenseAutomationFluxAnalysisView, updateCurrentSelectedView, updateCurrentSelectedPeriod, getExpenseAutomationTransactionView, getLastTransferEntryReplacement, updateFluxAnalysisViewUIState, updateFluxAnalysisViewPageMetaData, MAX_SELECTION_LIMIT, checkIfAllLineItemsAreCategoryClassFilled, getLineItemsByTransactionIdsFromLocalData, isAnyItemWithUncategorizedExpenseAccount, excludeAccountFromReconciliation, includeAccountInReconciliation, saveExpenseAutomationReconciliationDetail, updateExpenseAutomationReconcileTabListScrollState, updateExpenseAutomationReconReviewTabListSortState, updateExpenseAutomationReconcileTabListSortState, updateExpenseAutomationReconcileTabLocalData, updateExpenseAutomationAccountReconciliationSelectedTab, updateExpenseAutomationAccountReconciliationSelectedAccountId, getExpenseAutomationReconciliationView, getLedgerAccountsForCombinedStatementMapping, getReparseStatementStatusByAccountId, getUnavailableLedgerAccountIds, fetchReconciliationView, uploadAccountStatementIntoDocumentAI, updateExpenseAutomationReconListScrollPosition, setConnectionInProgressForAccountReconciliation, getAccountReconByAccountIdAndSelectedPeriod, toReconciliationTabsType, isAccountReconReport, updateExpenseAutomationReconReviewTabLocalData, updateExpenseAutomationSelectedDrawerAccountId, updateStatementProcessingFailed, saveExpenseAutomationReconciliationReview, updateExpenseAutomationAccountReconciliationLocalData, toReconciliationAccountSource, deleteAccountStatement, uploadAccountStatement, parseCombinedStatement, parseStatement, parseStatementSuccess, parseStatementFailure, reparseStatement, reparseStatementSuccess, reparseStatementFailure, resetReparseStatementStatus, updateParsedStatementData, updateNodeCollapseState, updateStatementUploadChosen, submitCombinedStatementUpdate, submitStatementUpdate, clearStatementDateConflict, confirmStatementMapping, updateStatementUpdateLocalData, isReviewTransactionBankTransferType, isReviewTransactionBillPaymentType, isReviewTransactionCreditCardPaymentType, isReviewTransactionDepositType, isReviewTransactionExpenseType, isReviewTransactionCreditCardCreditType, setStatementParseInProgress, setStatementProcessingDismissedToBackground, updateStatementProcessingBackgroundCompletedSteps, };
488
489
  export { fetchTransactionCategorization, fetchTransactionCategorizationView, updateTransactionCategorizationUIState, updateTransactionFilters, updateSelectedCheckboxTransactionIds, markCategoryClassRecommendationsFailureForCategorization, setEntityRecommendationForLineIdsForCategorization, initializeTransactionCategorizationViewLocalData, setAllItemsToCategoryClassInLocalDataForCategorization, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, createTransferEntry, createTransferEntryFailure, createTransferEntryReplacedTransaction, createTransferEntrySuccess, resetCreateTransferEntryStatus, clearTransferEntryRouteReplacement, fetchAccountsForTransferFlow, removeTransactionFromAllTabs, clearExpenseAutomationTransactionsView, toTransactionsSortKey, };
@@ -28,7 +28,6 @@ export const initialState = {
28
28
  reimbursementAccountList: initialAccountList,
29
29
  schedulesAccountList: initialAccountList,
30
30
  billPayAccountList: initialAccountList,
31
- invoicingAccountList: initialAccountList,
32
31
  },
33
32
  };
34
33
  const accountList = createSlice({
@@ -27,6 +27,7 @@ export const backgroundRefetchReviewTabEpic = (actions$, state$, zeniAPI) => act
27
27
  page_token: pageToken,
28
28
  page_size: PAGE_SIZE,
29
29
  search_text: reviewUiState.searchString,
30
+ transaction_group: state.expenseAutomationTransactionsViewState.selectedTransactionGroup,
30
31
  };
31
32
  return zeniAPI.getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/transactions?query=${encodeURIComponent(JSON.stringify(queryParam))}`);
32
33
  };
@@ -36,6 +36,7 @@ groupBy((action) => action.payload.selectedTab), mergeMap((group$) => group$.pip
36
36
  sub_tab: selectedTab === 'autoCategorized'
37
37
  ? expenseAutomationTransactionsViewState.selectedTransactionCategorizationCompletedSubTab
38
38
  : DEFAULT_COMPLETED_SUB_TAB,
39
+ transaction_group: expenseAutomationTransactionsViewState.selectedTransactionGroup,
39
40
  };
40
41
  return zeniAPI
41
42
  .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/transactions?query=${encodeURIComponent(JSON.stringify(queryParam))}`)
@@ -6,7 +6,18 @@ import { isVendorTransaction } from '../../../entity/transaction/stateTypes/vend
6
6
  import { clearAll } from '../../../rootActions';
7
7
  import { filterAutoTabLineItems, mergeTabSpecificLineItems, removeTransactionFromTabView, setEntityRecommendationForLineIdInLocalData, shouldAutoSelectAndAdd, toSetAllLineItemsToCategoryClass, toTransactionDetailLocalData, } from '../helpers/transactionCategorizationLocalDataHelper';
8
8
  import { DEFAULT_COMPLETED_SUB_TAB, } from '../types/completedSubTab';
9
+ import { DEFAULT_TRANSACTION_GROUP, } from '../types/transactionGroup';
9
10
  import { TRANSACTIONS_TABS, } from '../types/transactionsViewState';
11
+ export const initialUIState = {
12
+ sortKey: 'date',
13
+ sortOrder: 'descending',
14
+ searchString: '',
15
+ nodeCollapseState: {},
16
+ scrollPosition: { scrollTop: 0, scrollLeft: undefined },
17
+ pageToken: null,
18
+ totalCount: {},
19
+ limit: 10,
20
+ };
10
21
  export const initialTransactionTabViewState = {
11
22
  saveStatus: {
12
23
  fetchState: 'Not-Started',
@@ -20,16 +31,7 @@ export const initialTransactionTabViewState = {
20
31
  fetchState: 'Not-Started',
21
32
  error: undefined,
22
33
  },
23
- uiState: {
24
- sortKey: 'date',
25
- sortOrder: 'descending',
26
- searchString: '',
27
- nodeCollapseState: {},
28
- scrollPosition: { scrollTop: 0, scrollLeft: undefined },
29
- pageToken: null,
30
- totalCount: {},
31
- limit: 10,
32
- },
34
+ uiState: { ...initialUIState },
33
35
  filters: {
34
36
  categoryCombinationOperator: 'AND',
35
37
  categories: [],
@@ -55,6 +57,7 @@ export const initialState = {
55
57
  },
56
58
  selectedTransactionCategorizationTab: 'review',
57
59
  selectedTransactionCategorizationCompletedSubTab: DEFAULT_COMPLETED_SUB_TAB,
60
+ selectedTransactionGroup: DEFAULT_TRANSACTION_GROUP,
58
61
  transactionCategorizationView: {
59
62
  review: { ...initialTransactionTabViewState },
60
63
  autoCategorized: { ...initialTransactionTabViewState },
@@ -400,6 +403,48 @@ const expenseAutomationTransactionsView = createSlice({
400
403
  return { payload: { newSubTab } };
401
404
  },
402
405
  },
406
+ updateTransactionGroup: {
407
+ reducer(draft, action) {
408
+ const { newGroup } = action.payload;
409
+ if (draft.selectedTransactionGroup === newGroup) {
410
+ return;
411
+ }
412
+ draft.selectedTransactionGroup = newGroup;
413
+ // A group change re-scopes the entire dataset on the backend (by
414
+ // account_type), so every parent tab, sub-tab snapshot and count is now
415
+ // stale. Drop the sub-tab cache and parent counts, and reset each tab's
416
+ // paginated data slots so the view epic's cache check falls through to a
417
+ // fresh page-1 fetch. User preferences (search, sort, limit, collapse
418
+ // state) are intentionally preserved, mirroring the sub-tab switch.
419
+ draft.autoCategorizedSubTabCache = {};
420
+ draft.parentTotalCountByTab = { review: {}, autoCategorized: {} };
421
+ TRANSACTIONS_TABS.forEach((tab) => {
422
+ const view = draft.transactionCategorizationView[tab];
423
+ // Reset uiState to its initial values but keep the user prefs (search,
424
+ // sort, limit, collapse) in place -- mirroring the sub-tab switch. Then
425
+ // drop the loaded rows and any stale selection pointing at a now-absent
426
+ // row to avoid transient invalid state.
427
+ const { searchString, sortKey, sortOrder, nodeCollapseState, limit } = view.uiState;
428
+ view.uiState = {
429
+ ...initialUIState,
430
+ searchString,
431
+ sortKey,
432
+ sortOrder,
433
+ nodeCollapseState,
434
+ limit,
435
+ };
436
+ view.transactionIdsBySelectedPeriod = {};
437
+ view.fetchState = 'Not-Started';
438
+ view.error = undefined;
439
+ view.selectedTransactionId = undefined;
440
+ view.selectedTransactionLineId = undefined;
441
+ view.selectedCheckBoxTransactionIds = [];
442
+ });
443
+ },
444
+ prepare(newGroup) {
445
+ return { payload: { newGroup } };
446
+ },
447
+ },
403
448
  updateTransactionCategorization: {
404
449
  reducer(draft, action) {
405
450
  const selectedTab = action.payload.selectedTab;
@@ -1075,5 +1120,5 @@ const expenseAutomationTransactionsView = createSlice({
1075
1120
  });
1076
1121
  },
1077
1122
  });
1078
- export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, updateTransactionFilters, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, updateParentTotalCountForTab, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, markCategoryClassRecommendationsInProgressForCategorization, markCategoryClassRecommendationsCompletedForCategorization, markCategoryClassRecommendationsFailureForCategorization, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, removeTransactionFromAllTabs, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, } = expenseAutomationTransactionsView.actions;
1123
+ export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, updateTransactionFilters, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, updateTransactionGroup, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, updateParentTotalCountForTab, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, markCategoryClassRecommendationsInProgressForCategorization, markCategoryClassRecommendationsCompletedForCategorization, markCategoryClassRecommendationsFailureForCategorization, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, removeTransactionFromAllTabs, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, } = expenseAutomationTransactionsView.actions;
1079
1124
  export default expenseAutomationTransactionsView.reducer;
@@ -86,7 +86,7 @@ export const toTransactionView = (transaction, transactionLocalData) => {
86
86
  export { getLastTransferEntryReplacement } from '../../createTransferEntry/createTransferEntrySelector';
87
87
  export function getExpenseAutomationTransactionView(state) {
88
88
  const { accountState, classState, classListState, accountListState, createTransferEntryState, expenseAutomationTransactionsViewState, expenseAutomationViewState, projectState, projectListState, transactionState, monthEndCloseChecksState, } = state;
89
- const { selectedTransactionCategorizationCompletedSubTab, selectedTransactionCategorizationTab, } = expenseAutomationTransactionsViewState;
89
+ const { selectedTransactionCategorizationCompletedSubTab, selectedTransactionCategorizationTab, selectedTransactionGroup, } = expenseAutomationTransactionsViewState;
90
90
  const { uiState, refreshStatus, saveStatus, markAsNotMiscategorizedStatus, transactionIdsBySelectedPeriod, transactionReviewLocalDataById, selectedCheckBoxTransactionIds, transactionIdsWithUnsavedData, uploadReceiptStatusById, selectedTransactionId, selectedTransactionLineId, filters, } = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTransactionCategorizationTab];
91
91
  const uncategorizedIncomeExpense = getUncategorizedAccounts(accountState, accountListState, 'accountList');
92
92
  const accountList = getAccountList(accountState, accountListState, 'accountList');
@@ -240,5 +240,6 @@ export function getExpenseAutomationTransactionView(state) {
240
240
  creditCardTransferAccountsList,
241
241
  transferAccounts: transferAccountsList,
242
242
  selectedTransactionCategorizationCompletedSubTab,
243
+ selectedTransactionGroup,
243
244
  };
244
245
  }