@zeniai/client-epic-state 4.19.77-betaAK8 → 4.19.77-betaAS2

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 (44) hide show
  1. package/lib/entity/company/companyPayload.d.ts +1 -0
  2. package/lib/entity/company/companyPayload.js +1 -0
  3. package/lib/entity/company/companyStateTypes.d.ts +1 -0
  4. package/lib/esm/entity/company/companyPayload.js +1 -0
  5. package/lib/esm/index.js +2 -0
  6. package/lib/esm/view/companyView/epic/companyPassport/updateCompanyDetailsEpic.js +3 -0
  7. package/lib/esm/view/companyView/types/companyPassport/companyPassportLocalData.js +1 -0
  8. package/lib/esm/view/spendManagement/commonSetup/epic/setup/updateBusinessVerificationDetailsEpic.js +4 -1
  9. package/lib/esm/view/spendManagement/commonSetup/epic/setup/updateSetupViewLocalStoreDataEpic.js +2 -1
  10. package/lib/esm/view/spendManagement/commonSetup/setupViewSelector.js +2 -1
  11. package/lib/esm/view/spendManagement/commonSetup/types/businessVerification.js +141 -1
  12. package/lib/esm/view/transactionDetail/epics/initializeTransactionDetailLocalDataEpic.js +3 -1
  13. package/lib/esm/view/transactionDetail/epics/markTransactionAsNotMiscategorizedEpic.js +6 -6
  14. package/lib/esm/view/transactionDetail/epics/saveTransactionDetailEpic.js +2 -4
  15. package/lib/esm/view/transactionDetail/epics/transactionDetailEpic.js +8 -34
  16. package/lib/esm/view/transactionDetail/epics/updateTransactionDetailEpic.js +7 -12
  17. package/lib/esm/view/transactionDetail/epics/uploadMissingAttachmentSuccessEpic.js +3 -2
  18. package/lib/esm/view/transactionDetail/transactionDetailSelector.js +3 -5
  19. package/lib/esm/view/transactionDetail/transactionDetailState.js +2 -14
  20. package/lib/index.d.ts +3 -0
  21. package/lib/index.js +27 -20
  22. package/lib/view/companyView/epic/companyPassport/updateCompanyDetailsEpic.js +3 -0
  23. package/lib/view/companyView/types/companyPassport/companyDetailsLocalData.d.ts +1 -0
  24. package/lib/view/companyView/types/companyPassport/companyPassportLocalData.js +1 -0
  25. package/lib/view/spendManagement/commonSetup/epic/setup/updateBusinessVerificationDetailsEpic.js +4 -1
  26. package/lib/view/spendManagement/commonSetup/epic/setup/updateSetupViewLocalStoreDataEpic.js +2 -1
  27. package/lib/view/spendManagement/commonSetup/setupViewSelector.d.ts +1 -0
  28. package/lib/view/spendManagement/commonSetup/setupViewSelector.js +2 -1
  29. package/lib/view/spendManagement/commonSetup/setupViewState.d.ts +1 -0
  30. package/lib/view/spendManagement/commonSetup/types/businessVerification.d.ts +30 -3
  31. package/lib/view/spendManagement/commonSetup/types/businessVerification.js +143 -0
  32. package/lib/view/transactionDetail/epics/initializeTransactionDetailLocalDataEpic.js +3 -1
  33. package/lib/view/transactionDetail/epics/markTransactionAsNotMiscategorizedEpic.d.ts +2 -2
  34. package/lib/view/transactionDetail/epics/markTransactionAsNotMiscategorizedEpic.js +5 -5
  35. package/lib/view/transactionDetail/epics/saveTransactionDetailEpic.js +2 -4
  36. package/lib/view/transactionDetail/epics/transactionDetailEpic.js +8 -34
  37. package/lib/view/transactionDetail/epics/updateTransactionDetailEpic.d.ts +2 -2
  38. package/lib/view/transactionDetail/epics/updateTransactionDetailEpic.js +6 -11
  39. package/lib/view/transactionDetail/epics/uploadMissingAttachmentSuccessEpic.js +3 -2
  40. package/lib/view/transactionDetail/transactionDetailSelector.js +3 -5
  41. package/lib/view/transactionDetail/transactionDetailState.d.ts +1 -1
  42. package/lib/view/transactionDetail/transactionDetailState.js +2 -14
  43. package/package.json +1 -1
  44. package/lib/tsconfig.typecheck.tsbuildinfo +0 -1
@@ -48,6 +48,7 @@ export interface CompanyIncInfoPayload {
48
48
  company_inc_state_code: string | null;
49
49
  company_inc_type: string | null;
50
50
  company_industry_sub_type?: string | null;
51
+ company_industry_type?: string | null;
51
52
  file_ids?: ID[] | null;
52
53
  }
