feat(importer): support YYYYMMDD date format (#470)
## Summary Adds a compact `YYYYMMDD` (no separators) date format to the transactions and balance importers. - New `DateFormat.YearMonthDayCompact = 'YYYYMMDD'` enum value - `parseDate` parses the 8-digit form (`20241231` → `2024-12-31`) - `autoDetectDateFormat` recognizes it (unambiguous, no separators) - Both mapping UIs (transactions + balances) expose it as a selectable option ## Testing - `bunx vitest run resources/js/lib/file-parser.test.ts` — 34 passed - Added tests: compact auto-detection + compact conversion - `bun run format`, `bun run lint` clean
This commit is contained in:
parent
d68fee6c2d
commit
f9d1303d98
|
|
@ -1696,6 +1696,7 @@
|
|||
"Works everywhere": "Funciona en todas partes",
|
||||
"Would you like to add more accounts or continue to the dashboard?": "¿Quieres agregar más cuentas o continuar al panel?",
|
||||
"YYYY-MM-DD (e.g., 2024-12-31)": "YYYY-MM-DD (ej., 2024-12-31)",
|
||||
"YYYYMMDD (e.g., 20241231)": "YYYYMMDD (ej., 20241231)",
|
||||
"Year": "Año",
|
||||
"Yearly": "Anual",
|
||||
"Yearly code": "Código anual",
|
||||
|
|
|
|||
|
|
@ -282,6 +282,19 @@ export function ImportBalanceStepMapping({
|
|||
{__('DD-MM-YYYY (e.g., 31-12-2024)')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem
|
||||
value={DateFormat.YearMonthDayCompact}
|
||||
id="format-ymd-compact"
|
||||
/>
|
||||
|
||||
<Label
|
||||
htmlFor="format-ymd-compact"
|
||||
className="cursor-pointer font-normal"
|
||||
>
|
||||
{__('YYYYMMDD (e.g., 20241231)')}
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -593,6 +593,19 @@ export function ImportStepMapping({
|
|||
{__('DD-MM-YYYY (e.g., 31-12-2024)')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem
|
||||
value={DateFormat.YearMonthDayCompact}
|
||||
id="format-ymd-compact"
|
||||
/>
|
||||
|
||||
<Label
|
||||
htmlFor="format-ymd-compact"
|
||||
className="cursor-pointer font-normal"
|
||||
>
|
||||
{__('YYYYMMDD (e.g., 20241231)')}
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,30 @@ describe('convertRowsToTransactions', () => {
|
|||
process.env.TZ = originalTimezone;
|
||||
}
|
||||
});
|
||||
|
||||
it('parses YYYYMMDD compact dates', () => {
|
||||
const transactions = convertRowsToTransactions(
|
||||
[
|
||||
{
|
||||
date: '20241231',
|
||||
description: 'New Year Eve',
|
||||
amount: '10.00',
|
||||
},
|
||||
],
|
||||
{
|
||||
transaction_date: 'date',
|
||||
description: 'description',
|
||||
amount: 'amount',
|
||||
balance: null,
|
||||
creditor_name: null,
|
||||
debtor_name: null,
|
||||
},
|
||||
DateFormat.YearMonthDayCompact,
|
||||
);
|
||||
|
||||
expect(transactions).toHaveLength(1);
|
||||
expect(transactions[0].transaction_date).toBe('2024-12-31');
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertRowsToTransactions balance column', () => {
|
||||
|
|
@ -279,6 +303,17 @@ describe('autoDetectDateFormat', () => {
|
|||
DateFormat.DayMonthYear,
|
||||
);
|
||||
});
|
||||
|
||||
it('detects YYYYMMDD compact format unambiguously', () => {
|
||||
const data = [
|
||||
{ date: '20240115' },
|
||||
{ date: '20240220' },
|
||||
{ date: '20240325' },
|
||||
];
|
||||
expect(autoDetectDateFormat(data, 'date')).toBe(
|
||||
DateFormat.YearMonthDayCompact,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLatestTransactionDate', () => {
|
||||
|
|
|
|||
|
|
@ -224,12 +224,14 @@ export function autoDetectDateFormat(
|
|||
DateFormat.YearMonthDay,
|
||||
DateFormat.DayMonthYear,
|
||||
DateFormat.MonthDayYear,
|
||||
DateFormat.YearMonthDayCompact,
|
||||
];
|
||||
const sampleSize = Math.min(10, data.length);
|
||||
const scores: Record<DateFormat, number> = {
|
||||
[DateFormat.YearMonthDay]: 0,
|
||||
[DateFormat.DayMonthYear]: 0,
|
||||
[DateFormat.MonthDayYear]: 0,
|
||||
[DateFormat.YearMonthDayCompact]: 0,
|
||||
};
|
||||
|
||||
for (let i = 0; i < sampleSize; i++) {
|
||||
|
|
@ -427,7 +429,14 @@ export function parseDate(
|
|||
month: number | undefined,
|
||||
day: number | undefined;
|
||||
|
||||
if (str.length === 5) {
|
||||
if (format === DateFormat.YearMonthDayCompact) {
|
||||
const compactArray = /^(\d{4})(\d{2})(\d{2})$/.exec(str);
|
||||
if (compactArray) {
|
||||
year = Number(compactArray[1]);
|
||||
month = Number(compactArray[2]);
|
||||
day = Number(compactArray[3]);
|
||||
}
|
||||
} else if (str.length === 5) {
|
||||
const dateRegex = /^(\d{1,2})-(\d{1,2})$/;
|
||||
const dateArray = dateRegex.exec(str);
|
||||
if (dateArray) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export enum DateFormat {
|
|||
YearMonthDay = 'YYYY-MM-DD',
|
||||
MonthDayYear = 'MM-DD-YYYY',
|
||||
DayMonthYear = 'DD-MM-YYYY',
|
||||
YearMonthDayCompact = 'YYYYMMDD',
|
||||
}
|
||||
|
||||
export interface ColumnMapping {
|
||||
|
|
|
|||
Loading…
Reference in New Issue