@@ -11158,6 +11158,238 @@ describe('ReportUtils', () => {
1115811158 });
1115911159 });
1116011160
11161+ describe('getViolatingReportIDForRBRInLHN', () => {
11162+ it('should return null for a non-policy-expense-chat report', async () => {
11163+ await Onyx.clear();
11164+
11165+ const regularChat: Report = {
11166+ ...createRegularChat(800, []),
11167+ ownerAccountID: currentUserAccountID,
11168+ policyID: 'policy-non-pec',
11169+ };
11170+
11171+ await Onyx.merge(ONYXKEYS.SESSION, {accountID: currentUserAccountID, email: currentUserEmail});
11172+ await waitForBatchedUpdates();
11173+
11174+ const result = getViolatingReportIDForRBRInLHN(regularChat, {});
11175+ expect(result).toBeNull();
11176+
11177+ await Onyx.clear();
11178+ });
11179+
11180+ it('should return null when current user is not the submitter of the policy expense chat', async () => {
11181+ await Onyx.clear();
11182+
11183+ const otherAccountID = 999;
11184+ const chatReport: Report = {
11185+ ...createPolicyExpenseChat(801, false),
11186+ ownerAccountID: otherAccountID,
11187+ policyID: 'policy-not-submitter',
11188+ };
11189+
11190+ await Onyx.merge(ONYXKEYS.SESSION, {accountID: currentUserAccountID, email: currentUserEmail});
11191+ await waitForBatchedUpdates();
11192+
11193+ const result = getViolatingReportIDForRBRInLHN(chatReport, {});
11194+ expect(result).toBeNull();
11195+
11196+ await Onyx.clear();
11197+ });
11198+
11199+ it('should return null when policy expense chat has no policyID', async () => {
11200+ await Onyx.clear();
11201+
11202+ const chatReport: Report = {
11203+ ...createPolicyExpenseChat(802),
11204+ ownerAccountID: currentUserAccountID,
11205+ policyID: undefined,
11206+ };
11207+
11208+ await Onyx.merge(ONYXKEYS.SESSION, {accountID: currentUserAccountID, email: currentUserEmail});
11209+ await waitForBatchedUpdates();
11210+
11211+ const result = getViolatingReportIDForRBRInLHN(chatReport, {});
11212+ expect(result).toBeNull();
11213+
11214+ await Onyx.clear();
11215+ });
11216+
11217+ it('should return the violating report ID for an open expense report with violations', async () => {
11218+ await Onyx.clear();
11219+
11220+ const policyID = 'policy-rbr-positive';
11221+ const chatReportID = 'chat-rbr-positive';
11222+ const expenseReportID = 'expense-rbr-positive';
11223+ const transactionID = 'transaction-rbr-positive';
11224+
11225+ const policyData: Policy = {
11226+ id: policyID,
11227+ name: 'RBR Positive Test Workspace',
11228+ type: CONST.POLICY.TYPE.TEAM,
11229+ role: CONST.POLICY.ROLE.ADMIN,
11230+ outputCurrency: CONST.CURRENCY.USD,
11231+ reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES,
11232+ approvalMode: CONST.POLICY.APPROVAL_MODE.BASIC,
11233+ employeeList: {
11234+ [currentUserEmail]: {
11235+ role: CONST.POLICY.ROLE.ADMIN,
11236+ },
11237+ },
11238+ owner: currentUserEmail,
11239+ isPolicyExpenseChatEnabled: true,
11240+ };
11241+
11242+ const chatReport: Report = {
11243+ ...createPolicyExpenseChat(803),
11244+ reportID: chatReportID,
11245+ ownerAccountID: currentUserAccountID,
11246+ policyID,
11247+ iouReportID: expenseReportID,
11248+ hasOutstandingChildRequest: true,
11249+ };
11250+
11251+ const expenseReport: Report = {
11252+ ...createExpenseReport(804),
11253+ reportID: expenseReportID,
11254+ chatReportID,
11255+ ownerAccountID: currentUserAccountID,
11256+ managerID: 42,
11257+ policyID,
11258+ type: CONST.REPORT.TYPE.EXPENSE,
11259+ currency: CONST.CURRENCY.USD,
11260+ total: 5000,
11261+ stateNum: CONST.REPORT.STATE_NUM.OPEN,
11262+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
11263+ };
11264+
11265+ const baseTransaction = createRandomTransaction(803);
11266+ const transaction: Transaction = {
11267+ ...baseTransaction,
11268+ transactionID,
11269+ reportID: expenseReportID,
11270+ amount: 5000,
11271+ currency: CONST.CURRENCY.USD,
11272+ status: CONST.TRANSACTION.STATUS.POSTED,
11273+ reimbursable: true,
11274+ };
11275+
11276+ const transactionViolationsKey = `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}` as OnyxKey;
11277+ const transactionViolationsCollection: OnyxCollection<TransactionViolation[]> = {
11278+ [transactionViolationsKey]: [
11279+ {
11280+ name: CONST.VIOLATIONS.MISSING_CATEGORY,
11281+ type: CONST.VIOLATION_TYPES.VIOLATION,
11282+ },
11283+ ],
11284+ };
11285+
11286+ await Onyx.merge(ONYXKEYS.SESSION, {accountID: currentUserAccountID, email: currentUserEmail});
11287+ await waitForBatchedUpdates();
11288+
11289+ await Promise.all([
11290+ Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, policyData),
11291+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport),
11292+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, expenseReport),
11293+ Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction),
11294+ Onyx.merge(transactionViolationsKey, transactionViolationsCollection[transactionViolationsKey]),
11295+ ]);
11296+ await waitForBatchedUpdates();
11297+
11298+ const result = getViolatingReportIDForRBRInLHN(chatReport, transactionViolationsCollection);
11299+ expect(result).toBe(expenseReportID);
11300+
11301+ await Onyx.clear();
11302+ });
11303+
11304+ it('should return null when all expense reports in the policy are closed', async () => {
11305+ await Onyx.clear();
11306+
11307+ const policyID = 'policy-rbr-closed';
11308+ const chatReportID = 'chat-rbr-closed';
11309+ const expenseReportID = 'expense-rbr-closed';
11310+ const transactionID = 'transaction-rbr-closed';
11311+
11312+ const policyData: Policy = {
11313+ id: policyID,
11314+ name: 'RBR Closed Test Workspace',
11315+ type: CONST.POLICY.TYPE.TEAM,
11316+ role: CONST.POLICY.ROLE.ADMIN,
11317+ outputCurrency: CONST.CURRENCY.USD,
11318+ reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES,
11319+ approvalMode: CONST.POLICY.APPROVAL_MODE.BASIC,
11320+ employeeList: {
11321+ [currentUserEmail]: {
11322+ role: CONST.POLICY.ROLE.ADMIN,
11323+ },
11324+ },
11325+ owner: currentUserEmail,
11326+ isPolicyExpenseChatEnabled: true,
11327+ };
11328+
11329+ const chatReport: Report = {
11330+ ...createPolicyExpenseChat(805),
11331+ reportID: chatReportID,
11332+ ownerAccountID: currentUserAccountID,
11333+ policyID,
11334+ iouReportID: expenseReportID,
11335+ };
11336+
11337+ // Closed/approved report — stateNum > 1, so it won't be in reportsByPolicyID
11338+ // and won't pass isOpenOrProcessingReport
11339+ const expenseReport: Report = {
11340+ ...createExpenseReport(806),
11341+ reportID: expenseReportID,
11342+ chatReportID,
11343+ ownerAccountID: currentUserAccountID,
11344+ managerID: 42,
11345+ policyID,
11346+ type: CONST.REPORT.TYPE.EXPENSE,
11347+ currency: CONST.CURRENCY.USD,
11348+ total: 5000,
11349+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
11350+ statusNum: CONST.REPORT.STATUS_NUM.APPROVED,
11351+ };
11352+
11353+ const baseTransaction = createRandomTransaction(805);
11354+ const transaction: Transaction = {
11355+ ...baseTransaction,
11356+ transactionID,
11357+ reportID: expenseReportID,
11358+ amount: 5000,
11359+ currency: CONST.CURRENCY.USD,
11360+ status: CONST.TRANSACTION.STATUS.POSTED,
11361+ reimbursable: true,
11362+ };
11363+
11364+ const transactionViolationsKey = `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}` as OnyxKey;
11365+ const transactionViolationsCollection: OnyxCollection<TransactionViolation[]> = {
11366+ [transactionViolationsKey]: [
11367+ {
11368+ name: CONST.VIOLATIONS.MISSING_CATEGORY,
11369+ type: CONST.VIOLATION_TYPES.VIOLATION,
11370+ },
11371+ ],
11372+ };
11373+
11374+ await Onyx.merge(ONYXKEYS.SESSION, {accountID: currentUserAccountID, email: currentUserEmail});
11375+ await waitForBatchedUpdates();
11376+
11377+ await Promise.all([
11378+ Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, policyData),
11379+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport),
11380+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, expenseReport),
11381+ Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction),
11382+ Onyx.merge(transactionViolationsKey, transactionViolationsCollection[transactionViolationsKey]),
11383+ ]);
11384+ await waitForBatchedUpdates();
11385+
11386+ const result = getViolatingReportIDForRBRInLHN(chatReport, transactionViolationsCollection);
11387+ expect(result).toBeNull();
11388+
11389+ await Onyx.clear();
11390+ });
11391+ });
11392+
1116111393 it('should surface a GBR for admin with held expenses requiring approval or payment and avoid showing an RBR', async () => {
1116211394 await Onyx.clear();
1116311395
0 commit comments