@@ -166,6 +166,7 @@ import {
166166 isPendingDeletePolicy,
167167 isPolicyAdmin as isPolicyAdminPolicyUtils,
168168 isPolicyAuditor,
169+ isPolicyFieldListEmpty,
169170 isPolicyMember,
170171 isPolicyMemberWithoutPendingDelete,
171172 isPolicyOwner,
@@ -993,6 +994,24 @@ Onyx.connectWithoutView({
993994// Example case: when we need to get a report name of a thread which is dependent on a report action message.
994995const parsedReportActionMessageCache: Record<string, string> = {};
995996
997+ /**
998+ * Fallback title field used when a policy has an empty fieldList (matches OldDot behavior).
999+ */
1000+ const FALLBACK_TITLE_FIELD: PolicyReportField = {
1001+ fieldID: CONST.REPORT_FIELD_TITLE_FIELD_ID,
1002+ name: CONST.POLICY.DEFAULT_FIELD_LIST_NAME,
1003+ type: CONST.REPORT_FIELD_TYPES.TEXT,
1004+ defaultValue: CONST.REPORT.DEFAULT_EXPENSE_REPORT_NAME,
1005+ deletable: true,
1006+ target: CONST.POLICY.DEFAULT_FIELD_LIST_TARGET,
1007+ values: [],
1008+ keys: [],
1009+ externalIDs: [],
1010+ disabledOptions: [],
1011+ orderWeight: 0,
1012+ isTax: false,
1013+ };
1014+
9961015let conciergeReportIDOnyxConnect: OnyxEntry<string>;
9971016Onyx.connect({
9981017 key: ONYXKEYS.CONCIERGE_REPORT_ID,
@@ -4427,6 +4446,22 @@ function getTitleReportField(reportFields: Record<string, PolicyReportField>) {
44274446 return Object.values(reportFields).find((field) => isReportFieldOfTypeTitle(field));
44284447}
44294448
4449+ /**
4450+ * Gets the title field from a policy, with a fallback when the policy fieldList is empty (matches OldDot behavior).
4451+ */
4452+ function getTitleFieldWithFallback(policy: OnyxEntry<Policy>): PolicyReportField | undefined {
4453+ const policyTitleField = policy?.fieldList?.[CONST.POLICY.FIELDS.FIELD_LIST_TITLE];
4454+ if (policyTitleField) {
4455+ return policyTitleField;
4456+ }
4457+
4458+ if (isPolicyFieldListEmpty(policy)) {
4459+ return FALLBACK_TITLE_FIELD;
4460+ }
4461+
4462+ return undefined;
4463+ }
4464+
44304465/**
44314466 * Get the key for a report field
44324467 */
@@ -6579,6 +6614,30 @@ function buildOptimisticInvoiceReport(
65796614 return invoiceReport;
65806615}
65816616
6617+ /**
6618+ * Computes the optimistic report name using the policy's title field formula, with a fallback to the default expense report name.
6619+ */
6620+ function computeOptimisticReportName(report: Report, policy: OnyxEntry<Policy>, policyID: string | undefined, reportTransactions: Record<string, Transaction>): string | null {
6621+ if (!isGroupPolicy(policy?.type ?? '')) {
6622+ return null;
6623+ }
6624+
6625+ const titleReportField = getTitleReportField(getReportFieldsByPolicyID(policyID) ?? {});
6626+ const formulaContext: FormulaContext = {
6627+ report,
6628+ policy,
6629+ allTransactions: reportTransactions,
6630+ };
6631+
6632+ // We use dynamic require here to avoid a circular dependency between ReportUtils and Formula
6633+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
6634+ const Formula = require('./Formula') as {compute: (formula?: string, context?: FormulaContext) => string};
6635+
6636+ // If there is no title field, use "New Report" as default (matches OldDot behavior)
6637+ const defaultValue = titleReportField?.defaultValue ?? CONST.REPORT.DEFAULT_EXPENSE_REPORT_NAME;
6638+ return Formula.compute(defaultValue, formulaContext);
6639+ }
6640+
65826641/**
65836642 * Returns the stateNum and statusNum for an expense report based on the policy settings
65846643 * @param policy
@@ -6688,20 +6747,12 @@ function buildOptimisticExpenseReport({
66886747 expenseReport.managerID = submitToAccountID;
66896748 }
66906749
6691- const titleReportField = getTitleReportField(getReportFieldsByPolicyID(policyID) ?? {});
6692- if (!!titleReportField && isGroupPolicy(policy?.type ?? '')) {
6693- const formulaContext: FormulaContext = {
6694- report: expenseReport,
6695- policy,
6696- allTransactions: reportTransactions ?? {},
6697- };
6698-
6699- // We use dynamic require here to avoid a circular dependency between ReportUtils and Formula
6700- // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
6701- const Formula = require('./Formula') as {compute: (formula?: string, context?: FormulaContext) => string};
6702- const computedName = Formula.compute(titleReportField.defaultValue, formulaContext);
6703- expenseReport.reportName = computedName ?? expenseReport.reportName;
6750+ // Compute optimistic report name if applicable
6751+ const computedName = computeOptimisticReportName(expenseReport, policy, policyID, reportTransactions ?? {});
6752+ if (computedName !== null) {
6753+ expenseReport.reportName = computedName;
67046754 }
6755+ // Otherwise, keep the default format: `${policyName} owes ${formattedTotal}`
67056756
67066757 expenseReport.fieldList = policy?.fieldList;
67076758
@@ -6718,7 +6769,6 @@ function buildOptimisticEmptyReport(
67186769 betas: OnyxEntry<Beta[]>,
67196770) {
67206771 const {stateNum, statusNum} = getExpenseReportStateAndStatus(policy, betas, true);
6721- const titleReportField = getTitleReportField(getReportFieldsByPolicyID(policy?.id) ?? {});
67226772 const optimisticEmptyReport: OptimisticNewReport = {
67236773 reportName: '',
67246774 reportID,
@@ -6740,17 +6790,11 @@ function buildOptimisticEmptyReport(
67406790 managerID: getManagerAccountID(policy, {ownerAccountID: accountID}),
67416791 };
67426792
6743- const formulaContext: FormulaContext = {
6744- report: optimisticEmptyReport as Report,
6745- policy,
6746- allTransactions: {},
6747- };
6748-
6749- // We use dynamic require here to avoid a circular dependency between ReportUtils and Formula
6750- // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
6751- const Formula = require('./Formula') as {compute: (formula?: string, context?: FormulaContext) => string};
6752- const optimisticReportName = Formula.compute(titleReportField?.defaultValue ?? CONST.POLICY.DEFAULT_REPORT_NAME_PATTERN, formulaContext);
6753- optimisticEmptyReport.reportName = optimisticReportName ?? '';
6793+ // Compute optimistic report name if applicable
6794+ const optimisticReportName = computeOptimisticReportName(optimisticEmptyReport as Report, policy, policy?.id, {});
6795+ if (optimisticReportName !== null) {
6796+ optimisticEmptyReport.reportName = optimisticReportName;
6797+ }
67546798
67556799 optimisticEmptyReport.participants = accountID
67566800 ? {
@@ -13119,6 +13163,7 @@ export {
1311913163 isSelectedManagerMcTest,
1312013164 isTestTransactionReport,
1312113165 getReportSubtitlePrefix,
13166+ computeOptimisticReportName,
1312213167 getPolicyChangeMessage,
1312313168 getMovedTransactionMessage,
1312413169 getUnreportedTransactionMessage,
@@ -13128,6 +13173,7 @@ export {
1312813173 isBusinessInvoiceRoom,
1312913174 buildOptimisticResolvedDuplicatesReportAction,
1313013175 getTitleReportField,
13176+ getTitleFieldWithFallback,
1313113177 getReportFieldsByPolicyID,
1313213178 getChatListItemReportName,
1313313179 buildOptimisticMovedTransactionAction,
0 commit comments