53
54
  export interface CompanyTaxInfoPayload {
@@ -76,6 +76,7 @@ const toCompanyIncInfo = (payload) => ({
76
76
  companyIncCountryCode: payload.company_inc_country_code ?? undefined,
77
77
  fileIds: payload.file_ids ?? [],
78
78
  companySubIndustry: payload.company_industry_sub_type ?? undefined,
79
+ companyIndustryType: payload.company_industry_type ?? undefined,
79
80
  });
80
81
  const toCompanyTaxInfo = (payload) => ({
81
82
  taxesFiledDate: payload.taxes_filed_date != null
@@ -61,6 +61,7 @@ export interface CompanyIncInfo {
61
61
  companyIncState?: string;
62
62
  companyIncStateCode?: string;
63
63
  companyIncType?: string;
64
+ companyIndustryType?: string;
64
65
  companySubIndustry?: string;
65
66
  }
66
67
  export interface CompanyTaxInfo {
@@ -73,6 +73,7 @@ const toCompanyIncInfo = (payload) => ({
73
73
  companyIncCountryCode: payload.company_inc_country_code ?? undefined,
74
74
  fileIds: payload.file_ids ?? [],
75
75
  companySubIndustry: payload.company_industry_sub_type ?? undefined,
76
+ companyIndustryType: payload.company_industry_type ?? undefined,
76
77
  });
77
78
  const toCompanyTaxInfo = (payload) => ({
78
79
  taxesFiledDate: payload.taxes_filed_date != null
package/lib/esm/index.js CHANGED
@@ -300,6 +300,7 @@ import { fetchDepositAccountListForCards, fetchIssueCardPage, issueChargeCards,
300
300
  import { getIssueChargeCardView, } from './view/spendManagement/chargeCards/issueChargeCard/issueChargeCardSelector';
301
301
  import { clearSetupViewDataInLocalStore, enableSetup, saveCompnayOfficerAdditionalDocumentsInLocalStore, saveCompnayOfficerPhoneInLocalStore, saveIndustryAndIncDateInLocalStore, saveSetupViewDataInLocalStore, saveTreasuryAdditionalDocumentsInLocalStore, updateBusinessVerificationDetails, updateSelectedCompanyOfficer, updateSetupViewLocalStoreData, } from './view/spendManagement/commonSetup/setupViewReducer';
302
302
  import { getBusinessVerificationDetails, getCommonSetupViewDetails, } from './view/spendManagement/commonSetup/setupViewSelector';
303
+ import { COMPANY_ONBOARDING_INDUSTRY_TYPE_CODES, COMPANY_ONBOARDING_SUB_INDUSTRY_CODES_BY_INDUSTRY, COMPANY_PURPOSE_OF_ACCOUNT_CODES, COMPANY_SOURCE_OF_FUNDS_CODES, COMPANY_TRANSACTION_VOLUME_CODES, getCompanyOnboardingSubIndustryCodesForIndustry, } from './view/spendManagement/commonSetup/types/businessVerification';
303
304
  import { checkIfLowBalance, getActualPaymentDate, getBillListUniqueType, getBulkOperationSuffix, getRemiListUniqueType, getSelectedCompanyOfficer, getSpendManagementEffectiveListPeriod, isAwaitingMarkAsPaid, isBulkProcessing, isPaymentMethodOutsideZeni, isVerifiedPaymentAccountProviderStatusCode, isVerifiedStatusCode, showBillPayPromoPage, showReimbursementPromoPage, toAccountsPromoConfig, toSameDayAchDisablementConfig, } from './view/spendManagement/helpers';
304
305
  import { establishPlaidConnection, getPaymentAccounts, getPlaidLinkToken, updateMappedCashAccount, updatePaymentAccount, updatePaymentAccountLoginStatus, updatePaymentAccountStatus, updatePrimaryFundingAccount, } from './view/spendManagement/plaidAccount/plaidAccountViewReducer';
305
306
  import { getPlaidAccountDetails } from './view/spendManagement/plaidAccount/plaidAccountViewSelector';
@@ -504,6 +505,7 @@ export { ALL_COCKPIT_TABS_FILE_TYPES, ALL_COCKPIT_TABS_IDS, toCockpitTabsIDStric
504
505
  export { isAllowedValueWithCode, isAllowedValueWithID, };
505
506
  export { fetchCompanyPassportView, getCompanyPassportView, getCompanyPassportLocalStoreData, isCompanyPassportDataToBeSaved, companyPassportSaveDataInLocalStore, saveIndustryAndIncDateInCompanyPassportLocalStore, deleteCompanyOfficerInLocalStore, companyPassportClearDataInLocalStore, updateCompanyPassportLocalStoreData, saveCompanyPassportDetails, clearCompanyView, };
506
507
  export { toCompanyPassportLocalData, };
508
+ export { COMPANY_ONBOARDING_INDUSTRY_TYPE_CODES, COMPANY_ONBOARDING_SUB_INDUSTRY_CODES_BY_INDUSTRY, COMPANY_PURPOSE_OF_ACCOUNT_CODES, COMPANY_SOURCE_OF_FUNDS_CODES, COMPANY_TRANSACTION_VOLUME_CODES, getCompanyOnboardingSubIndustryCodesForIndustry, };
507
509
  export { getUncategorizedAccounts, getNestedAccountListHierarchy, };
508
510
  export { getBankAccount };
509
511
  // TODO - remove alias and replace types across all repos
@@ -12,6 +12,9 @@ export const updateCompanyDetailsEpic = (actions$, state$, zeniAPI) => actions$.
12
12
  ...toCompanyIncInfoUpdatableInfoPayload(companyDetails.incInfo),
13
13
  ...toCompanyTaxInfoUpdatableInfoPayload(companyDetails.taxDetails),
14
14
  ...toCompanyFileInfoPayload(companyDetails.incInfo, companyDetails.taxDetails),
15
+ ...(companyDetails.companyInfo.companyIndustryType != null && {
16
+ company_industry_type: companyDetails.companyInfo.companyIndustryType,
17
+ }),
15
18
  ...(companyDetails.companyInfo.companySubIndustry != null && {
16
19
  company_industry_sub_type: companyDetails.companyInfo.companySubIndustry,
17
20
  }),
@@ -10,6 +10,7 @@ export function toCompanyPassportLocalData(companyView, companyFiles) {
10
10
  website: companyInfo.companyUrl,
11
11
  meetingLink: companyInfo.meetingUrl,
12
12
  industry: companyQuestionaire.companyIndustry,
13
+ companyIndustryType: incInfo.companyIndustryType,
13
14
  companySubIndustry: incInfo.companySubIndustry,
14
15
  businessModel: companyQuestionaire.companyBusinessModel ?? '',
15
16
  phone: companyInfo.phone ?? '',
@@ -136,7 +136,7 @@ function toUsNexusTypeKey(value) {
136
136
  return US_NEXUS_LABEL_TO_KEY[value] ?? value;
137
137
  }
138
138
  export const toBusinessVerificationCompanyDataPayload = (companyLocalData, sendForVerification, companyAddressData, registeredAddressData, setupViewType) => {
139
- const { companyDescription, companyLegalName, companyIndustry, companySubIndustry, countriesOfOperations, companySourceOfFunds, companySourceOfFundsDescription, transactionVolume, transactionVolumeDescription, purposeOfAccount, purposeOfAccountDescription, regulatedStatus, regulatedStatusDescription, usNexus, usNexusTypes, website, incDate, taxIdOrEIN, phone, syncToken, fileIds, typeOfIncorporation, stateOfIncorporation, sourceOfFunds, } = companyLocalData;
139
+ const { companyDescription, companyLegalName, companyIndustry, companyIndustryType, companySubIndustry, countriesOfOperations, companySourceOfFunds, companySourceOfFundsDescription, transactionVolume, transactionVolumeDescription, purposeOfAccount, purposeOfAccountDescription, regulatedStatus, regulatedStatusDescription, usNexus, usNexusTypes, website, incDate, taxIdOrEIN, phone, syncToken, fileIds, typeOfIncorporation, stateOfIncorporation, sourceOfFunds, } = companyLocalData;
140
140
  const company = {
141
141
  is_ready_for_verification: sendForVerification,
142
142
  file_ids: fileIds,
@@ -194,6 +194,9 @@ export const toBusinessVerificationCompanyDataPayload = (companyLocalData, sendF
194
194
  company['company_industry'] = companyIndustry;
195
195
  }
196
196
  Object.assign(company, {
197
+ ...(companyIndustryType != null && {
198
+ company_industry_type: companyIndustryType,
199
+ }),
197
200
  ...(companySubIndustry != null && {
198
201
  company_industry_sub_type: companySubIndustry,
199
202
  }),
@@ -45,13 +45,14 @@ export function toSetupViewLocalData(company, users, userRoles, primaryContact)
45
45
  const { companyInfo, incInfo, taxInfo, questionaire, companyBillPayInfo } = company;
46
46
  const { companyId, companyDescription, companyLegalName, companyUrl, phone, syncToken, sourceOfFunds, companySourceOfFunds: incCompanySourceOfFunds, companySourceOfFundsDescription, countriesOfOperations, transactionVolume, transactionVolumeDescription, purposeOfAccount, purposeOfAccountDescription, regulatedStatus, regulatedStatusDescription, usNexus, usNexusTypes, } = companyInfo;
47
47
  const { usersSelectedForVerification: usersSelected } = companyBillPayInfo;
48
- const { fileIds, companyIncDate: incDate, companyIncType, companyIncState, companySubIndustry, } = incInfo;
48
+ const { fileIds, companyIncDate: incDate, companyIncType, companyIncState, companySubIndustry, companyIndustryType, } = incInfo;
49
49
  const { companyIndustry } = questionaire;
50
50
  const { taxId } = taxInfo;
51
51
  const companyDetails = {
52
52
  companyId,
53
53
  companyDescription,
54
54
  companyIndustry,
55
+ companyIndustryType,
55
56
  companyLegalName,
56
57
  companySubIndustry,
57
58
  companySourceOfFunds: incCompanySourceOfFunds,
@@ -109,7 +109,7 @@ export const getCompanyAndIdentityDetails = (state, companyId, companyAndIdentit
109
109
  if (companyDetails.localData != null) {
110
110
  const deleteFileStatusById = {};
111
111
  const updateFileStatusById = {};
112
- const { syncToken, companyDescription, companyLegalName, website, companyIndustry, companySubIndustry, companySourceOfFunds, companySourceOfFundsDescription, countriesOfOperations, phone, taxIdOrEIN, incDate, fileIds, typeOfIncorporation, sourceOfFunds, transactionVolume, transactionVolumeDescription, purposeOfAccount, purposeOfAccountDescription, regulatedStatus, regulatedStatusDescription, stateOfIncorporation, usNexus, usNexusTypes, } = companyDetails.localData;
112
+ const { syncToken, companyDescription, companyLegalName, website, companyIndustry, companyIndustryType, companySubIndustry, companySourceOfFunds, companySourceOfFundsDescription, countriesOfOperations, phone, taxIdOrEIN, incDate, fileIds, typeOfIncorporation, sourceOfFunds, transactionVolume, transactionVolumeDescription, purposeOfAccount, purposeOfAccountDescription, regulatedStatus, regulatedStatusDescription, stateOfIncorporation, usNexus, usNexusTypes, } = companyDetails.localData;
113
113
  fileIds.forEach((fileId) => {
114
114
  deleteFileStatusById[fileId] = getFileDeleteStatusById(state, fileId);
115
115
  });
@@ -123,6 +123,7 @@ export const getCompanyAndIdentityDetails = (state, companyId, companyAndIdentit
123
123
  companyDescription,
124
124
  companyLegalName,
125
125
  companyIndustry,
126
+ companyIndustryType,
126
127
  companySubIndustry,
127
128
  companySourceOfFunds,
128
129
  companySourceOfFundsDescription,
@@ -1 +1,141 @@
1
- export {};
1
+ export const COMPANY_TRANSACTION_VOLUME_CODES = [
2
+ 'less_than_10000',
3
+ '10000_50000',
4
+ '50001_250000',
5
+ '250001_1000000',
6
+ '1000001_2000000',
7
+ 'over_2000000',
8
+ ];
9
+ export const COMPANY_SOURCE_OF_FUNDS_CODES = [
10
+ 'sales_of_goods',
11
+ 'sales_of_services',
12
+ 'customer_payments',
13
+ 'investment_capital',
14
+ 'business_loans',
15
+ 'owner_contributions',
16
+ 'franchise_revenue',
17
+ 'rental_income',
18
+ 'government_contracts_grants',
19
+ 'donations_fundraising',
20
+ 'membership_fees_subscriptions',
21
+ 'licensing_royalties',
22
+ 'commission_income',
23
+ 'import_export_revenue',
24
+ 'cryptocurrency_activity',
25
+ ];
26
+ export const COMPANY_PURPOSE_OF_ACCOUNT_CODES = [
27
+ 'retail_sales',
28
+ 'ecommerce_sales',
29
+ 'cash_heavy_operations',
30
+ 'import_export_trade',
31
+ 'professional_services_not_handling_funds',
32
+ 'professional_services_handling_funds',
33
+ 'holding_investment_company',
34
+ 'property_management_real_estate',
35
+ 'charitable_nonprofit',
36
+ 'construction_contracting',
37
+ 'commercial_cash_operations',
38
+ 'freight_forwarding_logistics',
39
+ 'third_party_payment_processing',
40
+ 'technology_startup',
41
+ 'wholesale_distribution',
42
+ 'franchise_operation',
43
+ 'healthcare_provider',
44
+ 'educational_institution',
45
+ ];
46
+ /** Industry type codes for onboarding / business verification (paired with sub-industry). */
47
+ export const COMPANY_ONBOARDING_INDUSTRY_TYPE_CODES = [
48
+ 'retail_consumer_goods',
49
+ 'financial_services',
50
+ 'food_agriculture',
51
+ 'healthcare_pharmaceuticals',
52
+ 'professional_services',
53
+ 'manufacturing_industrial',
54
+ 'real_estate_construction',
55
+ 'logistics_transportation',
56
+ 'wholesale_trade',
57
+ 'business_support_services',
58
+ 'escort_dating_adult_entertainment',
59
+ ];
60
+ export const COMPANY_ONBOARDING_SUB_INDUSTRY_CODES_BY_INDUSTRY = {
61
+ retail_consumer_goods: [
62
+ 'grocery_stores_supermarkets',
63
+ 'convenience_stores',
64
+ 'specialty_food_retailers',
65
+ 'gas_stations_retail',
66
+ 'general_merchandise_department',
67
+ 'online_retail_ecommerce',
68
+ 'subscription_membership',
69
+ 'direct_to_consumer',
70
+ 'cannabis',
71
+ ],
72
+ financial_services: [
73
+ 'banks_credit_unions',
74
+ 'fintech_payment_processing',
75
+ 'insurance_providers',
76
+ 'investment_advisors_broker',
77
+ 'lending_mortgage',
78
+ 'treasury_management',
79
+ 'personal_finance_apps',
80
+ 'retirement_planning',
81
+ 'real_estate_investment',
82
+ 'money_service_businesses',
83
+ 'cryptocurrency',
84
+ 'debt_collection',
85
+ 'payday_lending',
86
+ 'gambling',
87
+ ],
88
+ food_agriculture: [
89
+ 'farms_agricultural',
90
+ 'food_wholesalers_distributors',
91
+ 'restaurants_cafes',
92
+ 'bars_nightclubs',
93
+ 'catering_services',
94
+ 'farmers_markets',
95
+ 'restaurant_tech_pos',
96
+ ],
97
+ healthcare_pharmaceuticals: [
98
+ 'hospitals_clinics',
99
+ 'pharmacies',
100
+ 'medical_equipment',
101
+ 'biotechnology',
102
+ 'home_health',
103
+ 'healthcare_staffing',
104
+ 'wellness_benefits',
105
+ 'healthcare_social_assistance',
106
+ ],
107
+ professional_services: [
108
+ 'legal_services',
109
+ 'accounting_auditing',
110
+ 'consulting_firms',
111
+ 'marketing_advertising',
112
+ 'real_estate_agents_property',
113
+ 'corporate_services_incorporation',
114
+ 'hr_workforce_management',
115
+ 'direct_marketing_telemarketing',
116
+ 'legal_accounting_consulting_programming',
117
+ ],
118
+ manufacturing_industrial: [
119
+ 'chemical_manufacturing',
120
+ 'electronics_hardware',
121
+ 'automotive_manufacturing',
122
+ 'construction_materials',
123
+ 'textiles_apparel',
124
+ 'mining',
125
+ ],
126
+ real_estate_construction: ['real_estate', 'construction'],
127
+ logistics_transportation: ['transportation_warehousing'],
128
+ wholesale_trade: ['wholesale_trade'],
129
+ business_support_services: ['business_support_building'],
130
+ escort_dating_adult_entertainment: [
131
+ 'escort_services',
132
+ 'dating_adult_entertainment',
133
+ ],
134
+ };
135
+ export function getCompanyOnboardingSubIndustryCodesForIndustry(industryTypeCode) {
136
+ if (industryTypeCode == null || industryTypeCode === '') {
137
+ return [];
138
+ }
139
+ const row = COMPANY_ONBOARDING_SUB_INDUSTRY_CODES_BY_INDUSTRY[industryTypeCode];
140
+ return row != null ? [...row] : [];
141
+ }
@@ -4,11 +4,13 @@ import { getSupportedTransactionById } from '../../../entity/transaction/transac
4
4
  import { getUncategorizedAccounts } from '../../accountList/accountListSelector';
5
5
  import { toTransactionDetailLocalData } from '../transactionDetailLocalDataHelper';
6
6
  import { clearTransactionDetailLocalData, initializeTransactionDetailLocalData, saveTransactionDetailLocalData, } from '../transactionDetailReducer';
7
+ import { getTransactionDetailKey } from '../transactionDetailState';
7
8
  export const initializeTransactionDetailLocalDataEpic = (actions$, state$) => actions$.pipe(filter(initializeTransactionDetailLocalData.match), mergeMap((action) => {
8
9
  const { transactionId, vendorId } = action.payload;
9
10
  const fetchActions = [];
10
11
  const transactionState = state$.value.transactionState;
11
- const detail = getSupportedTransactionById(transactionState, transactionId.id); // entity store uses full zeni ID as canonical key
12
+ const key = getTransactionDetailKey(transactionId);
13
+ const detail = getSupportedTransactionById(transactionState, key);
12
14
  if (detail != null) {
13
15
  const uncategorizedIncomeExpense = getUncategorizedAccounts(state$.value.accountState, state$.value.accountListState, 'accountList');
14
16
  const localData = toTransactionDetailLocalData(detail, vendorId, uncategorizedIncomeExpense);
@@ -3,7 +3,7 @@ import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
4
4
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
5
5
  import { getCurrentTenant } from '../../../entity/tenant/tenantSelector';
6
- import { updateTransactionIsMiscategorized } from '../../../entity/transaction/transactionReducer';
6
+ import { removeTransactionCategorization, updateTransactionIsMiscategorized, } from '../../../entity/transaction/transactionReducer';
7
7
  import { createZeniAPIStatus, isSuccessStatus, } from '../../../responsePayload';
8
8
  import { updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateTotalCountForTransactionCategorization, } from '../../expenseAutomationView/reducers/transactionsViewReducer';
9
9
  import { markTransactionAsNotMiscategorized, updateStatusConsolidatedSaveTransactionDetail, updateStatusForTransactionNotMiscategorizedUpdate, } from '../transactionDetailReducer';
@@ -37,11 +37,11 @@ export const markTransactionAsNotMiscategorizedEpic = (actions$, state$, zeniAPI
37
37
  ? []
38
38
  : [txnId];
39
39
  actions.push(updateStatusForTransactionNotMiscategorizedUpdateForCategorization(selectedTransactionCategorizationTab, [txnId], removeIdsFromPeriod, selectedPeriod, 'Completed'));
40
- // NOTE: Do NOT dispatch removeTransactionCategorization here.
41
- // That action sets transactionById[txnId] = undefined, removing the
42
- // entity from the store. On the detail page the user is still viewing
43
- // this transaction, so removing it causes an infinite loading skeleton.
44
- // The expense automation list view's own epic handles list cleanup.
40
+ if (!hasAutoLines) {
41
+ actions.push(removeTransactionCategorization({
42
+ transactionIds: [txnId],
43
+ }));
44
+ }
45
45
  if (removeIdsFromPeriod.length > 0) {
46
46
  const totalCountByPeriod = uiState.totalCount[toMonthYearPeriodId(selectedPeriod)];
47
47
  const updatedTotalCount = totalCountByPeriod === 0
@@ -11,10 +11,8 @@ export const saveTransactionDetailEpic = (actions$, state$) => actions$.pipe(fil
11
11
  const transactionDetailState = state$.value.transactionDetailState;
12
12
  const transactionState = state$.value.transactionState;
13
13
  const key = getTransactionDetailKey(transactionId);
14
- const detail = recordGet(transactionDetailState.transactionDetailById, key, // detail key is correct for transactionDetailState
15
- undefined);
16
- const transaction = getSupportedTransactionById(transactionState, transactionId.id // entity store uses full zeni ID as canonical key
17
- );
14
+ const detail = recordGet(transactionDetailState.transactionDetailById, key, undefined);
15
+ const transaction = getSupportedTransactionById(transactionState, key);
18
16
  if (detail != null && transaction != null) {
19
17
  const localData = detail.transactionDetailLocalData;
20
18
  const transactionSaveUpdates = toTransactionUpdatesFromTransactionDetailLocalData(localData, transaction, isExpenseAutomationEnabled);
@@ -21,7 +21,7 @@ export const fetchTransactionDetailEpic = (actions$, state$, zeniAPI) => actions
21
21
  const detailKey = getTransactionDetailKey(transactionId);
22
22
  const transactionActions = [];
23
23
  if (cacheOverride === true) {
24
- const transaction = getSupportedTransactionById(state$.value.transactionState, transactionId.id);
24
+ const transaction = getSupportedTransactionById(state$.value.transactionState, detailKey);
25
25
  transactionActions.push(...removeCompleteTransaction(transactionId, transaction));
26
26
  }
27
27
  const transactionDetail = recordGet(state$.value.transactionDetailState.transactionDetailById, detailKey, undefined);
@@ -40,27 +40,6 @@ export const fetchTransactionDetailEpic = (actions$, state$, zeniAPI) => actions
40
40
  transactionActions.push(fetchClassList());
41
41
  }
42
42
  transactionActions.push(updateTransactionDetailFetchState(transactionId, 'In-Progress'));
43
- // If the transaction already exists in the entity store (e.g. fetched by the
44
- // list page), initialize local data immediately so the UI can render whatever
45
- // data is available while the full detail fetch completes in parallel.
46
- //
47
- // The entity store canonical key is the full zeni ID (transactionId.id),
48
- // which is the same key used by all list-fetch epics.
49
- //
50
- // DEPENDENCY: The web-components TransactionDetailPage sets effectiveIsDisabled
51
- // to "disabled" while fetchState !== "Completed". This prevents any user edits
52
- // between this early init and the second init dispatched on API response (below).
53
- // If that guard is ever removed, user edits made against partial list data will
54
- // be silently overwritten when the full detail response arrives.
55
- if (cacheOverride !== true) {
56
- const existingTransaction = getSupportedTransactionById(state$.value.transactionState, transactionId.id);
57
- if (existingTransaction != null) {
58
- transactionActions.push(initializeTransactionDetailLocalData({
59
- transactionId,
60
- vendorId: existingTransaction.vendorId,
61
- }));
62
- }
63
- }
64
43
  const query = { transaction_type: transactionId.type };
65
44
  const getTransactionDetail$ = zeniAPI
66
45
  .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/transactions/${transactionId.id}?query=${encodeURIComponent(JSON.stringify(query))}`)
@@ -68,7 +47,7 @@ export const fetchTransactionDetailEpic = (actions$, state$, zeniAPI) => actions
68
47
  if (isSuccessResponse(response) &&
69
48
  response.data?.transaction?.transaction_id != null) {
70
49
  const updateActions = [];
71
- const responseTransactionId = toTransactionID(response.data.transaction);
50
+ const transcationId = toTransactionID(response.data.transaction);
72
51
  const responseData = {
73
52
  ...response.data,
74
53
  vendors: response.data.vendors?.map((vendor) => ({
@@ -79,15 +58,13 @@ export const fetchTransactionDetailEpic = (actions$, state$, zeniAPI) => actions
79
58
  if (responseData.vendors.length > 0) {
80
59
  updateActions.push(updateVendors(responseData.vendors, 'merge'));
81
60
  }
82
- // Use the full zeni ID as the entity store key so it matches
83
- // how all list-fetch epics save transactions, avoiding
84
- // duplicate entries under different key formats.
85
- updateActions.push(updateTransaction(responseTransactionId.id, responseData.transaction));
61
+ const transactionDetailKeyForResponse = getTransactionDetailKey(transcationId);
62
+ updateActions.push(updateTransaction(transactionDetailKeyForResponse, responseData.transaction));
86
63
  updateActions.push(initializeTransactionDetailLocalData({
87
- transactionId: responseTransactionId,
64
+ transactionId: transcationId,
88
65
  vendorId: responseData.vendors[0]?.vendor_id,
89
66
  }));
90
- updateActions.push(updateTransactionDetailFetchState(responseTransactionId, 'Completed'));
67
+ updateActions.push(updateTransactionDetailFetchState(transcationId, 'Completed'));
91
68
  // IMPORTANT: Fetch linked transactions..
92
69
  updateActions.push(...getAllFetchLinkedTransactionDetailActions(responseData.transaction, state$.value));
93
70
  if (responseData.vendors.length > 0) {
@@ -122,15 +99,12 @@ function removeCompleteTransaction(transactionId, transaction) {
122
99
  const actions = [];
123
100
  actions.push(removeTransactionDetail(transactionId));
124
101
  if (transaction != null) {
125
- // Use transactionId.id (full zeni ID) — the canonical entity store key.
126
- // getTransactionDetailKey produces a different composite key used only by
127
- // transactionDetailState, not the entity store.
128
- actions.push(removeTransaction(transactionId.id));
102
+ actions.push(removeTransaction(getTransactionDetailKey(transactionId)));
129
103
  // remove all the linked transactions too..
130
104
  const linkedTransactions = getAllLinkedTransactions(transaction);
131
105
  linkedTransactions.forEach((linkedTransaction) => {
132
106
  actions.push(removeTransactionDetail(linkedTransaction));
133
- actions.push(removeTransaction(linkedTransaction.id));
107
+ actions.push(removeTransaction(getTransactionDetailKey(linkedTransaction)));
134
108
  });
135
109
  }
136
110
  return actions;
@@ -14,7 +14,7 @@ import { toJEAndDepositTransactionTypeStrict, toVendorTransactionTypeStrict, } f
14
14
  import { isVendorTransaction, } from '../../../entity/transaction/stateTypes/vendorTransaction';
15
15
  import toSupportedTransaction from '../../../entity/transaction/toSupportedTransaction';
16
16
  import { getTransactionWithCOT } from '../../../entity/transaction/transactionHelper';
17
- import { updateTransaction } from '../../../entity/transaction/transactionReducer';
17
+ import { updateTransaction, updateTransactions, } from '../../../entity/transaction/transactionReducer';
18
18
  import { getSupportedTransactionById } from '../../../entity/transaction/transactionSelector';
19
19
  import { updateVendors } from '../../../entity/vendor/vendorReducer';
20
20
  import { getVendorByVendorId } from '../../../entity/vendor/vendorSelector';
@@ -34,8 +34,7 @@ export const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => action
34
34
  if (isSuccessResponse(response) && response.data != null) {
35
35
  const transactionIdResp = toTransactionID(response.data.transaction);
36
36
  const transactionDetailKeyForResponse = getTransactionDetailKey(transactionIdResp);
37
- const tranDetail = recordGet(transactionDetailState.transactionDetailById, transactionDetailKeyForResponse, // detail key is correct for transactionDetailState
38
- undefined);
37
+ const tranDetail = recordGet(transactionDetailState.transactionDetailById, transactionDetailKeyForResponse, undefined);
39
38
  const tranVendorUpdates = tranDetail?.transactionDetailLocalData.vendorUpdates;
40
39
  let vendorUpdates = undefined;
41
40
  if (tranVendorUpdates?.qboId != null) {
@@ -57,7 +56,7 @@ export const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => action
57
56
  if (response.data.vendors != null) {
58
57
  actions.push(updateVendors(response.data.vendors, 'merge'));
59
58
  }
60
- actions.push(updateTransaction(transactionIdResp.id, response.data.transaction));
59
+ actions.push(updateTransaction(transactionDetailKeyForResponse, response.data.transaction));
61
60
  actions.push(initializeTransactionDetailLocalData({
62
61
  transactionId: transactionIdResp,
63
62
  vendorId: response.data.vendors?.[0]?.vendor_id,
@@ -106,12 +105,9 @@ export const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => action
106
105
  }));
107
106
  }
108
107
  }
109
- // No separate updateTransactions call needed: updateTransaction above
110
- // writes under transactionIdResp.id (the full zeni ID). This is the same
111
- // key all list-fetch epics use, so list selectors will read fresh data.
112
- // ASSUMPTION: toTransactionID(response).id === the transaction_id value
113
- // that list-fetch epics pass to updateTransactions. If these ever diverge,
114
- // list rows will go stale after a detail save.
108
+ // Sync transaction categorization list state from detail save
109
+ // Updates list-keyed canonical entity so the list selector reads fresh data
110
+ actions.push(updateTransactions([response.data.transaction], (value) => value.transaction_id, 'merge'));
115
111
  const transactionWithCOT = getTransactionWithCOT(transaction);
116
112
  const pendingReviewLineIds = getPendingReviewLineIdsFromTransaction(transactionWithCOT);
117
113
  actions.push(syncTransactionCategorizationFromDetailSave(transactionIdResp, transactionWithCOT, pendingReviewLineIds, uncategorizedIncomeExpense, isUncategorizedExpenseCategoryEnabled));
@@ -129,8 +125,7 @@ export const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => action
129
125
  }));
130
126
  function preparePayload(transactionState, vendorState, transactionDetailState, transactionId, saveUpdates, cotTransactionTracking) {
131
127
  const transactionDetailKey = getTransactionDetailKey(transactionId);
132
- const detail = getSupportedTransactionById(transactionState, transactionId.id // entity store uses full zeni ID as canonical key
133
- );
128
+ const detail = getSupportedTransactionById(transactionState, transactionDetailKey);
134
129
  const tranDetail = recordGet(transactionDetailState.transactionDetailById, transactionDetailKey, undefined);
135
130
  const tranVendorUpdates = tranDetail?.transactionDetailLocalData.vendorUpdates;
136
131
  const tranCustomerUpdates = tranDetail?.transactionDetailLocalData.customerUpdates;
@@ -4,12 +4,13 @@ import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
4
4
  import { toTransactionID } from '../../../entity/transaction/payloadTypes/transactionIDPayload';
5
5
  import { updateTransaction } from '../../../entity/transaction/transactionReducer';
6
6
  import { uploadMissingAttachmentSuccess } from '../transactionDetailReducer';
7
+ import { getTransactionDetailKey } from '../transactionDetailState';
7
8
  export const uploadMissingAttachmentSuccessEpic = (actions$) => actions$.pipe(filter(uploadMissingAttachmentSuccess.match), switchMap((action) => {
8
9
  const transactionId = toTransactionID(action.payload.payload);
10
+ const transactionDetailKeyForResponse = getTransactionDetailKey(transactionId);
9
11
  return of(openSnackbar({
10
12
  messageSection: 'receipts_upload',
11
13
  messageText: 'success',
12
14
  type: 'success',
13
- }), updateTransaction(transactionId.id, // entity store uses full zeni ID as canonical key
14
- action.payload.payload));
15
+ }), updateTransaction(transactionDetailKeyForResponse, action.payload.payload));
15
16
  }));
@@ -16,8 +16,7 @@ export const getTransactionDetail = (state, transactionId, fetchLinkedTransactio
16
16
  fetchState: transactionDetail?.fetchState ?? 'Not-Started',
17
17
  error: transactionDetail?.error,
18
18
  };
19
- const transaction = getSupportedTransactionById(transactionState, transactionId.id // entity store uses full zeni ID as canonical key
20
- );
19
+ const transaction = getSupportedTransactionById(transactionState, detailKey);
21
20
  let linkedTransactions = [];
22
21
  if (transactionDetail?.fetchState === 'Completed' && transaction != null) {
23
22
  // IMPORTANT: filtering out unsupported linked transactions
@@ -37,9 +36,8 @@ export const getTransactionDetail = (state, transactionId, fetchLinkedTransactio
37
36
  linkedTransactionsFetchState.fetchState === 'Completed') {
38
37
  linkedTransactions = linkedTransactionDetails
39
38
  .map((linkedTranDetail) => {
40
- const linkedTranId = linkedTranDetail.transactionId; // eslint-disable-line @typescript-eslint/no-non-null-assertion
41
- return getSupportedTransactionById(transactionState, linkedTranId.id // entity store uses full zeni ID as canonical key
42
- );
39
+ const key = getTransactionDetailKey(linkedTranDetail.transactionId); // eslint-disable-line @typescript-eslint/no-non-null-assertion
40
+ return getSupportedTransactionById(transactionState, key);
43
41
  })
44
42
  .filter((value) => {
45
43
  if (value == null) {
@@ -1,18 +1,6 @@
1
- // TWO-KEY SYSTEM read before touching transaction lookups:
2
- //
3
- // 1. transactionDetailState.transactionDetailById → keyed by getTransactionDetailKey()
4
- // Format: "<qbo_id>_<type>" (strips the "tran_<uuid>:" prefix)
5
- // Used by: all transactionDetail epics/selectors for detail metadata (fetch state, local data, etc.)
6
- //
7
- // 2. transactionState.transactionById → keyed by transactionId.id (full zeni ID, e.g. "tran_<uuid>:<qbo_id>")
8
- // Used by: all list-fetch epics, getSupportedTransactionById, and all entity store reads/writes
9
- //
10
- // IMPORTANT: Never use getTransactionDetailKey() to look up or remove from transactionState.
11
- // Always use transactionId.id for the entity store.
12
- // Produces the transactionDetailState key: "<qbo_id>_<type>"
1
+ // This transaction key only extracts the third party ID and uses that
2
+ // for the ID
13
3
  export const getTransactionDetailKey = (transactionId) => `${getActualTransactionID(transactionId.id)}_${transactionId.type}`;
14
- // Produces the full-zeni-ID-based key: "<tran_uuid:qbo_id>_<type>"
15
- // Note: this key format is not used by the entity store (which uses transactionId.id directly).
16
4
  export const getZeniTransactionDetailKey = (transactionId) => `${transactionId.id}_${transactionId.type}`;
17
5
  export const getThirdPartyIdFromTransactionId = (transactionId) => {
18
6
  const zeniGeneratedId = transactionId.id;
package/lib/index.d.ts CHANGED
@@ -456,6 +456,7 @@ import { ActivityHistorySelectorView } from './view/spendManagement/commonHistor
456
456
  import { clearSetupViewDataInLocalStore, enableSetup, saveCompnayOfficerAdditionalDocumentsInLocalStore, saveCompnayOfficerPhoneInLocalStore, saveIndustryAndIncDateInLocalStore, saveSetupViewDataInLocalStore, saveTreasuryAdditionalDocumentsInLocalStore, updateBusinessVerificationDetails, updateSelectedCompanyOfficer, updateSetupViewLocalStoreData } from './view/spendManagement/commonSetup/setupViewReducer';
457
457
  import { BusinessVerificationDetails, CompanyDetails, CompanyOfficersDetails, FundingAccount, SetupView, getBusinessVerificationDetails, getCommonSetupViewDetails } from './view/spendManagement/commonSetup/setupViewSelector';
458
458
  import { SetupViewLocalData, SetupViewState } from './view/spendManagement/commonSetup/setupViewState';
459
+ import { COMPANY_ONBOARDING_INDUSTRY_TYPE_CODES, COMPANY_ONBOARDING_SUB_INDUSTRY_CODES_BY_INDUSTRY, COMPANY_PURPOSE_OF_ACCOUNT_CODES, COMPANY_SOURCE_OF_FUNDS_CODES, COMPANY_TRANSACTION_VOLUME_CODES, CompanyOnboardingIndustryTypeCode, CompanyOnboardingSubIndustryTypeCode, CompanyPurposeOfAccountCode, CompanySourceOfFundsCode, CompanyTransactionVolumeCode, getCompanyOnboardingSubIndustryCodesForIndustry } from './view/spendManagement/commonSetup/types/businessVerification';
459
460
  import { BulkOperationFinishedType, BulkOperationType, SameDayAchDisablementConfig, ZeniAccountsPromoConfig, checkIfLowBalance, getActualPaymentDate, getBillListUniqueType, getBulkOperationSuffix, getRemiListUniqueType, getSelectedCompanyOfficer, getSpendManagementEffectiveListPeriod, isAwaitingMarkAsPaid, isBulkProcessing, isPaymentMethodOutsideZeni, isVerifiedPaymentAccountProviderStatusCode, isVerifiedStatusCode, showBillPayPromoPage, showReimbursementPromoPage, toAccountsPromoConfig, toSameDayAchDisablementConfig } from './view/spendManagement/helpers';
460
461
  import { establishPlaidConnection, getPaymentAccounts, getPlaidLinkToken, updateMappedCashAccount, updatePaymentAccount, updatePaymentAccountLoginStatus, updatePaymentAccountStatus, updatePrimaryFundingAccount } from './view/spendManagement/plaidAccount/plaidAccountViewReducer';
461
462
  import { getPlaidAccountDetails } from './view/spendManagement/plaidAccount/plaidAccountViewSelector';
@@ -733,6 +734,8 @@ export { AllowedValueWithCode, AllowedValue, AllowedValueWithID, isAllowedValueW
733
734
  export { ManagementUIState, PortfolioUIState };
734
735
  export { fetchCompanyPassportView, CompanyPassportView, getCompanyPassportView, getCompanyPassportLocalStoreData, isCompanyPassportDataToBeSaved, companyPassportSaveDataInLocalStore, saveIndustryAndIncDateInCompanyPassportLocalStore, deleteCompanyOfficerInLocalStore, companyPassportClearDataInLocalStore, updateCompanyPassportLocalStoreData, saveCompanyPassportDetails, clearCompanyView, UpdateActionType, CompanyPassportLocalStoreDataView, };
735
736
  export { CompanyInfoLocalData, IncInfoLocalData, TaxDetailsLocalData, CompanyDetailsLocalData, PrimaryContactLocalData, CompanyOfficerLocalData, CompanyOfficerType, CompanyPassportLocalData, toCompanyPassportLocalData, };
737
+ export type { CompanyOnboardingIndustryTypeCode, CompanyOnboardingSubIndustryTypeCode, CompanyPurposeOfAccountCode, CompanySourceOfFundsCode, CompanyTransactionVolumeCode, };
738
+ export { COMPANY_ONBOARDING_INDUSTRY_TYPE_CODES, COMPANY_ONBOARDING_SUB_INDUSTRY_CODES_BY_INDUSTRY, COMPANY_PURPOSE_OF_ACCOUNT_CODES, COMPANY_SOURCE_OF_FUNDS_CODES, COMPANY_TRANSACTION_VOLUME_CODES, getCompanyOnboardingSubIndustryCodesForIndustry, };
736
739
  export { getUncategorizedAccounts, UncategorizedAccounts, getNestedAccountListHierarchy, };
737
740
  export { getBankAccount, BankAccountSelectorView };
738
741
  export { AddOnEstimate, SubscriptionViewSortKey, toSubscriptionSortKeyType, SubscriptionView, SubscriptionListView as CompanySubscriptionView, getSubscriptionListView as getCompanySubscriptionView, SubscriptionListUIState as SubscriptionUIState, updateSubscriptionListUIState as updateCompanySubscriptionUIState, SubscriptionUpdateData, SubscriptionCreateData, SubscriptionActionType, SubscriptionDetails, SubscriptionEstimateWithSchema, fetchSubscriptionList, fetchSubscriptionPlans, fetchSubscriptionAddOns, fetchSubscriptionCoupons, fetchSubscriptionDetails, saveSubscriptionUpdates, saveSubscriptionNotesUpdates, fetchSubscriptionCreateEstimate, fetchSubscriptionUpdateEstimate, clearSubscriptionView, initializeSubscriptionLocalData, saveSubscriptionLocalData, clearSubscriptionUpdateViewDataByTenantId, clearSubscriptionLocalData, getSubscriptionsDetailView, getSubscriptionLocalStoreDataView, getSubscriptionEstimationData, SubscriptionEstimationData, SubscriptionLocalStoreDataView, SubscriptionsDetailView, };