Skip to content

Expense-Tax rate modified violation appears after merging expenses & disappears on navigation #86306

Closed

Description

@nlemma

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Version Number: V9.3.42-3
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #85658
Email or phone of affected tester (no customers): dreamnitethedragon+yydyd373@gmail.com
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Redminote 10S Android 13
App Component: Money Requests

Action Performed:

  1. Launch app
  2. Create workspace
  3. Enable taxes
  4. Create 4 manual expenses without category
    Eg: ₹5 - tax 0%
    ₹6 - tax 5%
    ₹7 - tax 0%
    ₹8 - tax tax 5%
  5. Open the expense with amount ₹5 and merge with expense ₹6 selecting all second options in merge flow
  6. Note merged expense and another expense with tax rate 5% shows tax rate was modified violation
  7. Open the expense and navigate back and note violation disappears

Case 2

  1. Launch app
  2. Create workspace
  3. Enable taxes
  4. Create 4 manual expenses with category
    Eg: ₹5 - tax 0%
    ₹6 - tax 5%
    ₹7 - tax 0%
    ₹8 - tax tax 5%
  5. Open the expense with amount ₹5 and merge with expense ₹6 selecting all second options in merge flow
  6. Note merged expense and another expense with tax rate 5% shows tax rate was modified violation
  7. Open the expense and navigate back and note violation

Expected Result:

The violation should only appear if there is a real tax issue.

Unrelated expenses should not show the violation.

The violation should not disappear on navigation and must remain until resolved.

Actual Result:

After merging ₹5 (0% tax) with ₹6 (5% tax):
A “tax rate was modified” violation appears on the merged expense.

The same violation also appears on another expense with 5% tax.

When the expense( without category) is opened and navigated back, the violation disappears automatically.

Case 2

When the expense( with category) is opened and navigated back, the violation persist.

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome / Safari

Screenshots/Videos

Bug7112403_1774409262146.Screenrecorder-2026-03-25-08-39-03-332.mp4
Bug7112403_1774427926697.Screenrecorder-2026-03-25-14-02-55-177.mp4

View all open jobs on GitHub

Activity

  1. added a commit that references this issue on Mar 25, 2026
    32726d8
  2. wildan-m commented on Mar 25, 2026

    @wildan-m
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    After merging two expenses with different tax rates, the merged expense shows a "tax rate was modified" violation that disappears on navigation. Additionally, unrelated expenses may also show spurious violations.

    What is the root cause of that problem?

    There are two bugs in mergeTransactionRequest in MergeTransaction.ts:

    Bug 1 — Target transaction violations are duplicated by optimistic data ordering

    optimisticTransactionViolations writes violations for BOTH the target and source transactions using Onyx.METHOD.MERGE:

    // Optimistic delete duplicated transaction violations
    const optimisticTransactionViolations: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS>> = [targetTransaction.transactionID, sourceTransaction.transactionID].map(
    (id) => {
    const violations = allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + id] ?? [];
    return {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${id}`,
    value: violations.filter((violation) => violation.name !== CONST.VIOLATIONS.DUPLICATED_TRANSACTION),
    };
    },
    );

    However, the target transaction's violations are already computed by getViolationsOnyxData (called from getUpdateMoneyRequestParams) using Onyx.METHOD.SET:

    return {
    onyxMethod: Onyx.METHOD.SET,
    key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${updatedTransaction.transactionID}`,
    value: newTransactionViolations,
    };

    When API.write calls Onyx.update(), both operations target the same key. Due to Onyx v3's batching, SET enqueues [null, computed] and then MERGE pushes the original violations to the same queue — resulting in [null, computed, original]. When mergeAndMarkChanges processes this queue, it concatenates the arrays, causing duplicate violation entries on the target transaction.

    https://github.com/nickenbach/react-native-onyx/blob/8c0a72f3d27e49daf5a2aaaba1aff0c4cd01c47d/lib/Onyx.ts#L411-L424

    These duplicated violations appear immediately after merge and disappear on the next navigation because the server response overwrites them with the correct violations.

    Bug 2 — Source transaction violations are orphaned

    The source transaction itself is SET to null (deleted), but its violations are only MERGEd (filtering out DUPLICATED_TRANSACTION) rather than being deleted. This leaves orphaned violation data in ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS for a transaction that no longer exists.

    What changes do you think we should make in order to solve the problem?

    1. Remove the target transaction from optimisticTransactionViolations entirely — let getViolationsOnyxData (via getUpdateMoneyRequestParams) handle target violations exclusively with its SET operation, avoiding the SET+MERGE concatenation.

    2. Change the source transaction's violations to use Onyx.METHOD.SET with null value instead of MERGE, properly cleaning up violations for the deleted transaction. Add corresponding failure data that restores the source violations for rollback.

    Compare: https://github.com/Expensify/App/compare/main...wildan-m:App:wildan/86306-fix-merge-violation-ordering?expand=1

  3. melvin-bot commented on Mar 30, 2026

    @melvin-bot

    Huh... This is 4 days overdue. Who can take care of this?

  4. melvin-bot commented on Mar 30, 2026

    @melvin-bot

    Eep! 4 days overdue now. Issues have feelings too...

  5. melvin-bot commented on Mar 30, 2026

    @melvin-bot

    Huh... This is 4 days overdue. Who can take care of this?

  6. melvin-bot commented on Apr 1, 2026

    @melvin-bot

    Still overdue 6 days?! Let's take care of this!

  7. melvin-bot commented on Apr 3, 2026

    @melvin-bot

    Now this issue is 8 days overdue. Are you sure this should be a Daily? Feel free to change it!

  8. melvin-bot commented on Apr 7, 2026

    @melvin-bot

    12 days overdue now... This issue's end is nigh!

  9. melvin-bot commented on Apr 10, 2026

    @melvin-bot

    This issue has not been updated in over 14 days. eroding to Weekly issue.

  10. melvin-bot commented on May 7, 2026

    @melvin-bot

    This issue has not been updated in over 15 days. eroding to Monthly issue.

    P.S. Is everyone reading this sure this is really a near-term priority? Be brave: if you disagree, go ahead and close it out. If someone disagrees, they'll reopen it, and if they don't: one less thing to do!

  11. melvin-bot commented on Jul 20, 2026

    @melvin-bot

    @nlemma, this Monthly task hasn't been acted upon in 6 weeks; closing.

    If you disagree, feel encouraged to reopen it -- but pick your least important issue to close instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions