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