@@ -7,17 +7,8 @@ import {hasEnabledOptions} from '@libs/OptionsListUtils';
77import Permissions from '@libs/Permissions' ;
88import { getLoginByAccountID } from '@libs/PersonalDetailsUtils' ;
99import { getTagLists , isGroupPolicy , isMultiLevelTags , resolveCurrentTaxCode } from '@libs/PolicyUtils' ;
10- import { getIOUActionForTransactionID , isMoneyRequestAction } from '@libs/ReportActionsUtils' ;
11- import {
12- canEditFieldOfMoneyRequest ,
13- canEditMoneyRequest ,
14- canUserPerformWriteAction ,
15- findSelfDMReportID ,
16- isArchivedReport ,
17- isInvoiceReport ,
18- isIOUReport ,
19- shouldEnableNegative ,
20- } from '@libs/ReportUtils' ;
10+ import { isMoneyRequestAction } from '@libs/ReportActionsUtils' ;
11+ import { canEditFieldOfMoneyRequest , canEditMoneyRequest , canUserPerformWriteAction , isArchivedReport , isInvoiceReport , isIOUReport , shouldEnableNegative } from '@libs/ReportUtils' ;
2112import { hasEnabledTags } from '@libs/TagsOptionsListUtils' ;
2213import {
2314 calculateTaxAmount ,
@@ -55,11 +46,12 @@ import type {ValueOf} from 'type-fest';
5546/**
5647 * Actions for inline editing of transactions from the Search results table and the Expense Report page.
5748 *
58- * Each function delegates to the corresponding IOU action which owns the canonical Onyx record,
59- * the API write, failure rollback, and snapshot updates (when a hash is provided).
49+ * These functions are pure: every Onyx value they need (the transaction and violation
50+ * collections, the resolved reports/report action, session, betas, etc.) is passed in by
51+ * the caller (`useTransactionInlineEdit`), which reads it via `useOnyx`. Each function
52+ * delegates to the corresponding IOU action which owns the canonical Onyx record, the API
53+ * write, failure rollback, and snapshot updates (when a hash is provided).
6054 */
61- import Onyx from 'react-native-onyx' ;
62-
6355import {
6456 updateMoneyRequestAmountAndCurrency ,
6557 updateMoneyRequestCategory ,
@@ -79,64 +71,6 @@ type TransactionEditPermissions = {
7971 canEditTag : boolean ;
8072} ;
8173
82- let allTransactions : NonNullable < OnyxCollection < Transaction > > = { } ;
83- Onyx . connectWithoutView ( {
84- key : ONYXKEYS . COLLECTION . TRANSACTION ,
85- callback : ( value ) => {
86- allTransactions = value ?? { } ;
87- } ,
88- } ) ;
89-
90- let allTransactionViolations : NonNullable < OnyxCollection < TransactionViolations > > = { } ;
91- Onyx . connectWithoutView ( {
92- key : ONYXKEYS . COLLECTION . TRANSACTION_VIOLATIONS ,
93- callback : ( value ) => {
94- allTransactionViolations = value ?? { } ;
95- } ,
96- } ) ;
97-
98- let allReports : NonNullable < OnyxCollection < Report > > = { } ;
99- Onyx . connectWithoutView ( {
100- key : ONYXKEYS . COLLECTION . REPORT ,
101- callback : ( value ) => {
102- allReports = value ?? { } ;
103- } ,
104- } ) ;
105-
106- let allReportActions : NonNullable < OnyxCollection < ReportActions > > = { } ;
107- Onyx . connectWithoutView ( {
108- key : ONYXKEYS . COLLECTION . REPORT_ACTIONS ,
109- callback : ( value ) => {
110- allReportActions = value ?? { } ;
111- } ,
112- } ) ;
113-
114- let currentUserAccountID : number = CONST . DEFAULT_NUMBER_ID ;
115- let currentUserEmail = '' ;
116- Onyx . connectWithoutView ( {
117- key : ONYXKEYS . SESSION ,
118- callback : ( value ) => {
119- currentUserEmail = value ?. email ?? '' ;
120- currentUserAccountID = value ?. accountID ?? CONST . DEFAULT_NUMBER_ID ;
121- } ,
122- } ) ;
123-
124- let allBetas : Beta [ ] | undefined ;
125- Onyx . connectWithoutView ( {
126- key : ONYXKEYS . BETAS ,
127- callback : ( value ) => {
128- allBetas = value ?? undefined ;
129- } ,
130- } ) ;
131-
132- let introSelected : OnyxEntry < IntroSelected > ;
133- Onyx . connectWithoutView ( {
134- key : ONYXKEYS . NVP_INTRO_SELECTED ,
135- callback : ( value ) => {
136- introSelected = value ;
137- } ,
138- } ) ;
139-
14074const NO_EDIT : Readonly < TransactionEditPermissions > = Object . freeze ( {
14175 canEditDate : false ,
14276 canEditMerchant : false ,
@@ -201,6 +135,24 @@ type GetIouParamsInput = {
201135 isTrackIntentUser : boolean | undefined ;
202136 getCurrencyDecimals : CurrencyListActionsContextType [ 'getCurrencyDecimals' ] ;
203137 getCurrencySymbol : CurrencyListActionsContextType [ 'getCurrencySymbol' ] ;
138+
139+ /** The transaction being edited plus any of its duplicates, scoped by the caller. */
140+ transactions : OnyxCollection < Transaction > ;
141+
142+ /** Violations for the transaction being edited plus any of its duplicates, scoped by the caller. */
143+ transactionViolations : OnyxCollection < TransactionViolations > ;
144+
145+ /** Betas the current user has access to, used to gate ASAP submit behavior. */
146+ betas : Beta [ ] | undefined ;
147+
148+ /** Onboarding intro data, needed to build a transaction thread report when one doesn't exist yet. */
149+ introSelected : OnyxEntry < IntroSelected > ;
150+
151+ /** The current user's account ID. */
152+ currentUserAccountID : number ;
153+
154+ /** The current user's email/login. */
155+ currentUserEmail : string ;
204156} ;
205157
206158type TransactionInlineEditParams = GetIouParamsInput & {
@@ -236,51 +188,31 @@ function getIouParamsForTransaction({
236188 isTrackIntentUser,
237189 getCurrencyDecimals,
238190 getCurrencySymbol,
191+ transactionViolations,
192+ betas,
193+ introSelected,
194+ currentUserAccountID,
195+ currentUserEmail,
239196} : GetIouParamsInput ) {
240- const transactionViolations = allTransactionViolations [ `${ ONYXKEYS . COLLECTION . TRANSACTION_VIOLATIONS } ${ transactionID } ` ] ;
241- const isUnreportedExpense = ! transaction ?. reportID || transaction . reportID === CONST . REPORT . UNREPORTED_REPORT_ID ;
242-
243- let resolvedParentReport = parentReport ;
244- if ( ! resolvedParentReport ?. reportID && transaction ?. reportID && transaction . reportID !== CONST . REPORT . UNREPORTED_REPORT_ID ) {
245- resolvedParentReport = allReports [ `${ ONYXKEYS . COLLECTION . REPORT } ${ transaction . reportID } ` ] ;
246- }
247-
248- let resolvedParentReportAction = parentReportAction ;
249- if ( ! resolvedParentReportAction && resolvedParentReport ?. reportID ) {
250- const reportActions = allReportActions [ `${ ONYXKEYS . COLLECTION . REPORT_ACTIONS } ${ resolvedParentReport . reportID } ` ] ?? { } ;
251- resolvedParentReportAction = getIOUActionForTransactionID ( Object . values ( reportActions ) , transactionID ) ;
252- }
253-
254- if ( isUnreportedExpense ) {
255- const selfDMReportID = findSelfDMReportID ( allReports ) ;
256- if ( selfDMReportID ) {
257- resolvedParentReport = allReports [ `${ ONYXKEYS . COLLECTION . REPORT } ${ selfDMReportID } ` ] ?? resolvedParentReport ;
258-
259- if ( ! resolvedParentReportAction ) {
260- const selfDMReportActions = allReportActions [ `${ ONYXKEYS . COLLECTION . REPORT_ACTIONS } ${ selfDMReportID } ` ] ?? { } ;
261- resolvedParentReportAction = getIOUActionForTransactionID ( Object . values ( selfDMReportActions ) , transactionID ) ;
262- }
263- }
264- }
197+ // transaction is passed in by the caller; only the scoped violations are derived here for the thread-report build.
198+ const transactionViolationsForTransaction = transactionViolations ?. [ `${ ONYXKEYS . COLLECTION . TRANSACTION_VIOLATIONS } ${ transactionID } ` ] ;
265199
200+ // parentReport (already resolved to the self DM for unreported expenses), parentReportAction, and
201+ // transactionThreadReport are resolved by the caller (useTransactionInlineEdit) via useOnyx, so they
202+ // are used directly here. The only remaining resolution is building a transaction thread report when
203+ // one doesn't exist in Onyx yet.
266204 let resolvedTransactionThreadReport = transactionThreadReport ;
267- const transactionThreadReportID = resolvedTransactionThreadReport ?. reportID ?? transaction ?. transactionThreadReportID ?? resolvedParentReportAction ?. childReportID ;
268-
269- if ( ! resolvedTransactionThreadReport && transactionThreadReportID ) {
270- resolvedTransactionThreadReport = allReports [ `${ ONYXKEYS . COLLECTION . REPORT } ${ transactionThreadReportID } ` ] ;
271- }
272-
273- if ( ! resolvedTransactionThreadReport && resolvedParentReportAction && transaction ) {
205+ if ( ! resolvedTransactionThreadReport && parentReportAction && transaction ) {
274206 resolvedTransactionThreadReport = createTransactionThreadReport ( {
275207 introSelected,
276208 conciergeChat,
277209 currentUserLogin : currentUserEmail ,
278210 currentUserAccountID,
279- betas : allBetas ,
280- iouReport : resolvedParentReport ,
281- iouReportAction : resolvedParentReportAction ,
211+ betas,
212+ iouReport : parentReport ,
213+ iouReportAction : parentReportAction ,
282214 transaction,
283- transactionViolations : transactionViolations ?? undefined ,
215+ transactionViolations : transactionViolationsForTransaction ?? undefined ,
284216 personalDetails : personalDetailsList ,
285217 isSelfTourViewed,
286218 hasCompletedGuidedSetupFlow,
@@ -290,14 +222,14 @@ function getIouParamsForTransaction({
290222 return {
291223 transactionID,
292224 transactionThreadReport : resolvedTransactionThreadReport ,
293- parentReport : resolvedParentReport ,
294- iouReportOwnerLogin : getLoginByAccountID ( resolvedParentReport ?. ownerAccountID , personalDetailsList ) ,
225+ parentReport,
226+ iouReportOwnerLogin : getLoginByAccountID ( parentReport ?. ownerAccountID , personalDetailsList ) ,
295227 policy,
296228 policyForTrackExpense,
297229 policyCategories,
298230 currentUserAccountIDParam : currentUserAccountID ,
299231 currentUserEmailParam : currentUserEmail ,
300- isASAPSubmitBetaEnabled : Permissions . isBetaEnabled ( CONST . BETAS . ASAP_SUBMIT , allBetas ) ,
232+ isASAPSubmitBetaEnabled : Permissions . isBetaEnabled ( CONST . BETAS . ASAP_SUBMIT , betas ) ,
301233 delegateAccountID,
302234 isTrackIntentUser,
303235 getCurrencyDecimals,
@@ -320,8 +252,8 @@ function editTransactionDateInline(params: TransactionInlineEditParams, newDate:
320252 // updateMoneyRequestDate uses 'policyTags' (not policyTagList)
321253 policyTags : iouParams . policyTagList ,
322254 value : newDate ,
323- transactions : allTransactions ,
324- transactionViolations : allTransactionViolations ,
255+ transactions : params . transactions ,
256+ transactionViolations : params . transactionViolations ,
325257 isOffline : params . isOffline ,
326258 hash : params . hash ,
327259 distanceOriginalPolicy : params . distanceOriginalPolicy ,
@@ -331,6 +263,9 @@ function editTransactionDateInline(params: TransactionInlineEditParams, newDate:
331263
332264/** Updates the merchant of an expense from the Search results table or the Expense Report page. */
333265function editTransactionMerchantInline ( params : TransactionInlineEditParams , newMerchant : string ) {
266+ // Validate before building iouParams: getIouParamsForTransaction can call createTransactionThreadReport,
267+ // which fires Onyx.merge + openReport (an API call). Building params first would optimistically create a
268+ // thread report for an edit we're about to discard, so validate first.
334269 if ( ! isValidMerchant ( newMerchant , params . transaction , params . parentReport ) ) {
335270 return ;
336271 }
@@ -367,15 +302,19 @@ function editTransactionCategoryInline(params: TransactionInlineEditParams, newC
367302
368303/** Updates the amount and currency of an expense from the Search results table or the Expense Report page. */
369304function editTransactionAmountInline ( params : TransactionInlineEditParams , newAmount : number ) {
370- const iouParams = getIouParamsForTransaction ( params ) ;
305+ // Validate before building iouParams: getIouParamsForTransaction can call createTransactionThreadReport,
306+ // which fires Onyx.merge + openReport (an API call). Building params first would optimistically create a
307+ // thread report for an edit we're about to discard, so validate against params directly first.
371308 const iouType = isInvoiceReport ( params . parentReport ) ? CONST . IOU . TYPE . INVOICE : CONST . IOU . TYPE . SUBMIT ;
372- const allowNegative = shouldEnableNegative ( params . parentReport , iouParams . policy , iouType ) ;
309+ const allowNegative = shouldEnableNegative ( params . parentReport , params . policy , iouType ) ;
373310 const isP2P = isIOUReport ( params . parentReport ) ;
374311
375312 if ( ! isValidMoneyRequestAmount ( newAmount , iouType , allowNegative , isP2P ) ) {
376313 return ;
377314 }
378315
316+ const iouParams = getIouParamsForTransaction ( params ) ;
317+
379318 // Keep the existing currency — only the amount is changing from the search table
380319 const currency = iouParams . transaction ?. modifiedCurrency ?? iouParams . transaction ?. currency ?? CONST . CURRENCY . USD ;
381320 // Recalculate tax from the existing tax code and the new amount
@@ -391,8 +330,8 @@ function editTransactionAmountInline(params: TransactionInlineEditParams, newAmo
391330 taxCode,
392331 taxValue : taxPercentage ,
393332 allowNegative,
394- transactions : allTransactions ,
395- transactionViolations : allTransactionViolations ,
333+ transactions : params . transactions ,
334+ transactionViolations : params . transactionViolations ,
396335 policyRecentlyUsedCurrencies : [ ] ,
397336 hash : params . hash ,
398337 } ) ;
0 commit comments