Skip to content

Commit bc7d1be

Browse files
authored
Merge pull request #91676 from Expensify/claude-taskBadgePriorityByAge
LHN badge priority: use oldest report action between Task and IOU
2 parents a8eb086 + f0bcac1 commit bc7d1be

2 files changed

Lines changed: 185 additions & 20 deletions

File tree

src/libs/ReportUtils.ts

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4255,22 +4255,7 @@ function getReasonAndReportActionThatRequiresAttention(
42554255
const invoiceReceiverPolicy = invoiceReceiverPolicyID ? getPolicy(invoiceReceiverPolicyID) : undefined;
42564256
const actionTypeForAssigneeToComplete = getActionTypeForAssigneeToComplete(optionOrReport, parentReportAction);
42574257

4258-
if (actionTypeForAssigneeToComplete) {
4259-
const isAssigneeExpenseAction = actionTypeForAssigneeToComplete === CONST.REPORT.ACTION_TYPES_FOR_ASSIGNEE_TO_COMPLETE.EXPENSE;
4260-
const assigneeBadge = isAssigneeExpenseAction
4261-
? getBadgeFromIOUReport(optionOrReport, undefined, policy, optionReportMetadata, invoiceReceiverPolicy, currentUserLogin, currentUserAccountID)
4262-
: CONST.REPORT.ACTION_BADGE.TASK;
4263-
return {
4264-
reason: CONST.REQUIRES_ATTENTION_REASONS.IS_WAITING_FOR_ASSIGNEE_TO_COMPLETE_ACTION,
4265-
reportAction: Object.values(reportActions)
4266-
.filter((action) => action.childType === CONST.REPORT.TYPE.TASK && !isTaskCompleted(action) && action.childManagerAccountID === deprecatedCurrentUserAccountID)
4267-
// eslint-disable-next-line rulesdir/prefer-locale-compare-from-context
4268-
.sort((a, b) => (!a.created || !b.created ? 0 : a.created.localeCompare(b.created)))
4269-
.at(0),
4270-
...(assigneeBadge ? {actionBadge: assigneeBadge} : {}),
4271-
};
4272-
}
4273-
4258+
// Compute IOU candidate upfront so we can compare timestamps with task candidate
42744259
const {reportAction: iouReportActionToApproveOrPay, actionBadge} = getIOUReportActionWithBadge(
42754260
optionOrReport,
42764261
policy,
@@ -4286,11 +4271,44 @@ function getReasonAndReportActionThatRequiresAttention(
42864271

42874272
// Has a child report that is awaiting action (e.g. approve, pay, add bank account) from current user
42884273
const hasStaleChildRequest = isTripRoom(optionOrReport) && (optionOrReport.transactionCount ?? 0) === 0;
4289-
4290-
if (
4274+
const hasValidIOUAction =
42914275
((optionOrReport.hasOutstandingChildRequest === true && !hasStaleChildRequest) || iouReportActionToApproveOrPay?.reportActionID) &&
4292-
(policy?.reimbursementChoice !== CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO || !hasOnlyPendingTransactions)
4293-
) {
4276+
(policy?.reimbursementChoice !== CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO || !hasOnlyPendingTransactions);
4277+
4278+
if (actionTypeForAssigneeToComplete) {
4279+
const isAssigneeExpenseAction = actionTypeForAssigneeToComplete === CONST.REPORT.ACTION_TYPES_FOR_ASSIGNEE_TO_COMPLETE.EXPENSE;
4280+
if (isAssigneeExpenseAction) {
4281+
const assigneeBadge = getBadgeFromIOUReport(optionOrReport, undefined, policy, optionReportMetadata, invoiceReceiverPolicy, currentUserLogin, currentUserAccountID);
4282+
return {
4283+
reason: CONST.REQUIRES_ATTENTION_REASONS.IS_WAITING_FOR_ASSIGNEE_TO_COMPLETE_ACTION,
4284+
...(assigneeBadge ? {actionBadge: assigneeBadge} : {}),
4285+
};
4286+
}
4287+
4288+
// Task badge candidate - compare with IOU candidate and pick the oldest report action
4289+
const oldestTaskAction = Object.values(reportActions)
4290+
.filter((action) => action.childType === CONST.REPORT.TYPE.TASK && !isTaskCompleted(action) && action.childManagerAccountID === deprecatedCurrentUserAccountID)
4291+
// eslint-disable-next-line rulesdir/prefer-locale-compare-from-context
4292+
.sort((a, b) => (!a.created || !b.created ? 0 : a.created.localeCompare(b.created)))
4293+
.at(0);
4294+
4295+
// If there's a valid IOU action that is older than the task, use the IOU badge instead
4296+
if (hasValidIOUAction && iouReportActionToApproveOrPay?.created && (!oldestTaskAction || iouReportActionToApproveOrPay.created < oldestTaskAction.created)) {
4297+
return {
4298+
reason: CONST.REQUIRES_ATTENTION_REASONS.HAS_CHILD_REPORT_AWAITING_ACTION,
4299+
reportAction: iouReportActionToApproveOrPay,
4300+
actionBadge,
4301+
};
4302+
}
4303+
4304+
return {
4305+
reason: CONST.REQUIRES_ATTENTION_REASONS.IS_WAITING_FOR_ASSIGNEE_TO_COMPLETE_ACTION,
4306+
reportAction: oldestTaskAction,
4307+
actionBadge: CONST.REPORT.ACTION_BADGE.TASK,
4308+
};
4309+
}
4310+
4311+
if (hasValidIOUAction) {
42944312
return {
42954313
reason: CONST.REQUIRES_ATTENTION_REASONS.HAS_CHILD_REPORT_AWAITING_ACTION,
42964314
reportAction: iouReportActionToApproveOrPay,

tests/unit/ReportUtilsTest.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9426,6 +9426,153 @@ describe('ReportUtils', () => {
94269426
expect(result?.reportAction?.reportActionID).toBe('current-user-task');
94279427
});
94289428

9429+
it('should return the IOU badge when the IOU action is older than the task action', async () => {
9430+
const iouReportID = 'iou-report-for-priority';
9431+
9432+
const taskAction: ReportAction = {
9433+
reportActionID: 'task-action-newer',
9434+
actionName: CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS,
9435+
childType: CONST.REPORT.TYPE.TASK,
9436+
childReportID: 'task-report-priority',
9437+
childManagerAccountID: currentUserAccountID,
9438+
created: '2024-01-02 00:00:00',
9439+
originalMessage: {
9440+
assigneeAccountID: currentUserAccountID,
9441+
cardID: 99001,
9442+
},
9443+
};
9444+
9445+
const iouAction: ReportAction = {
9446+
reportActionID: 'iou-action-older',
9447+
actionName: CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW,
9448+
childType: CONST.REPORT.TYPE.IOU,
9449+
childReportID: iouReportID,
9450+
childManagerAccountID: currentUserAccountID,
9451+
created: '2024-01-01 00:00:00',
9452+
message: [{html: 'iou preview', text: 'iou preview', type: 'COMMENT'}],
9453+
};
9454+
9455+
const workspaceChat = {
9456+
...createPolicyExpenseChat(42001),
9457+
hasOutstandingChildTask: true,
9458+
hasOutstandingChildRequest: true,
9459+
iouReportID,
9460+
};
9461+
9462+
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${workspaceChat.reportID}`, workspaceChat);
9463+
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${workspaceChat.reportID}`, {
9464+
[taskAction.reportActionID]: taskAction,
9465+
[iouAction.reportActionID]: iouAction,
9466+
});
9467+
await waitForBatchedUpdates();
9468+
9469+
const {result: isReportArchived} = renderHook(() => useReportIsArchived(workspaceChat.reportID));
9470+
const result = getReasonAndReportActionThatRequiresAttention(workspaceChat, currentUserEmail, currentUserAccountID, undefined, isReportArchived.current);
9471+
9472+
// The IOU action is older, so its badge should take priority over the task badge
9473+
expect(result?.reason).toBe(CONST.REQUIRES_ATTENTION_REASONS.HAS_CHILD_REPORT_AWAITING_ACTION);
9474+
expect(result?.reportAction?.reportActionID).toBe('iou-action-older');
9475+
expect(result?.actionBadge).toBe(CONST.REPORT.ACTION_BADGE.PAY);
9476+
});
9477+
9478+
it('should return the Task badge when the task action is older than the IOU action', async () => {
9479+
const iouReportID = 'iou-report-for-priority-2';
9480+
9481+
const taskAction: ReportAction = {
9482+
reportActionID: 'task-action-older',
9483+
actionName: CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS,
9484+
childType: CONST.REPORT.TYPE.TASK,
9485+
childReportID: 'task-report-priority-2',
9486+
childManagerAccountID: currentUserAccountID,
9487+
created: '2024-01-01 00:00:00',
9488+
originalMessage: {
9489+
assigneeAccountID: currentUserAccountID,
9490+
cardID: 99002,
9491+
},
9492+
};
9493+
9494+
const iouAction: ReportAction = {
9495+
reportActionID: 'iou-action-newer',
9496+
actionName: CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW,
9497+
childType: CONST.REPORT.TYPE.IOU,
9498+
childReportID: iouReportID,
9499+
childManagerAccountID: currentUserAccountID,
9500+
created: '2024-01-02 00:00:00',
9501+
message: [{html: 'iou preview', text: 'iou preview', type: 'COMMENT'}],
9502+
};
9503+
9504+
const workspaceChat = {
9505+
...createPolicyExpenseChat(42002),
9506+
hasOutstandingChildTask: true,
9507+
hasOutstandingChildRequest: true,
9508+
iouReportID,
9509+
};
9510+
9511+
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${workspaceChat.reportID}`, workspaceChat);
9512+
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${workspaceChat.reportID}`, {
9513+
[taskAction.reportActionID]: taskAction,
9514+
[iouAction.reportActionID]: iouAction,
9515+
});
9516+
await waitForBatchedUpdates();
9517+
9518+
const {result: isReportArchived} = renderHook(() => useReportIsArchived(workspaceChat.reportID));
9519+
const result = getReasonAndReportActionThatRequiresAttention(workspaceChat, currentUserEmail, currentUserAccountID, undefined, isReportArchived.current);
9520+
9521+
// The task action is older, so the task badge should take priority
9522+
expect(result?.reason).toBe(CONST.REQUIRES_ATTENTION_REASONS.IS_WAITING_FOR_ASSIGNEE_TO_COMPLETE_ACTION);
9523+
expect(result?.reportAction?.reportActionID).toBe('task-action-older');
9524+
expect(result?.actionBadge).toBe(CONST.REPORT.ACTION_BADGE.TASK);
9525+
});
9526+
9527+
it('should return the IOU badge when task action has no childManagerAccountID (backend bug before opening report)', async () => {
9528+
const iouReportID = 'iou-report-for-missing-manager';
9529+
9530+
// Task action without childManagerAccountID — simulates the backend bug
9531+
// where childManagerAccountID is missing before the report is opened
9532+
const taskAction: ReportAction = {
9533+
reportActionID: 'task-action-no-manager',
9534+
actionName: CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS,
9535+
childType: CONST.REPORT.TYPE.TASK,
9536+
childReportID: 'task-report-no-manager',
9537+
created: '2024-01-01 00:00:00',
9538+
originalMessage: {
9539+
assigneeAccountID: currentUserAccountID,
9540+
cardID: 99003,
9541+
},
9542+
};
9543+
9544+
const iouAction: ReportAction = {
9545+
reportActionID: 'iou-action-fallback',
9546+
actionName: CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW,
9547+
childType: CONST.REPORT.TYPE.IOU,
9548+
childReportID: iouReportID,
9549+
childManagerAccountID: currentUserAccountID,
9550+
created: '2024-01-02 00:00:00',
9551+
message: [{html: 'iou preview', text: 'iou preview', type: 'COMMENT'}],
9552+
};
9553+
9554+
const workspaceChat = {
9555+
...createPolicyExpenseChat(42003),
9556+
hasOutstandingChildTask: true,
9557+
hasOutstandingChildRequest: true,
9558+
iouReportID,
9559+
};
9560+
9561+
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${workspaceChat.reportID}`, workspaceChat);
9562+
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${workspaceChat.reportID}`, {
9563+
[taskAction.reportActionID]: taskAction,
9564+
[iouAction.reportActionID]: iouAction,
9565+
});
9566+
await waitForBatchedUpdates();
9567+
9568+
const {result: isReportArchived} = renderHook(() => useReportIsArchived(workspaceChat.reportID));
9569+
const result = getReasonAndReportActionThatRequiresAttention(workspaceChat, currentUserEmail, currentUserAccountID, undefined, isReportArchived.current);
9570+
9571+
// When oldestTaskAction is undefined (no childManagerAccountID), IOU badge should be returned
9572+
expect(result?.reason).toBe(CONST.REQUIRES_ATTENTION_REASONS.HAS_CHILD_REPORT_AWAITING_ACTION);
9573+
expect(result?.reportAction?.reportActionID).toBe('iou-action-fallback');
9574+
});
9575+
94299576
it('should return the earliest matching report action for invoice rooms with missing bank account', async () => {
94309577
const invoiceRoomID = '50000';
94319578
const olderChildReportID = '50001';

0 commit comments

Comments
 (0)