Skip to content

Commit 09a7472

Browse files
Merge pull request Expensify#70627 from mohammadjafarinejad/fix/68966
Fix/68966 - [ORTF] report:enddate
2 parents 976c376 + 6325bd3 commit 09a7472

3 files changed

Lines changed: 99 additions & 6 deletions

File tree

src/libs/Formula.ts

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@ function computeReportPart(part: FormulaPart, context: FormulaContext): string {
248248
return formatType(report.type);
249249
case 'startdate':
250250
return formatDate(getOldestTransactionDate(report.reportID, context), format);
251+
case 'enddate':
252+
return formatDate(getNewestTransactionDate(report.reportID, context), format);
251253
case 'total':
252254
return formatAmount(report.total, getCurrencySymbol(report.currency ?? '') ?? report.currency);
253255
case 'currency':
@@ -468,6 +470,26 @@ function formatType(type: string | undefined): string {
468470
return typeMapping[type.toLowerCase()] || type;
469471
}
470472

473+
/**
474+
* Get all transactions for a report, including any context transaction.
475+
* Updates an existing transaction if it matches the context or adds it if new.
476+
*/
477+
function getAllReportTransactionsWithContext(reportID: string, context?: FormulaContext): Transaction[] {
478+
const transactions = [...getReportTransactions(reportID)];
479+
const contextTransaction = context?.transaction;
480+
481+
if (contextTransaction?.transactionID && contextTransaction.reportID === reportID) {
482+
const transactionIndex = transactions.findIndex((transaction) => transaction?.transactionID === contextTransaction.transactionID);
483+
if (transactionIndex >= 0) {
484+
transactions[transactionIndex] = contextTransaction;
485+
} else {
486+
transactions.push(contextTransaction);
487+
}
488+
}
489+
490+
return transactions;
491+
}
492+
471493
/**
472494
* Get the date of the oldest transaction for a given report
473495
*/
@@ -476,25 +498,26 @@ function getOldestTransactionDate(reportID: string, context?: FormulaContext): s
476498
return undefined;
477499
}
478500

479-
const transactions = getReportTransactions(reportID);
501+
const transactions = getAllReportTransactionsWithContext(reportID, context);
480502
if (!transactions || transactions.length === 0) {
481503
return new Date().toISOString();
482504
}
483505

484506
let oldestDate: string | undefined;
485507

