Skip to content

[$250] Hold - Approve modal displays same amount full report and pending expense #102333

Open

Description

@applause-bot

If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.


Version Number: 9.4.94-0
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught during regression testing, add the test name, ID and link from BrowserStack: https://test-management.browserstack.com/projects/2219752/test-runs/75702454/folder/13176872/41237335/1798269621?sort=%2Bstatus_id
Email or phone of affected tester (no customers): gocemate+workspaceapprover2207@gmail.com
Issue reported by: Applause Internal Team
Bug source: Regression TC Execution
Device used: windows 10/chrome
App Component: Other

Action Performed:

Precondition:
Owner invite approver and employee to the workspace

  1. As employee submit two expenses in workspace chat
  2. As employee click View button
    3 As employee open one of the expenses and put it on Hold
  3. As approver click on Approve button

Expected Result:

Approve modal should display amount for pending expense and total amount for full report

Actual Result:

Approve modal displays total amount for both pending expense and full report

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7269497_1790353272470.Recording__1734.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Svetlana Lazutkina
Bug ID: 7269497

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @amyevans

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Sep 25, 2026
  2. applause-bot commented on Sep 25, 2026

    @applause-bot
    Author

    While Applause doesn't embed attachments for this bug, here are the links for quick view:
    https://platform.applause.com/services/links/v1/external/4e847f2a253fb9b17e2fbe16d41937da05e410a7b1c85c855da85848d72bb5e6

  3. MelvinBot commented on Sep 25, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    The Approve options get both amounts from report-level totals that the server sends. They don't use the report's transactions. getNonHeldAndFullAmount reads:

    • "Approve only X" from report.unheldTotal
    • "Approve Y" from reimbursableTotal + nonReimbursableTotal

    The transactions are used for only one thing: to check whether any expense is held.

    The employee who places the hold gets updated unheldTotal values right away, from optimistic data in putOnHold. The approver doesn't. The approver does receive the transaction's hold, which is why the "Approve only" option appears. But their copy of the report still has unheldTotal equal to the full total, so both options show the same amount.

    Supporting details

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

    In getNonHeldAndFullAmount, calculate the held amount from allReportTransactions. Every caller already passes these, and the approver has them.

    1. Add up the amounts of held transactions in the report currency. Convert when a transaction's currency differs from the report's.
    2. For the pay branch, count only reimbursable held transactions.
    3. Set the non-held amount to the full total minus the held amount.
    4. Use the same helper for the partial approve and partial pay amounts, so the amount shown matches the amount submitted.
    5. Add a unit test. Use a report whose unheldTotal equals total and one held transaction, and check that the non-held amount excludes the held transaction.

    Separately, an internal engineer should check whether the backend sends updated unheld totals to other report participants after a hold.

    What alternative solutions did you explore? (Optional)

    Keep the report fields, but switch to the transaction-based amount only when a held expense exists and unheldTotal equals the total. This change is smaller, but it misses cases where the report fields are only partly stale.

    Possibly related: Spend - Approve modal inconsistency when approved form Spend and report. It involves the same approve-with-hold amounts, but a different trigger ($0 held expense).


    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/102333) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.


    view run

  4. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Sep 25, 2026
  5. melvin-bot commented on Sep 25, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @brunovjk (External)

  6. changed the title [-]Hold - Approve modal displays same amount full report and pending expense[/-] [+][$175] Hold - Approve modal displays same amount full report and pending expense[/+] on Sep 25, 2026
  7. melvin-bot commented on Sep 25, 2026

    @melvin-bot
  8. ashusatyarthi-wq commented on Sep 25, 2026

    @ashusatyarthi-wq

    Proposal

    What is the root cause of that problem?

    In src/libs/ReportUtils.ts within getNonHeldAndFullAmount:

    function getNonHeldAndFullAmount(
        iouReport: OnyxEntry<Report>,
        shouldExcludeNonReimbursables: boolean,
        allReportTransactions: Transaction[],
        convertToDisplayString: CurrencyListActionsContextType['convertToDisplayString'],
    ): NonHeldAndFullAmount {
        ...
        const reimbursableTotal = getReimbursableTotal(iouReport);
        const unheldReimbursableTotal = getUnheldReimbursableTotal(iouReport);
    
        let total: number;
        let unheldTotal: number;
        if (shouldExcludeNonReimbursables) {
            total = reimbursableTotal;
            unheldTotal = unheldReimbursableTotal;
        } else {
            total = reimbursableTotal + (iouReport?.nonReimbursableTotal ?? 0);
            unheldTotal = iouReport?.unheldTotal ?? unheldReimbursableTotal + (iouReport?.unheldNonReimbursableTotal ?? 0);
        }
    1. When a user puts an expense on hold, the hold status is immediately saved on the transaction object (transaction.comment.hold = true).
    2. In ExpenseHeaderApprovalButton.tsx, allReportTransactions is passed into getNonHeldAndFullAmount. hasHeldExpenses(allReportTransactions) detects the hold and causes the dropdown menu to appear.
    3. However, getNonHeldAndFullAmount computes unheldTotal strictly from iouReport?.unheldTotal or iouReport?.unheldReimbursableTotal. Because iouReport stored totals in Onyx are not yet updated with the new unheld split until the report re-computes on the backend, iouReport?.unheldTotal remains undefined or equal to the full total.
    4. As a result, unheldTotal defaults to the full report total, causing both dropdown options ("Approve only <amount>" and "Approve <amount>") in getApprovalDropdownOptions to display the identical full report amount.

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

    In src/libs/ReportUtils.ts inside getNonHeldAndFullAmount:
    When allReportTransactions contains held expenses, compute the unheld amounts directly from allReportTransactions (filtering transactions where !isHold(transaction)) as an optimistic fallback when iouReport?.unheldTotal is missing or equal to the full total:

    • Calculate unheldTransactionTotal from allReportTransactions.filter(t => !isHold(t)) (separating reimbursable vs non-reimbursable amounts).
    • Use this derived amount for unheldTotal and unheldReimbursableTotal when report-level unheld fields have not yet synced from the backend.

    This ensures the partial approval dropdown immediately displays the true pending (non-held) amount in sync with the user's optimistic hold actions.

    What alternative solutions did you explore? (Optional)

    We could also update iouReport.unheldTotal optimistically in the putOnHold action, but calculating the client fallback inside getNonHeldAndFullAmount using the already-provided allReportTransactions is cleaner, self-contained, and guarantees accurate display across all report types and viewers.

    Test Plan:

    1. As an employee, submit two expenses (e.g. $50 and $50) in a workspace report.
    2. Open one of the expenses and put it on Hold.
    3. As an approver, navigate to the report and click on the "Approve" button.
    4. Verify the dropdown displays:
      • "Approve only $50" (for the unheld pending expense).
      • "Approve $100" (for the full report total).
    5. Confirm clicking "Approve only" approves only the non-held amount.
  9. ashusatyarthi-wq commented on Sep 25, 2026

    @ashusatyarthi-wq

    Contributor details
    Your Expensify account email: ashusatyarthi@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~010e53044b5828fe60

  10. melvin-bot commented on Sep 25, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  11. hgreco85 commented on Sep 28, 2026

    @hgreco85

    Proposal

    What is the root cause of that problem?

    getNonHeldAndFullAmount builds the Approve dropdown amounts from report-level unheld* totals.

    The problem is that after an expense is put on hold, the approver can already receive the held state on the transaction, so the partial-approve option appears, while their copy of the report may still have stale unheldTotal / unheldReimbursableTotal values equal to the full total.

    That makes both options display the same amount.

    The same stale report fields are also used by the partial approve/pay paths, so this can affect not only the label shown to the user, but also the optimistic amount submitted.

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

    Keep preferring backend-computed unheld* fields when they are consistent, but add a fallback when held transactions prove those fields are stale.

    Instead of summing unheld transactions, derive the non-held amount as:

    full report total - sum(held transaction amounts in report currency)

    This is safer because some callers may have only a partial transaction list; summing the visible unheld transactions could therefore undercount the real pending amount.

    I would:

    1. Add a small shared numeric helper near getNonHeldAndFullAmount.
    2. Use the transaction-derived fallback only when:
      • held transactions exist, and
      • the report-level unheld value is missing or still equal to the full total.
    3. For held transactions:
      • use the report-currency amount / converted amount when needed;
      • for the Pay branch, subtract only reimbursable held transactions.
    4. Reuse the same helper in:
      • getNonHeldAndFullAmount
      • the partial approve path in ReportWorkflow
      • the partial pay path in PayMoneyRequest

    This keeps the displayed amount and submitted amount consistent without broadly replacing the backend totals logic.

    What alternative solutions did you explore?

    Always recalculating the unheld amount from all unheld transactions would be broader and can be incorrect when a caller has an incomplete transaction list.

    Updating only the optimistic unheld* fields in putOnHold also would not fully solve this case, because that optimistic update runs on the client that places the hold, not necessarily on the approver's client.

    Test Plan

    Add focused unit coverage for:

    1. Stale approver case: unheldTotal === fullTotal with one held and one unheld transaction → partial amount excludes the held expense.
    2. Backend already correct: existing reduced unheldTotal is preserved.
    3. Reimbursable vs non-reimbursable held transactions for the Pay path.
    4. Mixed-currency held transaction using its report-currency converted amount.
    5. All-held case keeps the partial option suppressed.

    Also verify that the same calculated partial amount is used by the approve/pay submission paths.

    Manual repro:

    • Employee submits two expenses and holds one.
    • Approver opens the Approve menu.
    • "Approve only X" shows the non-held amount and differs from "Approve Y".
    • Choosing the partial action submits that same amount.
  12. 71 remaining items

  13. open5588 commented on Oct 15, 2026

    @open5588

    🚨 Edited by proposal-police: This proposal was edited at 2026-10-15 20:08:36 UTC.

    Proposal

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

    When an employee submits multiple expenses on a workspace report and puts one of the expenses on hold, clicking the Approve button as an approver opens the partial-vs-full approval modal/dropdown where both the non-held ("pending expense") option and the full report option display the same full report amount, instead of displaying the non-held amount for pending expenses and the total amount for the full report.

    What is the root cause of that problem?

    In src/libs/ReportUtils.ts inside getNonHeldAndFullAmount (lines 12508–12553), the non-held amount is computed solely from the report-level fields iouReport?.unheldReimbursableTotal, iouReport?.unheldTotal, and iouReport?.unheldNonReimbursableTotal:

    // src/libs/ReportUtils.ts:12520-12531
    const reimbursableTotal = getReimbursableTotal(iouReport);
    const unheldReimbursableTotal = getUnheldReimbursableTotal(iouReport);
    
    let total: number;
    let unheldTotal: number;
    if (shouldExcludeNonReimbursables) {
        total = reimbursableTotal;
        unheldTotal = unheldReimbursableTotal;
    } else {
        total = reimbursableTotal + (iouReport?.nonReimbursableTotal ?? 0);
        unheldTotal = iouReport?.unheldTotal ?? unheldReimbursableTotal + (iouReport?.unheldNonReimbursableTotal ?? 0);
    }

    When an expense is placed on hold (putOnHold in src/libs/actions/IOU/Hold.ts) or when the report object in Onyx has not updated unheldTotal / unheldReimbursableTotal to subtract the held transaction's amount (or when unheldTotal still equals total while individual transactions in allReportTransactions already have comment.hold violation/status), getUnheldReimbursableTotal(iouReport) and iouReport?.unheldTotal still reflect the full report total (or 0 when unheldTotal is undefined). Furthermore, when unheldTotal equals total on a positive/IOU report, adjustedUnheldTotal >= 0 in hasValidNonHeldAmount (line 12544) still evaluates to true, causing the UI to render both options with the identical full report total.

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

    In src/libs/ReportUtils.ts inside getNonHeldAndFullAmount (lines 12508–12553):

    1. When hasHeldExpenses(allReportTransactions) is true and all report transactions share the report currency (or when iouReport?.unheldTotal === undefined / Math.abs(unheldTotal) >= Math.abs(total) despite held transactions existing in allReportTransactions), derive the unheld reimbursable and non-reimbursable totals directly from allReportTransactions by summing the non-held transactions (!isOnHoldTransactionUtils(transaction)).
    2. Ensure hasValidNonHeldAmount requires Math.abs(adjustedUnheldTotal) !== Math.abs(adjustedTotal) in all cases (not only negative expense reports) so the partial approval option is never shown with an amount equal to the full report amount.
    --- a/src/libs/ReportUtils.ts
    +++ b/src/libs/ReportUtils.ts
    @@ -12518,18 +12518,36 @@ function getNonHeldAndFullAmount(
         // sum of the underlying transactions, so deriving from reimbursableTotal/nonReimbursableTotal keeps
         // displayed amounts in sync with what the user actually entered.
         const reimbursableTotal = getReimbursableTotal(iouReport);
    -    const unheldReimbursableTotal = getUnheldReimbursableTotal(iouReport);
    +    let unheldReimbursableTotal = getUnheldReimbursableTotal(iouReport);
    +    const hasHeldExpensesLocal = hasHeldExpenses(allReportTransactions);
     
         let total: number;
         let unheldTotal: number;
         if (shouldExcludeNonReimbursables) {
             total = reimbursableTotal;
             unheldTotal = unheldReimbursableTotal;
         } else {
             total = reimbursableTotal + (iouReport?.nonReimbursableTotal ?? 0);
             unheldTotal = iouReport?.unheldTotal ?? unheldReimbursableTotal + (iouReport?.unheldNonReimbursableTotal ?? 0);
         }
     
    +    const canCalculateFromTransactions =
    +        allReportTransactions.length > 0 &&
    +        allReportTransactions.every((transaction) => transaction.currency === iouReport?.currency);
    +
    +    if (hasHeldExpensesLocal && canCalculateFromTransactions && (iouReport?.unheldTotal === undefined || Math.abs(unheldTotal) >= Math.abs(total))) {
    +        const nonHeldTransactions = allReportTransactions.filter((transaction) => !isOnHoldTransactionUtils(transaction));
    +        unheldTotal = nonHeldTransactions.reduce((sum, transaction) => {
    +            if (shouldExcludeNonReimbursables && !isTransactionReimbursable(transaction)) {
    +                return sum;
    +            }
    +            return sum + getAmount(transaction, isExpenseReport(iouReport));
    +        }, 0) * coefficient;
    +    }
    +
         const adjustedUnheldTotal = unheldTotal * coefficient;
         const adjustedTotal = total * coefficient;
     
    -    const hasHeldExpensesLocal = hasHeldExpenses(allReportTransactions);
         const hasValidNonHeldAmount =
             hasHeldExpensesLocal &&
    +        Math.abs(adjustedUnheldTotal) !== Math.abs(adjustedTotal) &&
             // For normal cases (positive amounts or IOU reports)
             (adjustedUnheldTotal >= 0 ||
                 // For expense reports with negative amounts, check if amounts are meaningful
    -            (isExpenseReport(iouReport) && Math.abs(adjustedUnheldTotal) > 0 && Math.abs(adjustedUnheldTotal) !== Math.abs(adjustedTotal)));
    +            (isExpenseReport(iouReport) && Math.abs(adjustedUnheldTotal) > 0));

    What alternative solutions did you explore? (Optional)

    We also considered updating unheldTotal and unheldReimbursableTotal solely inside the optimistic Onyx data of putOnHold / unholdRequest in src/libs/actions/IOU/Hold.ts. However, computing the transaction-level fallback in getNonHeldAndFullAmount (where allReportTransactions is already passed in) handles both optimistic holds and any cases where the report-level unheldTotal lags behind transaction state across clients.

  14. melvin-bot commented on Oct 15, 2026

    @melvin-bot

    📣 @open5588! 📣
    Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
    Please follow these steps:

    1. Make sure you've read and understood the contributing guidelines.
    2. Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
    3. Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
    4. Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
      Screen Shot 2022-11-16 at 4 42 54 PM
      Format:
    Contributor details
    Your Expensify account email: <REPLACE EMAIL HERE>
    Upwork Profile Link: <REPLACE LINK HERE>
    
  15. github-actions commented on Oct 15, 2026

    @github-actions
    Contributor

    ⚠️ @open5588 Thanks for your proposal. Please update it to follow the proposal template, as proposals are only reviewed if they follow that format (note the mandatory sections).

  16. RajGahoi commented on Oct 18, 2026

    @RajGahoi
  17. melvin-bot commented on Oct 18, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  18. github-actions commented on Oct 18, 2026

    @github-actions
    Contributor

    ⚠️ @RajGahoi Thanks for your interest. To be considered for this job, please post a proposal following the proposal template (note the mandatory sections). Comments claiming the job without one are not reviewed.

  19. jdymitarai commented on Oct 19, 2026

    @jdymitarai

    Upwork Profile: https://www.upwork.com/freelancers/~01f541511c3fcd21d9
    Expensify Account: o10040115@gmail.com

    Proposal

    What is the root cause of that problem?

    In getNonHeldAndFullAmount inside src/libs/ReportUtils.ts, calculating unheldTotal relies exclusively on iouReport.unheldTotal (or iouReport.unheldReimbursableTotal). When an expense is placed on hold and unheldTotal has not yet been updated in Onyx or still equals iouReport.total, unheldTotal defaults to the full report amount even though allReportTransactions is passed into the function.

    Additionally, in hasValidNonHeldAmount, the condition is (adjustedUnheldTotal >= 0 || ...). For expense reports, amounts are stored as negative numbers in Onyx and multiplied by coefficient = -1, resulting in a positive adjustedUnheldTotal. This causes adjustedUnheldTotal >= 0 to always short-circuit to true, making the second check Math.abs(adjustedUnheldTotal) !== Math.abs(adjustedTotal) completely unreachable dead code. As a result, stale unheld totals that match the full report amount are incorrectly considered valid and displayed in the modal.

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

    In src/libs/ReportUtils.ts, update getNonHeldAndFullAmount so that when hasHeldExpenses(allReportTransactions) is true and the report unheld total is missing or equal to total, we compute the fallback unheld total from allReportTransactions provided all active transactions match the report currency. Filter out transactions that are on hold (!TransactionUtils.isOnHold(transaction)), transactions pending deletion, and non-reimbursable expenses when shouldExcludeNonReimbursables is true, summing their effective amounts using TransactionUtils.getAmount.

    In hasValidNonHeldAmount, remove the short-circuiting adjustedUnheldTotal >= 0 branch and require that hasHeldExpensesLocal is true, adjustedUnheldTotal > 0, and Math.abs(adjustedUnheldTotal) !== Math.abs(adjustedTotal).

    Additionally, update putOnHold and unholdRequest in src/libs/actions/IOU to optimistically update iouReport.unheldTotal (and unheldReimbursableTotal when reimbursable) if the transaction currency matches the report currency, keeping Onyx synchronized across all views.

    What alternative solutions did you explore? (Optional)

    If we only fix the boolean condition in hasValidNonHeldAmount without deriving the unheld sum or optimistic updates, hasValidNonHeldAmount evaluates to false when unheldTotal equals total, which hides the amount next to the button entirely instead of showing the correct non-held sum.

    We also considered unconditionally deriving the unheld total client-side for all reports, but this would break multi-currency expense reports where foreign transaction amounts require server-side conversion rates.

  20. RajGahoi commented on Oct 19, 2026

    @RajGahoi

    FREELANCE PROPOSAL: Expensify Issue #102333

    Issue Link: #102333
    Bounty: $250 USD
    Upwork Freelancer Profile: https://www.upwork.com/freelancers/~014898525b9c53fc32
    Expensify Account Email: gahoiraj01@gmail.com


    Contributor Details

    • Your Expensify account email: gahoiraj01@gmail.com
    • Upwork Profile Link: https://www.upwork.com/freelancers/~014898525b9c53fc32

    Proposal

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

    In the Approve expense report modal, when an expense report contains a held expense, the modal displays the same full report amount for both the total report and the pending/non-held amount, instead of calculating and displaying the unheld non-pending amount.

    What is the root cause of that problem?

    In src/libs/ReportUtils.ts:

    1. Unreachable Validation Guard in hasValidNonHeldAmount:

      adjustedUnheldTotal >= 0 || (hasHeldExpensesLocal && Math.abs(adjustedUnheldTotal) !== Math.abs(adjustedTotal))

      For expense reports, amounts are multiplied by coefficient = -1, resulting in a non-negative adjustedUnheldTotal >= 0 whenever an unheld total exists. Because adjustedUnheldTotal >= 0 evaluates to true, the condition immediately short-circuits. The secondary check Math.abs(adjustedUnheldTotal) !== Math.abs(adjustedTotal) is never evaluated. Consequently, stale unheld totals that match the full report amount are erroneously treated as valid distinct non-held amounts and rendered in the modal.

    2. Missing Local Fallback Computation in getNonHeldAndFullAmount:
      When an expense is placed on hold and the optimistic iouReport.unheldTotal is missing or temporarily equals iouReport.total, getNonHeldAndFullAmount simply returns iouReport.unheldTotal without deriving the actual non-held sum from the provided allReportTransactions collection.

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

    1. Fix hasValidNonHeldAmount in src/libs/ReportUtils.ts:
      Remove the short-circuiting check and explicitly require that:

      • Held expenses exist on the report (hasHeldExpensesLocal).
      • adjustedUnheldTotal > 0.
      • The unheld amount is strictly different from the full report amount:
      - return adjustedUnheldTotal >= 0 || (hasHeldExpensesLocal && Math.abs(adjustedUnheldTotal) !== Math.abs(adjustedTotal));
      + return hasHeldExpensesLocal && adjustedUnheldTotal > 0 && Math.abs(adjustedUnheldTotal) !== Math.abs(adjustedTotal);
    2. Add Local Fallback Summation in getNonHeldAndFullAmount (src/libs/ReportUtils.ts):
      When hasHeldExpenses(allReportTransactions) is true and iouReport.unheldTotal === iouReport.total, iterate over allReportTransactions for single-currency reports, filtering out transactions where TransactionUtils.isOnHold(transaction) or isTransactionPendingDelete(transaction), and compute the active non-held total using TransactionUtils.getAmount.

    3. Optimistic Sync in Actions (src/libs/actions/IOU/index.ts):
      Ensure putOnHold and unholdRequest optimistically deduct/restore transaction amounts from iouReport.unheldTotal when the transaction currency matches the report currency.

    4. Automated Unit Tests in tests/unit/ReportUtilsTest.ts:

      • Verify hasValidNonHeldAmount returns false when unheldTotal === total even if non-held expenses exist.
      • Verify getNonHeldAndFullAmount correctly calculates the deducted amount when 1 of 2 transactions is held.

    What alternative solutions did you explore? (Optional)

    • Deriving unheld total entirely client-side unconditionally: Rejected because foreign multi-currency transactions require backend conversion rates. Deriving locally only as fallback for matching single-currency reports preserves currency accuracy.
  21. open5588 commented on Oct 19, 2026

    @open5588
  22. melvin-bot commented on Oct 19, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  23. github-actions commented on Oct 19, 2026

    @github-actions
    Contributor

    ⚠️ @open5588 Thanks for your interest. To be considered for this job, please post a proposal following the proposal template (note the mandatory sections). Comments claiming the job without one are not reviewed.

  24. open5588 commented on Oct 19, 2026

    @open5588
  25. melvin-bot commented on Oct 19, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  26. github-actions commented on Oct 19, 2026

    @github-actions
    Contributor

    ⚠️ @open5588 Thanks for your interest. To be considered for this job, please post a proposal following the proposal template (note the mandatory sections). Comments claiming the job without one are not reviewed.

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

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributorHelp WantedApply this label when an issue is open to proposals by contributorsOverdue

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions