fix(accounting): emit ISO dates in exports (Postgres returns Date objects)

The Date column imported empty into Banana because dateOnly() did
String(d).slice(0,10) — on Postgres the date columns come back as JS Date
objects, so that yields "Thu Jan 15" instead of "2026-01-15", which Banana
rejects. (SQLite returns strings, so the tests never caught it — the
pg-date-serialisation trap.)

- ledgerService.dateOnly + taxReportService CSV now format Date objects to
  yyyy-mm-dd via local calendar parts (DATE columns are local-midnight).
- Regression test added with a real Date object (the existing tests all used
  string dates).
This commit is contained in:
Luca
2026-06-15 23:01:20 +02:00
parent 445d6d7b6d
commit 0c0fb29770
3 changed files with 39 additions and 2 deletions
+14 -1
View File
@@ -390,7 +390,20 @@ function csvEscape(cell) {
return `"${s.replace(/"/g, '""')}"`;
}
function minorToDecimal(m) { return ((Number(m) || 0) / 100).toFixed(2); }
function dateOnly(d) { return String(d || '').slice(0, 10); }
// yyyy-mm-dd, robust to Postgres returning DATE/TIMESTAMP columns as JS Date
// objects (SQLite returns strings). String(dateObj).slice(0,10) yields
// "Thu Jan 15", which Banana / accounting tools reject — so format the calendar
// parts explicitly. Uses local parts (DATE columns come back at local midnight).
function dateOnly(d) {
if (!d) return '';
if (d instanceof Date) {
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
}
return String(d).slice(0, 10);
}
const EXPORT_FORMATS = ['generic', 'banana', 'banana_ie', 'bexio'];
+11 -1
View File
@@ -978,6 +978,16 @@ async function renderTaxReportCsv({ from, to, currency, locale } = {}) {
const minorToDotDecimal = (m) => ((Number(m) || 0) / 100).toFixed(2);
// yyyy-mm-dd, robust to Postgres returning dates as JS Date objects (SQLite
// returns strings) — raw String(dateObj) is "Thu Jan 15", not an ISO date.
const isoDate = (d) => {
if (!d) return '';
if (d instanceof Date) {
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}
return String(d).slice(0, 10);
};
const typeLabelKey = (type) => (
type === 'outgoing' ? 'tax_type_outgoing'
: type === 'incoming' ? 'tax_type_incoming'
@@ -1013,7 +1023,7 @@ async function renderTaxReportCsv({ from, to, currency, locale } = {}) {
lines.push([
i + 1,
t(useLocale, typeLabelKey(row.type)),
row.date,
isoDate(row.date),
reference,
row.party,
row.eventName,