@@ -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' ;
@@ -7025,5 +7026,71 @@ describe('actions/Policy', () => {
70257026 apiWriteSpy . mockRestore ( ) ;
70267027 isIOUReportUsingReportSpy . mockRestore ( ) ;
70277028 } ) ;
7029+
7030+ it ( 'should negate the converted transaction amounts on the optimistic data so the expense-report table total stays positive' , async ( ) => {
7031+ await Onyx . set ( ONYXKEYS . SESSION , { email : ESH_EMAIL , accountID : ESH_ACCOUNT_ID } ) ;
7032+ await waitForBatchedUpdates ( ) ;
7033+
7034+ const employeeAccountID = 400 ;
7035+ const iouReportOwnerEmail = 'employee@example.com' ;
7036+
7037+ const iouReport : Report = {
7038+ ...createRandomReport ( 1 , undefined ) ,
7039+ reportID : '900' ,
7040+ type : CONST . REPORT . TYPE . IOU ,
7041+ ownerAccountID : employeeAccountID ,
7042+ chatReportID : '901' ,
7043+ policyID : 'oldPolicyID' ,
7044+ currency : CONST . CURRENCY . USD ,
7045+ total : 5000 ,
7046+ } ;
7047+
7048+ // IOU transactions can be stored with either sign; use the negative case here so the test fails
7049+ // if the helper ever regresses to a plain `-convertedAmount` (which would flip back to positive)
7050+ const transaction : Transaction = {
7051+ ...createRandomTransaction ( 900 ) ,
7052+ transactionID : 'transaction900' ,
7053+ reportID : iouReport . reportID ,
7054+ amount : 5000 ,
7055+ modifiedAmount : '' ,
7056+ convertedAmount : - 6000 ,
7057+ convertedTaxAmount : - 600 ,
7058+ } ;
7059+
7060+ await Onyx . set ( `${ ONYXKEYS . COLLECTION . REPORT } ${ iouReport . reportID } ` , iouReport ) ;
7061+ await waitForBatchedUpdates ( ) ;
7062+
7063+ const apiWriteSpy = jest . spyOn ( require ( '@libs/API' ) , 'write' ) . mockImplementation ( ( ) => Promise . resolve ( ) ) ;
7064+ const isIOUReportUsingReportSpy = jest . spyOn ( ReportUtils , 'isIOUReportUsingReport' ) . mockReturnValue ( true ) ;
7065+ const getReportTransactionsSpy = jest . spyOn ( ReportUtils , 'getReportTransactions' ) . mockReturnValue ( [ transaction ] ) ;
7066+
7067+ const mockTranslate = ( ( key : string ) => key ) as unknown as Parameters < typeof Policy . createWorkspaceFromIOUPayment > [ 8 ] ;
7068+ Policy . createWorkspaceFromIOUPayment ( iouReport , undefined , ESH_ACCOUNT_ID , ESH_EMAIL , iouReportOwnerEmail , undefined , CONST . CURRENCY . USD , undefined , mockTranslate , { } ) ;
7069+ await waitForBatchedUpdates ( ) ;
7070+
7071+ const writeOptions = apiWriteSpy . mock . calls . at ( 0 ) ?. at ( 2 ) as {
7072+ optimisticData ?: Array < { key ?: string ; value ?: Record < string , Transaction > | null } > ;
7073+ failureData ?: Array < { key ?: string ; value ?: Record < string , Transaction > | null } > ;
7074+ } ;
7075+
7076+ const transactionOptimisticUpdate = ( writeOptions ?. optimisticData ?? [ ] ) . find ( ( update ) => update . key === ONYXKEYS . COLLECTION . TRANSACTION ) ;
7077+ const optimisticTransaction = transactionOptimisticUpdate ?. value ?. [ `${ ONYXKEYS . COLLECTION . TRANSACTION } ${ transaction . transactionID } ` ] ;
7078+
7079+ // The expense-report sign convention flips the stored sign for display, so the converted magnitudes must be stored negative
7080+ expect ( optimisticTransaction ?. amount ) . toBe ( - 5000 ) ;
7081+ expect ( optimisticTransaction ?. convertedAmount ) . toBe ( - 6000 ) ;
7082+ expect ( optimisticTransaction ?. convertedTaxAmount ) . toBe ( - 600 ) ;
7083+
7084+ // And the failure data restores the original values on rollback
7085+ const transactionFailureUpdate = ( writeOptions ?. failureData ?? [ ] ) . find ( ( update ) => update . key === ONYXKEYS . COLLECTION . TRANSACTION ) ;
7086+ const rolledBackTransaction = transactionFailureUpdate ?. value ?. [ `${ ONYXKEYS . COLLECTION . TRANSACTION } ${ transaction . transactionID } ` ] ;
7087+ expect ( rolledBackTransaction ?. amount ) . toBe ( 5000 ) ;
7088+ expect ( rolledBackTransaction ?. convertedAmount ) . toBe ( - 6000 ) ;
7089+ expect ( rolledBackTransaction ?. convertedTaxAmount ) . toBe ( - 600 ) ;
7090+
7091+ apiWriteSpy . mockRestore ( ) ;
7092+ isIOUReportUsingReportSpy . mockRestore ( ) ;
7093+ getReportTransactionsSpy . mockRestore ( ) ;
7094+ } ) ;
70287095 } ) ;
70297096} ) ;
0 commit comments