Skip to content

Commit 74721f3

Browse files
committed
Merge branch 'main' of https://github.com/TaduJR/App into fix-localization-dates-not-being-localized-correctly
2 parents 46fa31d + ec619e1 commit 74721f3

77 files changed

Lines changed: 2579 additions & 338 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 1 addition & 0 deletions
Loading

cspell.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"Crédit",
9090
"DFOLLY",
9191
"DSYM",
92+
"DUALENTRY",
9293
"DYNAMICEXTERNAL",
9394
"Danske",
9495
"Deel",
@@ -582,6 +583,7 @@
582583
"dotlottie",
583584
"dsyms",
584585
"dylib",
586+
"dualEntry",
585587
"durationMillis",
586588
"e2edelta",
587589
"ecash",

docs/articles/new-expensify/concierge-ai/How-Concierge-Analyzes-Spend.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: How Concierge Analyzes Spend
33
description: Learn how Concierge proactively analyzes company spend to share highlights, and how to ask Concierge questions about your expense data on demand.
4-
keywords: [New Expensify, Concierge, spend analysis, ask Concierge about expenses, expense questions, Concierge management report, insights, AI, expense trends, proactive insights, spend monitoring, flux analysis]
4+
keywords: [New Expensify, Concierge, spend analysis, ask Concierge about expenses, expense questions, Concierge management report, insights, AI, expense trends, proactive insights, spend monitoring, flux analysis, spend chart, bar chart, Concierge chart]
55
internalScope: Audience is Workspace Admins and Members. Covers how Concierge performs proactive spend analysis and how to ask Concierge questions about expense data. Does not cover how to use Insights or generate custom reports.
66
---
77

@@ -51,6 +51,19 @@ Concierge currently answers direct lookup questions about your expense data. If
5151

5252
---
5353

54+
## How to explore a Concierge spend chart
55+
56+
When you ask Concierge to show spending as a chart—for example, “Give me a bar chart of my monthly expenses this year”—Concierge can reply with an interactive bar chart directly in the chat, alongside its written answer.
57+
58+
On web, you can interact with the chart to dig into the numbers behind it:
59+
60+
- Hover over a bar to see a tooltip with the details for that bar.
61+
- Select a bar to open its matching expenses on the **Search** page.
62+
63+
This lets you move from a high-level view of your spend straight to the individual expenses without building a search yourself.
64+
65+
---
66+
5467
## Where Concierge shares spend analysis insights
5568

5669
When Concierge analyzes spend proactively, it sends a message to the #admins room for that workspace. The message is visible to all members of that room.

src/CONST/index.ts

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ const CONST = {
519519
RILLET: 'Rillet',
520520
BILLCOM: 'Bill.com',
521521
ZENEFITS: 'Zenefits',
522+
DUALENTRY: 'DualEntry',
522523
},
523524

