@@ -11,10 +11,11 @@ import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager';
1111import { askToJoinPolicy , joinAccessiblePolicy } from '@src/libs/actions/Policy/Member' ;
1212import * as Policy from '@src/libs/actions/Policy/Policy' ;
1313import ONYXKEYS from '@src/ONYXKEYS' ;
14- import type { Onboarding , PolicyJoinMember , PolicyReportField , Policy as PolicyType , Report , ReportAction , ReportActions , TransactionViolations } from '@src/types/onyx' ;
14+ import type { Onboarding , PolicyJoinMember , PolicyReportField , Policy as PolicyType , Report , ReportAction , ReportActions , Transaction , TransactionViolations } from '@src/types/onyx' ;
1515import type { Participant } from '@src/types/onyx/Report' ;
1616import createRandomPolicy from '../utils/collections/policies' ;
1717import { createRandomReport } from '../utils/collections/reports' ;
18+ import createRandomTransaction from '../utils/collections/transaction' ;
1819import getOnyxValue from '../utils/getOnyxValue' ;
1920import * as TestHelper from '../utils/TestHelper' ;
2021import type { MockFetch } from '../utils/TestHelper' ;
@@ -7093,5 +7094,71 @@ describe('actions/Policy', () => {
70937094 apiWriteSpy . mockRestore ( ) ;
70947095 isIOUReportUsingReportSpy . mockRestore ( ) ;
70957096 } ) ;
7097+
7098+ it ( 'should negate the converted transaction amounts on the optimistic data so the expense-report table total stays positive' , async ( ) => {
7099+ await Onyx . set ( ONYXKEYS . SESSION , { email : ESH_EMAIL , accountID : ESH_ACCOUNT_ID } ) ;
7100+ await waitForBatchedUpdates ( ) ;
7101+
7102+ const employeeAccountID = 400 ;
7103+ const iouReportOwnerEmail = 'employee@example.com' ;
7104+
7105+ const iouReport : Report = {
7106+ ...createRandomReport ( 1 , undefined ) ,
7107+ reportID : '900' ,
7108+ type : CONST . REPORT . TYPE . IOU ,
7109+ ownerAccountID : employeeAccountID ,
7110+ chatReportID : '901' ,
7111+ policyID : 'oldPolicyID' ,
7112+ currency : CONST . CURRENCY . USD ,
7113+ total : 5000 ,
7114+ } ;
7115+
7116+ // IOU transactions can be stored with either sign; use the negative case here so the test fails
7117+ // if the helper ever regresses to a plain `-convertedAmount` (which would flip back to positive)
7118+ const transaction : Transaction = {
7119+ ...createRandomTransaction ( 900 ) ,
7120+ transactionID : 'transaction900' ,
7121+ reportID : iouReport . reportID ,
7122+ amount : 5000 ,
7123+ modifiedAmount : '' ,
7124+ convertedAmount : - 6000 ,
7125+ convertedTaxAmount : - 600 ,
7126+ } ;
7127+
7128+ await Onyx . set ( `${ ONYXKEYS . COLLECTION . REPORT } ${ iouReport . reportID } ` , iouReport ) ;
7129+ await waitForBatchedUpdates ( ) ;
7130+
7131+ const apiWriteSpy = jest . spyOn ( require ( '@libs/API' ) , 'write' ) . mockImplementation ( ( ) => Promise . resolve ( ) ) ;
7132+ const isIOUReportUsingReportSpy = jest . spyOn ( ReportUtils , 'isIOUReportUsingReport' ) . mockReturnValue ( true ) ;
7133+ const getReportTransactionsSpy = jest . spyOn ( ReportUtils , 'getReportTransactions' ) . mockReturnValue ( [ transaction ] ) ;
7134+
7135+ const mockTranslate = ( ( key : string ) => key ) as unknown as Parameters < typeof Policy . createWorkspaceFromIOUPayment > [ 8 ] ;
7136+ Policy . createWorkspaceFromIOUPayment ( iouReport , undefined , ESH_ACCOUNT_ID , ESH_EMAIL , iouReportOwnerEmail , undefined , CONST . CURRENCY . USD , undefined , mockTranslate , { } ) ;
7137+ await waitForBatchedUpdates ( ) ;
7138+
7139+ const writeOptions = apiWriteSpy . mock . calls . at ( 0 ) ?. at ( 2 ) as {
7140+ optimisticData ?: Array < { key ?: string ; value ?: Record < string , Transaction > | null } > ;
7141+ failureData ?: Array < { key ?: string ; value ?: Record < string , Transaction > | null } > ;
7142+ } ;
7143+
7144+ const transactionOptimisticUpdate = ( writeOptions ?. optimisticData ?? [ ] ) . find ( ( update ) => update . key === ONYXKEYS . COLLECTION . TRANSACTION ) ;
7145+ const optimisticTransaction = transactionOptimisticUpdate ?. value ?. [ `${ ONYXKEYS . COLLECTION . TRANSACTION } ${ transaction . transactionID } ` ] ;
7146+
7147+ // The expense-report sign convention flips the stored sign for display, so the converted magnitudes must be stored negative
7148+ expect ( optimisticTransaction ?. amount ) . toBe ( - 5000 ) ;
7149+ expect ( optimisticTransaction ?. convertedAmount ) . toBe ( - 6000 ) ;
7150+ expect ( optimisticTransaction ?. convertedTaxAmount ) . toBe ( - 600 ) ;
7151+
7152+ // And the failure data restores the original values on rollback
7153+ const transactionFailureUpdate = ( writeOptions ?. failureData ?? [ ] ) . find ( ( update ) => update . key === ONYXKEYS . COLLECTION . TRANSACTION ) ;
7154+ const rolledBackTransaction = transactionFailureUpdate ?. value ?. [ `${ ONYXKEYS . COLLECTION . TRANSACTION } ${ transaction . transactionID } ` ] ;
7155+ expect ( rolledBackTransaction ?. amount ) . toBe ( 5000 ) ;
7156+ expect ( rolledBackTransaction ?. convertedAmount ) . toBe ( - 6000 ) ;
7157+ expect ( rolledBackTransaction ?. convertedTaxAmount ) . toBe ( - 600 ) ;
7158+
7159+ apiWriteSpy . mockRestore ( ) ;
7160+ isIOUReportUsingReportSpy . mockRestore ( ) ;
7161+ getReportTransactionsSpy . mockRestore ( ) ;
7162+ } ) ;
70967163 } ) ;
70977164} ) ;
0 commit comments