486508
transactions.forEach((transaction) => {
487-
// Use updated transaction data if available and matches this transaction
488-
const currentTransaction = context?.transaction && transaction.transactionID === context.transaction.transactionID ? context.transaction : transaction;
489-
490-
const created = getCreated(currentTransaction);
509+
const created = getCreated(transaction);
491510
if (!created) {
492511
return;
493512
}
513+
// Skip transactions with pending deletion (offline deletes) to calculate dates properly.
514+
if (transaction.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
515+
return;
516+
}
494517
if (oldestDate && created >= oldestDate) {
495518
return;
496519
}
497-
if (isPartialTransaction(currentTransaction)) {
520+
if (isPartialTransaction(transaction)) {
498521
return;
499522
}
500523
oldestDate = created;
@@ -503,6 +526,42 @@ function getOldestTransactionDate(reportID: string, context?: FormulaContext): s
503526
return oldestDate;
504527
}
505528

529+
/**
530+
* Get the date of the newest transaction for a given report
531+
*/
532+
function getNewestTransactionDate(reportID: string, context?: FormulaContext): string | undefined {
533+
if (!reportID) {
534+
return undefined;
535+
}
536+
537+
const transactions = getAllReportTransactionsWithContext(reportID, context);
538+
if (!transactions || transactions.length === 0) {
539+
return new Date().toISOString();
540+
}
541+
542+
let newestDate: string | undefined;
543+
544+
transactions.forEach((transaction) => {
545+
const created = getCreated(transaction);
546+
if (!created) {
547+
return;
548+
}
549+
// Skip transactions with pending deletion (offline deletes) to calculate dates properly.
550+
if (transaction.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
551+
return;
552+
}
553+
if (newestDate && created <= newestDate) {
554+
return;
555+
}
556+
if (isPartialTransaction(transaction)) {
557+
return;
558+
}
559+
newestDate = created;
560+
});
561+
562+
return newestDate;
563+
}
564+
506565
export {FORMULA_PART_TYPES, compute, extract, parse};
507566

508567
export type {FormulaContext, FormulaPart};

src/libs/ReportUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6254,6 +6254,7 @@ function populateOptimisticReportFormula(formula: string, report: OptimisticExpe
62546254
// We don't translate because the server response is always in English
62556255
.replaceAll(/\{report:type\}/gi, 'Expense Report')
62566256
.replaceAll(/\{report:startdate\}/gi, createdDate ? format(createdDate, CONST.DATE.FNS_FORMAT_STRING) : '')
6257+
.replaceAll(/\{report:enddate\}/gi, createdDate ? format(createdDate, CONST.DATE.FNS_FORMAT_STRING) : '')
62576258
.replaceAll(/\{report:id\}/gi, getBase62ReportID(Number(report.reportID)))
62586259
.replaceAll(/\{report:total\}/gi, report.total !== undefined && !Number.isNaN(report.total) ? convertToDisplayString(Math.abs(report.total), report.currency).toString() : '')
62596260
.replaceAll(/\{report:currency\}/gi, report.currency ?? '')

tests/unit/FormulaTest.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ describe('CustomFormula', () => {
180180
expect(result).toBe('2025-01-08'); // Should use oldest transaction date (2025-01-08)
181181
});
182182

183+
test('should compute enddate formula using transactions', () => {
184+
const result = compute('{report:enddate}', mockContext);
185+
expect(result).toBe('2025-01-14'); // Should use newest transaction date (2025-01-14)
186+
});
187+
183188
test('should compute created formula using report actions', () => {
184189
const result = compute('{report:created}', mockContext);
185190
expect(result).toBe('2025-01-10'); // Should use oldest report action date (2025-01-10)
@@ -190,6 +195,11 @@ describe('CustomFormula', () => {
190195
expect(result).toBe('01/08/2025'); // Should use oldest transaction date with yyyy-MM-dd format
191196
});
192197

198+
test('should compute enddate with custom format', () => {
199+
const result = compute('{report:enddate:MM/dd/yyyy}', mockContext);
200+
expect(result).toBe('01/14/2025'); // Should use newest transaction date with MM/dd/yyyy format
201+
});
202+
193203
test('should compute created with custom format', () => {
194204
const result = compute('{report:created:MMMM dd, yyyy}', mockContext);
195205
expect(result).toBe('January 10, 2025'); // Should use oldest report action date with MMMM dd, yyyy format
@@ -200,6 +210,11 @@ describe('CustomFormula', () => {
200210
expect(result).toBe('08 Jan 2025'); // Should use oldest transaction date with dd MMM yyyy format
201211
});
202212

213+
test('should compute enddate with short month format', () => {
214+
const result = compute('{report:enddate:dd MMM yyyy}', mockContext);
215+
expect(result).toBe('14 Jan 2025'); // Should use newest transaction date with dd MMM yyyy format
216+
});
217+
203218
test('should compute policy name', () => {
204219
const result = compute('{report:policyname}', mockContext);
205220
expect(result).toBe('Test Policy');
@@ -366,6 +381,18 @@ describe('CustomFormula', () => {
366381
expect(result).toBe(expected);
367382
});
368383

384+
test('should handle missing transactions for enddate', () => {
385+
mockReportUtils.getReportTransactions.mockReturnValue([]);
386+
const context: FormulaContext = {
387+
report: {reportID: '123'} as Report,
388+
policy: null as unknown as Policy,
389+
};
390+
const today = new Date();
391+
const expected = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
392+
const result = compute('{report:enddate}', context);
393+
expect(result).toBe(expected);
394+
});
395+
369396
test('should call getReportTransactions with correct reportID for startdate', () => {
370397
const context: FormulaContext = {
371398
report: {reportID: 'test-report-123'} as Report,
@@ -416,6 +443,9 @@ describe('CustomFormula', () => {
416443

417444
const result = compute('{report:startdate}', context);
418445
expect(result).toBe('2025-01-12');
446+
447+
const endResult = compute('{report:enddate}', context);
448+
expect(endResult).toBe('2025-01-15');
419449
});
420450

421451
test('should skip partial transactions (zero amount)', () => {
@@ -449,6 +479,9 @@ describe('CustomFormula', () => {
449479

450480
const result = compute('{report:startdate}', context);
451481
expect(result).toBe('2025-01-12');
482+
483+
const endResult = compute('{report:enddate}', context);
484+
expect(endResult).toBe('2025-01-15');
452485
});
453486
});
454487
});

0 commit comments

Comments
 (0)