524525
REVERSED_TRANSACTION_ATTRIBUTE: 'is-reversed-transaction',
@@ -1028,6 +1029,7 @@ const CONST = {
10281029
BULK_SUBMIT_APPROVE_PAY: 'bulkSubmitApprovePay',
10291030
VENDOR_MATCHING: 'vendorMatching',
10301031
RILLET: 'rillet',
1032+
DUALENTRY: 'dualEntry',
10311033
INTUIT_ENTERPRISE_SUITE: 'intuitEnterpriseSuite',
10321034
RULES_REVAMP: 'rulesRevamp',
10331035
COMMUTER_EXCLUSIONS: 'commuterExclusions',
@@ -3579,6 +3581,54 @@ const CONST = {
35793581
CREDIT_CARD: 'Credit Card',
35803582
},
35813583

3584+
DUALENTRY_CONFIG: {
3585+
SUBSIDIARY_ID: 'subsidiaryID',
3586+
ENABLE_NEW_CATEGORIES: 'enableNewCategories',
3587+
SYNC_TAX_RATES: 'syncTaxRates',
3588+
EXPORTER: 'exporter',
3589+
EXPORT_DATE: 'exportDate',
3590+
REIMBURSABLE: 'reimbursable',
3591+
NON_REIMBURSABLE: 'nonReimbursable',
3592+
CREDIT_CARD_ACCOUNT_ID: 'creditCardAccountID',
3593+
EXPORT_TO_MULTIPLE_ACCOUNTS: 'exportToMultipleAccounts',
3594+
DEFAULT_VENDORID: 'defaultVendorID',
3595+
TRAVEL_INVOICING_PAYABLE_ACCOUNT_ID: 'travelInvoicingPayableAccountID',
3596+
ACCOUNTING_METHOD: 'accountingMethod',
3597+
AUTO_SYNC: 'autoSync',
3598+
SYNC_REIMBURSED_REPORTS: 'syncReimbursedReports',
3599+
BILL_PAYMENT_ACCOUNT_ID: 'billPaymentAccountID',
3600+
SYNC_EXPENSIFY_CARD_SETTLEMENTS: 'syncExpensifyCardSettlements',
3601+
SETTLEMENTS_BANK_ACCOUNT_ID: 'settlementsBankAccountID',
3602+
SYNC_TRAVEL_INVOICING_SETTLEMENTS: 'syncTravelInvoicingSettlements',
3603+
TRAVEL_INVOICING_SETTLEMENTS_BANK_ACCOUNT_ID: 'travelInvoicingSettlementsBankAccountID',
3604+
FIELD_MAPPING_PREFIX: 'fieldMapping_',
3605+
CARD_PROGRAM_ACCOUNT_PREFIX: 'cardProgramAccount_',
3606+
},
3607+
3608+
DUALENTRY_MAPPING_VALUE: {
3609+
NONE: 'NONE',
3610+
TAG: 'TAG',
3611+
},
3612+
3613+
DUALENTRY_EXPORT_REIMBURSABLE: {
3614+
VENDOR_BILL: 'VENDOR_BILL',
3615+
},
3616+
3617+
DUALENTRY_EXPORT_NON_REIMBURSABLE: {
3618+
DIRECT_EXPENSE: 'DIRECT_EXPENSE',
3619+
},
3620+
3621+
DUALENTRY_EXPORT_DATE: {
3622+
LAST_EXPENSE: 'LAST_EXPENSE',
3623+
REPORT_EXPORTED: 'REPORT_EXPORTED',
3624+
REPORT_SUBMITTED: 'REPORT_SUBMITTED',
3625+
},
3626+
3627+
DUALENTRY_TAX_TYPE: {
3628+
VAT: 'VAT',
3629+
GST: 'GST',
3630+
},
3631+
35823632
UPDATE_PERSONAL_BANK_ACCOUNT: {
35833633
PAGE_NAME: {
35843634
LEGAL_NAME: 'legal-name',
@@ -4328,6 +4378,7 @@ const CONST = {
43284378
SAGE_INTACCT: 'intacct',
43294379
CERTINIA: 'financialforce',
43304380
RILLET: 'rillet',
4381+
DUALENTRY: 'dualEntry',
43314382
GUSTO: 'gusto',
43324383
ZENEFITS: 'zenefits',
43334384
MERGE_HR: 'merge_hris',
@@ -4344,6 +4395,7 @@ const CONST = {
43444395
QBD: 'quickbooks-desktop',
43454396
CERTINIA: 'certinia',
43464397
RILLET: 'rillet',
4398+
DUALENTRY: 'dualentry',
43474399
GUSTO: 'gusto',
43484400
ZENEFITS: 'zenefits',
43494401
MERGE_HR: 'merge-hr',
@@ -4356,6 +4408,7 @@ const CONST = {
43564408
intacct: 'Sage Intacct',
43574409
financialforce: 'Certinia',
43584410
rillet: 'Rillet',
4411+
dualEntry: 'DualEntry',
43594412
gusto: 'Gusto',
43604413
billCom: 'Bill.com',
43614414
zenefits: 'TriNet',
@@ -4366,7 +4419,7 @@ const CONST = {
43664419
other: 'Other',
43674420
},
43684421
get ACCOUNTING_CONNECTION_NAMES() {
4369-
return [this.NAME.QBO, this.NAME.QBD, this.NAME.XERO, this.NAME.NETSUITE, this.NAME.SAGE_INTACCT, this.NAME.CERTINIA, this.NAME.RILLET] as const;
4422+
return [this.NAME.QBO, this.NAME.QBD, this.NAME.XERO, this.NAME.NETSUITE, this.NAME.SAGE_INTACCT, this.NAME.CERTINIA, this.NAME.RILLET, this.NAME.DUALENTRY] as const;
43704423
},
43714424
get HR_CONNECTION_NAMES() {
43724425
return [this.NAME.GUSTO, this.NAME.ZENEFITS, this.NAME.MERGE_HR] as const;
@@ -4469,6 +4522,9 @@ const CONST = {
44694522
RILLET_SYNC_TITLE: 'rilletSyncTitle',
44704523
RILLET_SYNC_CONNECTION: 'rilletSyncConnection',
44714524
RILLET_SYNC_IMPORT_DATA: 'rilletSyncImportData',
4525+
DUALENTRY_SYNC_TITLE: 'dualEntrySyncTitle',
4526+
DUALENTRY_SYNC_CONNECTION: 'dualEntrySyncConnection',
4527+
DUALENTRY_SYNC_IMPORT_DATA: 'dualEntrySyncImportData',
44724528
},
44734529
SYNC_STAGE_TIMEOUT_MINUTES: 20,
44744530
},
@@ -4920,6 +4976,11 @@ const CONST = {
49204976
* Name of Card NVP for Rillet custom export accounts
49214977
*/
49224978
NVP_RILLET_EXPORT_ACCOUNT: 'rillet_export_account',
4979+
4980+
/**
4981+
* Name of Card NVP for DualEntry custom export accounts
4982+
*/
4983+
NVP_DUALENTRY_EXPORT_ACCOUNT: 'dualentry_export_account',
49234984
},
49244985
EXPORT_CARD_POLICY_TYPES: {
49254986
/**
@@ -4967,6 +5028,11 @@ const CONST = {
49675028
* Name of Card NVP for Rillet custom export accounts
49685029
*/
49695030
NVP_RILLET_EXPORT_ACCOUNT_POLICY_ID: 'rillet_export_account_policy_id',
5031+
5032+
/**
5033+
* Name of Card NVP for DualEntry custom export accounts
5034+
*/
5035+
NVP_DUALENTRY_EXPORT_ACCOUNT_POLICY_ID: 'dualentry_export_account_policy_id',
49705036
},
49715037
},
49725038
AVATAR_ROW_SIZE: {
@@ -7794,6 +7860,14 @@ const CONST = {
77947860
description: `workspace.upgrade.${this.POLICY.CONNECTIONS.NAME.RILLET}.description` as const,
77957861
icon: 'RilletSquare',
77967862
},
7863+
[this.POLICY.CONNECTIONS.NAME.DUALENTRY]: {
7864+
id: this.POLICY.CONNECTIONS.NAME.DUALENTRY,
7865+
alias: 'dualentry',
7866+
name: this.POLICY.CONNECTIONS.NAME_USER_FRIENDLY.dualEntry,
7867+
title: `workspace.upgrade.${this.POLICY.CONNECTIONS.NAME.DUALENTRY}.title` as const,
7868+
description: `workspace.upgrade.${this.POLICY.CONNECTIONS.NAME.DUALENTRY}.description` as const,
7869+
icon: 'DualEntrySquare',
7870+
},
77977871
approvals: {
77987872
id: 'approvals' as const,
77997873
alias: 'approvals' as const,

src/ONYXKEYS.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,10 @@ const ONYXKEYS = {
808808
/** List of transaction IDs used when navigating to prev/next transaction when viewing it in RHP */
809809
TRANSACTION_THREAD_NAVIGATION_TRANSACTION_IDS: 'transactionThreadNavigationTransactionIDs',
810810

811+
/** Hash of the search snapshot that holds the transactions referenced by TRANSACTION_THREAD_NAVIGATION_TRANSACTION_IDS.
812+
* Used to fall back to snapshot data when the live transaction collection hasn't loaded those transactions yet (e.g. opening an expense from the Spend page as an approver). */
813+
TRANSACTION_THREAD_NAVIGATION_SNAPSHOT_HASH: 'transactionThreadNavigationSnapshotHash',
814+
811815
/** Optional map of transactionID -> sibling descriptor for prev/next navigation in snapshot-backed flows (e.g. Home "Recently added"), where siblings may be absent from the main Onyx collections. When set, navigation resolves (and lazily creates) each sibling's thread on demand from its descriptor. */
812816
TRANSACTION_THREAD_NAVIGATION_THREAD_REPORT_IDS: 'transactionThreadNavigationThreadReportIDs',
813817

@@ -1286,6 +1290,8 @@ const ONYXKEYS = {
12861290
EDIT_AGENT_RULE_FORM_DRAFT: 'editAgentRuleFormDraft',
12871291
RILLET_CREDENTIALS_FORM: 'rilletCredentialsForm',
12881292
RILLET_CREDENTIALS_FORM_DRAFT: 'rilletCredentialsFormDraft',
1293+
DUALENTRY_CREDENTIALS_FORM: 'dualEntryCredentialsForm',
1294+
DUALENTRY_CREDENTIALS_FORM_DRAFT: 'dualEntryCredentialsFormDraft',
12891295
},
12901296
DERIVED: {
12911297
REPORT_ATTRIBUTES: 'reportAttributes',
@@ -1437,6 +1443,7 @@ type OnyxFormValuesMapping = {
14371443
[ONYXKEYS.FORMS.ADD_AGENT_RULE_FORM]: FormTypes.AddAgentRuleForm;
14381444
[ONYXKEYS.FORMS.EDIT_AGENT_RULE_FORM]: FormTypes.EditAgentRuleForm;
14391445
[ONYXKEYS.FORMS.RILLET_CREDENTIALS_FORM]: FormTypes.RilletCredentialsForm;
1446+
[ONYXKEYS.FORMS.DUALENTRY_CREDENTIALS_FORM]: FormTypes.DualEntryCredentialsForm;
14401447
};
14411448

14421449
type OnyxFormDraftValuesMapping = {
@@ -1789,6 +1796,7 @@ type OnyxValuesMapping = {
17891796
[ONYXKEYS.REPORT_NAVIGATION_LAST_SEARCH_QUERY]: OnyxTypes.LastSearchParams;
17901797
[ONYXKEYS.NVP_LAST_ANDROID_LOGIN]: string;
17911798
[ONYXKEYS.TRANSACTION_THREAD_NAVIGATION_TRANSACTION_IDS]: string[];
1799+
[ONYXKEYS.TRANSACTION_THREAD_NAVIGATION_SNAPSHOT_HASH]: number;
17921800
[ONYXKEYS.TRANSACTION_THREAD_NAVIGATION_THREAD_REPORT_IDS]: Record<string, TransactionThreadNavigationDescriptor>;
17931801
[ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES]: OnyxTypes.ExportTemplate[];
17941802
[ONYXKEYS.ONBOARDING_USER_REPORTED_INTEGRATION]: OnboardingAccounting;

src/ROUTES.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4474,6 +4474,18 @@ const ROUTES = {
44744474
route: 'workspaces/:policyID/accounting/rillet/advanced/travel-invoicing-settlement-account',
44754475
getRoute: (policyID: string) => `workspaces/${policyID}/accounting/rillet/advanced/travel-invoicing-settlement-account` as const,
44764476
},
4477+
POLICY_ACCOUNTING_DUALENTRY_SETUP: {
4478+
route: 'workspaces/:policyID/accounting/dualentry/setup',
4479+
getRoute: (policyID: string) => `workspaces/${policyID}/accounting/dualentry/setup` as const,
4480+
},
4481+
POLICY_ACCOUNTING_DUALENTRY_EXISTING_CONNECTIONS: {
4482+
route: 'workspaces/:policyID/accounting/dualentry/existing-connections',
4483+
getRoute: (policyID: string) => `workspaces/${policyID}/accounting/dualentry/existing-connections` as const,
4484+
},
4485+
POLICY_ACCOUNTING_DUALENTRY_COMPANY_SELECTOR: {
4486+
route: 'workspaces/:policyID/accounting/dualentry/company-selector',
4487+
getRoute: (policyID: string) => `workspaces/${policyID}/accounting/dualentry/company-selector` as const,
4488+
},
44774489
ADD_EXISTING_EXPENSE: {
44784490
route: 'search/r/:reportID/add-existing-expense/:backToReport?',
44794491
getRoute: (reportID: string | undefined, backToReport?: string) => `search/r/${reportID}/add-existing-expense/${backToReport ?? ''}` as const,

src/SCREENS.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,9 @@ const SCREENS = {
689689
RILLET_BILL_PAYMENT_ACCOUNT: 'Policy_Accounting_Rillet_Bill_Payment_Account',
690690
RILLET_EXPENSIFY_CARD_SETTLEMENT_ACCOUNT: 'Policy_Accounting_Rillet_Expensify_Card_Settlement_Account',
691691
RILLET_TRAVEL_INVOICING_SETTLEMENT_ACCOUNT: 'Policy_Accounting_Rillet_Travel_Invoicing_Settlement_Account',
692+
DUALENTRY_SETUP: 'Policy_Accounting_DualEntry_Setup',
693+
DUALENTRY_EXISTING_CONNECTIONS: 'Policy_Accounting_DualEntry_Existing_Connections',
694+
DUALENTRY_COMPANY_SELECTOR: 'Policy_Accounting_DualEntry_Subsidiary_Selector',
692695
CARD_RECONCILIATION: 'Policy_Accounting_Card_Reconciliation',
693696
CARD_RECONCILIATION_SAGE_INTACCT_AUTO_SYNC: 'Policy_Accounting_Card_Reconciliation_Sage_Intacct_Auto_Sync',
694697
DYNAMIC_RECONCILIATION_ACCOUNT_SETTINGS: 'Dynamic_Policy_Accounting_Reconciliation_Account_Settings',
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import useHasReusablePoliciesConnectedTo from '@hooks/useHasReusablePoliciesConnectedTo';
2+
3+
import Navigation from '@libs/Navigation/Navigation';
4+
5+
import CONST from '@src/CONST';
6+
import ROUTES from '@src/ROUTES';
7+
8+
import {useEffect} from 'react';
9+
10+
type ConnectToDualEntryFlowProps = {
11+
policyID: string;
12+
};
13+
14+
function ConnectToDualEntryFlow({policyID}: ConnectToDualEntryFlowProps) {
15+
const hasReusablePoliciesConnectedToDualEntry = useHasReusablePoliciesConnectedTo(CONST.POLICY.CONNECTIONS.NAME.DUALENTRY, policyID);
16+
17+
useEffect(() => {
18+
if (hasReusablePoliciesConnectedToDualEntry) {
19+
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_DUALENTRY_EXISTING_CONNECTIONS.getRoute(policyID));
20+
return;
21+
}
22+
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_DUALENTRY_SETUP.getRoute(policyID));
23+
// This needs to run once as we will navigate away
24+
// eslint-disable-next-line react-hooks/exhaustive-deps
25+
}, []);
26+
27+
return null;
28+
}
29+
30+
export default ConnectToDualEntryFlow;

src/components/Icon/chunks/expensify-icons.chunk.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ import Info from '@assets/images/info.svg';
134134
import BillComSquare from '@assets/images/integrationicons/bill-com-icon-square.svg';
135135
import CertiniaSquare from '@assets/images/integrationicons/certinia-icon-square.svg';
136136
import CircleSlash from '@assets/images/integrationicons/circle-slash.svg';
137+
import DualEntrySquare from '@assets/images/integrationicons/dualentry-icon-square.svg';
137138
import NetSuiteExport from '@assets/images/integrationicons/export/netsuite-icon.svg';
138139
import QBOExport from '@assets/images/integrationicons/export/qbo-icon.svg';
139140
import SageIntacctExport from '@assets/images/integrationicons/export/sage-intacct-icon.svg';
@@ -473,6 +474,7 @@ const Expensicons = {
473474
ReplaceReceipt,
474475
ReceiptMultiple,
475476
RilletSquare,
477+
DualEntrySquare,
476478
Rotate,
477479
RotateLeft,
478480
Scan,

src/components/ImportedFromAccountingSoftware.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,17 @@ function ImportedFromAccountingSoftware({policyID, currentConnectionName, transl
4646
const StyleUtils = useStyleUtils();
4747
const {translate} = useLocalize();
4848
const {environmentURL} = useEnvironment();
49-
const expensifyIcons = useMemoizedLazyExpensifyIcons(['XeroSquare', 'QBOSquare', 'NetSuiteSquare', 'IntacctSquare', 'QBDSquare', 'CertiniaSquare', 'RilletSquare', 'GustoSquare']);
49+
const expensifyIcons = useMemoizedLazyExpensifyIcons([
50+
'XeroSquare',
51+
'QBOSquare',
52+
'NetSuiteSquare',
53+
'IntacctSquare',
54+
'QBDSquare',
55+
'CertiniaSquare',
56+
'RilletSquare',
57+
'DualEntrySquare',
58+
'GustoSquare',
59+
]);
5060
const icon = getIntegrationIcon(connectedIntegration, expensifyIcons);
5161

5262
if (!customTagName && shouldShow) {

0 commit comments

Comments
 (0)