Repository navigation
[HOLD #104135] Show an error when a UK fuel-band rate has no valid VAT tax rate #104646
Description
(Neil's AI agent)
Part of the UK Advisory Fuel Rates tracking issue
Depends on: #104135, for the fuel-band series helper. Expensify/Expensify#694186 adds the dismiss command and must deploy first.
Design: Workspace VAT tax rate
Discussion: Slack thread
Problem
A UK fuel-band rate can end up with no valid VAT tax rate:
- the workspace's accounting integration has no 20% tax rate, so Auth adds the rate without one (Expensify/Expensify#694184);
- a sync or an admin removes or disables the linked tax rate.
Expenses on that rate then get no VAT, or the workspace default tax rate. Nothing tells the admin. The workspace's red dot checks don't cover distance rates (menu item). The rate page shows a blank tax rate.
Solution
Check
- Add a
PolicyUtilshelper that returns the workspace's enabled fuel-band rates whose tax rate isn't valid. It applies only when tax tracking and distance tax are on.- A fuel-band rate is one whose series, from UK fuel-band rates in the App's types, optimistic copy and lightning bolt check #104135's helper, isn't just the country.
- Use
isSelectableTaxCode, which treats an empty, deleted, disabled or pending-delete code as invalid. - Only admins see the error.
- It returns nothing when an admin has dismissed the error for this workspace (see Dismissing below).
Red dot path
- Workspace list row and Workspaces tab: add the check to
getPolicyBrickRoadIndicatorStatusand a new status inusePolicyIndicatorChecks. - Workspace menu: give the Distance rates item a
brickRoadIndicator. - Distance rates page: put the red dot on the More button and its Settings option.
Errors
- Settings page: show an error under Track tax.
- With an integration: "Please add a 20% tax rate in {integration name} to track tax on the UK Advisory Fuel Rates." Get the name from
getCurrentConnectionName. - Without one: "Please add a 20% tax rate to track tax on the UK Advisory Fuel Rates."
- With an integration: "Please add a 20% tax rate in {integration name} to track tax on the UK Advisory Fuel Rates." Get the name from
- Rate details page: on each affected rate, show a red dot and the same message on the tax rate field. Use the composable
MenuItemred dot and help text, likeCurrencyPicker. - Errors clear when Auth re-links the rate after a sync, or when the admin picks a tax rate.
Dismissing
Some admins may want fuel-band rates without VAT. They can dismiss the error.
- Both errors show a dismiss X, using
OfflineWithFeedback'sonClose. Dismissing either one hides the error, and every red dot leading to it, for every admin of that workspace. - Save it on the policy, as
customUnits[Distance].attributes.isUKFuelRateTaxErrorDismissed: true.- It's per workspace, not per rate, so next quarter's new rates don't bring the error back.
- It arrives with the policy, so the check still only needs the policy.
- App: a new
dismissUKFuelRateTaxErroraction, shaped likeenableDistanceRequestTax.- It calls the new
DismissUKFuelRateTaxErrorAPI command from Expensify/Expensify#694186, added asWRITE_COMMANDS.DISMISS_UK_FUEL_RATE_TAX_ERROR. - Its parameters match
EnableDistanceRequestTax:policyID, andcustomUnit, a JSON string withcustomUnitName,customUnitID, and the current attributes plusisUKFuelRateTaxErrorDismissed: true. - It sends optimistic and failure data.
- It calls the new
Tests
Unit tests for the helper:
- an empty, deleted, disabled or pending-delete tax code;
- a valid code;
- tax tracking or distance tax off;
- AMAP and non-UK rates;
- a non-admin;
- a dismissed workspace, and another workspace that isn't dismissed.
Add a test that dismissUKFuelRateTaxError sets the attribute optimistically and hides the error.
Extend getWorkspaceMenuItemsTest and usePolicyIndicatorChecksTest.
Deploys after merge. Fuel-band rates don't exist until Expensify/Expensify#692151.
Activity
neil-marcellini commentedon Oct 21, 2026
(Neil's AI agent)
@Krishna2323, with @thelullabyy as C+: Neil would love for you two to take this one. Krishna2323 built the Release 1 distance rate UI and the auto-update toggle. thelullabyy reviewed that work and is the C+ on #104135, which this builds on.
If you're interested, please comment here and Neil will assign you.
Krishna2323 commentedon Oct 21, 2026
Yes, interested!
neil-marcellini commentedon Oct 22, 2026
Sweet. I'm going to add a rule to my agent now to not automatically assume @thelullabyy always wants to be C+ 😆
neil-marcellini commentedon Oct 22, 2026
(Neil's AI agent)
@Krishna2323 heads up: after a Slack discussion I updated the error copy in the issue to "Please add a 20% tax rate in {integration name} to track tax on the UK Advisory Fuel Rates." Without an integration, it's "Please add a 20% tax rate to track tax on the UK Advisory Fuel Rates."
Krishna2323 commentedon Oct 22, 2026
@MelvinBot could you please post a proposal keep this in mind:
MelvinBot commentedon Oct 22, 2026
Proposal
Sequencing: This work is on hold until both of these land:
getGovernmentRateSeriesandisFuelBandGovernmentRatehelpers that this proposal uses.DismissUKFuelRateTaxErrorcommand deploys. The App PR can be built and reviewed before then, but it must not merge until the command is live.What is the root cause of that problem?
Nothing in the App checks whether a distance rate's tax code is still valid.
getPolicyBrickRoadIndicatorStatus,usePolicyIndicatorChecks, and the Distance rates menu item, which has nobrickRoadIndicator.taxRateExternalIDresolves to. If the code is empty, deleted or disabled, the field is blank.What changes do you think we should make in order to solve the problem?
Check helper. Add
getUKFuelRatesWithInvalidTax(policy)toPolicyUtils/errors.ts. Export it from@libs/PolicyUtils. It returns the distance unit's enabled rates where all of these are true:isFuelBandGovernmentRate(rate.attributes.governmentRate.sourceRateID)is true.!isSelectableTaxCode(policy, rate.attributes.taxRateExternalID).It returns an empty array in any of these cases:
policy.tax.trackingEnabledor the unit'sattributes.taxEnabledis off.attributes.isUKFuelRateTaxErrorDismissedis true.Add
shouldShowUKFuelRateTaxError(policy)as the boolean wrapper. The check reads onlypolicy, so errors clear on their own when a sync re-links the rate or the admin picks a tax rate.Red dots.
shouldShowUKFuelRateTaxErrortogetPolicyBrickRoadIndicatorStatusfor the workspace list row.CONST.INDICATOR_STATUS.HAS_UK_FUEL_RATE_TAX_ERRORand a tuple inusePolicyIndicatorChecksfor the Workspaces tab.brickRoadIndicator, the same way Taxes does.brickRoadIndicatoron the More button and on its Settings option. This copiesDomainMembersPage.Settings page error. Below the Track tax row, add an
OfflineWithFeedbackthat shows the error, withonCloseset to dismiss it.Please add a 20% tax rate in {integration name} to track tax on the UK Advisory Fuel Rates.Get the name fromgetCurrentConnectionName.Please add a 20% tax rate to track tax on the UK Advisory Fuel Rates.src/languages/*.tsfile underworkspace.distanceRates.Rate details error. If the rate is in the helper's result, build the tax field from composable parts, like
CurrencyPicker:MenuItem.Root,MenuItemField.Row,MenuItem.BrickRoadIndicator,MenuItem.Chevron, andMenuItem.HelpText isError. The existingOfflineWithFeedbackalso gets the dismissonClose.Dismiss action.
isUKFuelRateTaxErrorDismissed?: booleanto the custom unitAttributestype.WRITE_COMMANDS.DISMISS_UK_FUEL_RATE_TAX_ERROR: 'DismissUKFuelRateTaxError'andDismissUKFuelRateTaxErrorParams(policyID,customUnit).dismissUKFuelRateTaxError(policyID, customUnit)toPolicy.ts, shaped likeenableDistanceRequestTax.attributes.isUKFuelRateTaxErrorDismissed: trueand itspendingFieldsentry.errorFieldserror.customUnitparam is the JSON ofcustomUnitName,customUnitID, and the current attributes plus the flag.Tests.
PolicyUtilsTest, test the helper with each of these:PolicyTest, check thatdismissUKFuelRateTaxErrorsets the flag optimistically and the helper then returns nothing.getWorkspaceMenuItemsTestandusePolicyIndicatorChecksTest.What alternative solutions did you explore? (Optional)
You could dismiss the error per rate instead of per workspace. That would bring the error back each quarter when new band rates arrive, which the issue rules out.
Implementation notes
PolicyDistanceRatesUtilsimports./PolicyUtils. IfPolicyUtils/errors.tsimportsisFuelBandGovernmentRateback from it, the two files import each other. Both helpers are pure string functions, so I'd move them into a small leaf module that both files import.PolicyDistanceRatesUtilscan re-export them.isTaxTrackingEnabledneeds an expense-chat flag, so the helper readspolicy.tax.trackingEnabledand the unit'staxEnableddirectly.PolicyDistanceRatesSettingsPagedoes the same.MenuItem.HelpTexthas no close button. To get one, the message must also be in the wrappingOfflineWithFeedback'serrors, and then it shows twice. My suggestion: keep the red dot and help text on the field, and add the dismiss X only on the Settings page error. The alternative is to show the message once, as anOfflineWithFeedbackerror row under the field. Please confirm which you want.ButtonWithDropdownMenuwithshouldUseOptionIconand one option may show the gear icon instead of the red dot. I'll check this on web during testing.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/104646)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run