@@ -1187,6 +1187,142 @@ describe('actions/IOU/ReportWorkflow', () => {
11871187 expect ( Navigation . navigate ) . not . toHaveBeenCalledWith ( ROUTES . RESTRICTED_ACTION . getRoute ( policyID ) ) ;
11881188 }
11891189 } ) ;
1190+
1191+ it ( 'preserves the existing report manager when cached policy data resolves to a different approver' , async ( ) => {
1192+ // eslint-disable-next-line rulesdir/no-multiple-api-calls -- Inspecting API.write calls to verify submit payload and optimistic data.
1193+ const apiWriteSpy = jest . spyOn ( API , 'write' ) . mockImplementation ( ( ) => Promise . resolve ( ) ) ;
1194+ const policyID = '1' ;
1195+ const submitterAccountID = 100 ;
1196+ const correctManagerAccountID = 101 ;
1197+ const defaultApproverAccountID = 102 ;
1198+ const submitterEmail = 'submitter@example.com' ;
1199+ const correctManagerEmail = 'correct-manager@example.com' ;
1200+ const defaultApproverEmail = 'default-approver@example.com' ;
1201+
1202+ await Onyx . set ( ONYXKEYS . PERSONAL_DETAILS_LIST , {
1203+ [ submitterAccountID ] : { accountID : submitterAccountID , login : submitterEmail } ,
1204+ [ correctManagerAccountID ] : { accountID : correctManagerAccountID , login : correctManagerEmail } ,
1205+ [ defaultApproverAccountID ] : { accountID : defaultApproverAccountID , login : defaultApproverEmail } ,
1206+ } ) ;
1207+
1208+ // Given cached policy data resolves to the default approver.
1209+ const policy : Policy = {
1210+ ...createRandomPolicy ( Number ( policyID ) ) ,
1211+ id : policyID ,
1212+ type : CONST . POLICY . TYPE . CORPORATE ,
1213+ approvalMode : CONST . POLICY . APPROVAL_MODE . ADVANCED ,
1214+ approver : defaultApproverEmail ,
1215+ owner : defaultApproverEmail ,
1216+ employeeList : { } ,
1217+ } ;
1218+
1219+ // And the report already has the correct manager.
1220+ const expenseReport : Report = {
1221+ ...createRandomReport ( Number ( policyID ) , undefined ) ,
1222+ reportID : '1' ,
1223+ policyID,
1224+ type : CONST . REPORT . TYPE . EXPENSE ,
1225+ ownerAccountID : submitterAccountID ,
1226+ managerID : correctManagerAccountID ,
1227+ stateNum : CONST . REPORT . STATE_NUM . OPEN ,
1228+ statusNum : CONST . REPORT . STATUS_NUM . OPEN ,
1229+ total : 1000 ,
1230+ currency : CONST . CURRENCY . USD ,
1231+ } ;
1232+
1233+ // When submitting the report.
1234+ submitReport ( {
1235+ expenseReport,
1236+ policy,
1237+ currentUserAccountIDParam : submitterAccountID ,
1238+ currentUserEmailParam : submitterEmail ,
1239+ hasViolations : false ,
1240+ isASAPSubmitBetaEnabled : false ,
1241+ expenseReportCurrentNextStepDeprecated : undefined ,
1242+ userBillingGracePeriodEnds : undefined ,
1243+ amountOwed : 0 ,
1244+ ownerBillingGracePeriodEnd : undefined ,
1245+ delegateEmail : undefined ,
1246+ } ) ;
1247+
1248+ // Then the API payload and optimistic report update preserve the existing manager.
1249+ const [ , parameters , onyxData ] = apiWriteSpy . mock . calls . at ( 0 ) as [ unknown , { managerAccountID ?: number } , OnyxData < typeof ONYXKEYS . COLLECTION . REPORT > ] ;
1250+ expect ( parameters . managerAccountID ) . toBe ( correctManagerAccountID ) ;
1251+
1252+ const optimisticReportUpdate = onyxData . optimisticData ?. find ( ( update ) => update . key === `${ ONYXKEYS . COLLECTION . REPORT } ${ expenseReport . reportID } ` ) ;
1253+ expect ( ( optimisticReportUpdate ?. value as Report | undefined ) ?. managerID ) . toBe ( correctManagerAccountID ) ;
1254+
1255+ apiWriteSpy . mockRestore ( ) ;
1256+ } ) ;
1257+
1258+ it ( 'ignores the existing report manager when it points to the submitter' , async ( ) => {
1259+ // eslint-disable-next-line rulesdir/no-multiple-api-calls -- Inspecting API.write calls to verify submit payload and optimistic data.
1260+ const apiWriteSpy = jest . spyOn ( API , 'write' ) . mockImplementation ( ( ) => Promise . resolve ( ) ) ;
1261+ const policyID = '1' ;
1262+ const submitterAccountID = 100 ;
1263+ const computedManagerAccountID = 101 ;
1264+ const submitterEmail = 'submitter@example.com' ;
1265+ const computedManagerEmail = 'computed-manager@example.com' ;
1266+
1267+ await Onyx . set ( ONYXKEYS . PERSONAL_DETAILS_LIST , {
1268+ [ submitterAccountID ] : { accountID : submitterAccountID , login : submitterEmail } ,
1269+ [ computedManagerAccountID ] : { accountID : computedManagerAccountID , login : computedManagerEmail } ,
1270+ } ) ;
1271+
1272+ // Given cached policy data resolves to the configured approver.
1273+ const policy : Policy = {
1274+ ...createRandomPolicy ( Number ( policyID ) ) ,
1275+ id : policyID ,
1276+ type : CONST . POLICY . TYPE . CORPORATE ,
1277+ approvalMode : CONST . POLICY . APPROVAL_MODE . ADVANCED ,
1278+ approver : computedManagerEmail ,
1279+ owner : computedManagerEmail ,
1280+ employeeList : {
1281+ [ submitterEmail ] : {
1282+ email : submitterEmail ,
1283+ submitsTo : computedManagerEmail ,
1284+ } ,
1285+ } ,
1286+ } ;
1287+
1288+ // And the draft report manager is still the submitter.
1289+ const expenseReport : Report = {
1290+ ...createRandomReport ( Number ( policyID ) , undefined ) ,
1291+ reportID : '1' ,
1292+ policyID,
1293+ type : CONST . REPORT . TYPE . EXPENSE ,
1294+ ownerAccountID : submitterAccountID ,
1295+ managerID : submitterAccountID ,
1296+ stateNum : CONST . REPORT . STATE_NUM . OPEN ,
1297+ statusNum : CONST . REPORT . STATUS_NUM . OPEN ,
1298+ total : 1000 ,
1299+ currency : CONST . CURRENCY . USD ,
1300+ } ;
1301+
1302+ // When submitting the report.
1303+ submitReport ( {
1304+ expenseReport,
1305+ policy,
1306+ currentUserAccountIDParam : submitterAccountID ,
1307+ currentUserEmailParam : submitterEmail ,
1308+ hasViolations : false ,
1309+ isASAPSubmitBetaEnabled : false ,
1310+ expenseReportCurrentNextStepDeprecated : undefined ,
1311+ userBillingGracePeriodEnds : undefined ,
1312+ amountOwed : 0 ,
1313+ ownerBillingGracePeriodEnd : undefined ,
1314+ delegateEmail : undefined ,
1315+ } ) ;
1316+
1317+ // Then the API payload and optimistic report update use the configured approver instead of routing to the submitter.
1318+ const [ , parameters , onyxData ] = apiWriteSpy . mock . calls . at ( 0 ) as [ unknown , { managerAccountID ?: number } , OnyxData < typeof ONYXKEYS . COLLECTION . REPORT > ] ;
1319+ expect ( parameters . managerAccountID ) . toBe ( computedManagerAccountID ) ;
1320+
1321+ const optimisticReportUpdate = onyxData . optimisticData ?. find ( ( update ) => update . key === `${ ONYXKEYS . COLLECTION . REPORT } ${ expenseReport . reportID } ` ) ;
1322+ expect ( ( optimisticReportUpdate ?. value as Report | undefined ) ?. managerID ) . toBe ( computedManagerAccountID ) ;
1323+
1324+ apiWriteSpy . mockRestore ( ) ;
1325+ } ) ;
11901326 } ) ;
11911327
11921328 describe ( 'delegateAccountID forwarding' , ( ) => {
@@ -1195,6 +1331,7 @@ describe('actions/IOU/ReportWorkflow', () => {
11951331
11961332 beforeEach ( async ( ) => {
11971333 jest . clearAllMocks ( ) ;
1334+ // eslint-disable-next-line rulesdir/no-multiple-api-calls -- Inspecting API.write calls to verify optimistic data.
11981335 jest . spyOn ( API , 'write' ) ;
11991336 await Onyx . merge ( ONYXKEYS . PERSONAL_DETAILS_LIST , {
12001337 [ DELEGATE_ACCOUNT_ID ] : {
0 commit comments