From 2a98cce8434f7ea27f777d9e6422a1eb92ba8be7 Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Thu, 17 Sep 2026 23:02:41 +0200 Subject: sie: import CRLF, #RAR 0, zero rows; skip #IB with prior history - tokenizer treats CR as whitespace; closing brace matches with CRLF - only #RAR with year indicator 0 selects the fiscal year - zero-amount #TRANS rows are dropped instead of violating the schema - #IB becomes an IB voucher only when the year has no earlier history --- src/sie.c | 46 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) (limited to 'src/sie.c') diff --git a/src/sie.c b/src/sie.c index 05604d2..ba4c1a8 100644 --- a/src/sie.c +++ b/src/sie.c @@ -9,6 +9,7 @@ #include #include "ledger.h" +#include "log.h" #include "util.h" #include "version.h" @@ -266,7 +267,7 @@ static int tokenize(char *line, char *toks[], int max, int *out_n) int n = 0; char *p = line; while (*p) { - while (*p == ' ' || *p == '\t') + while (*p == ' ' || *p == '\t' || *p == '\r') p++; if (!*p) break; @@ -298,7 +299,7 @@ static int tokenize(char *line, char *toks[], int max, int *out_n) } } else { toks[n++] = p; - while (*p && *p != ' ' && *p != '\t') + while (*p && *p != ' ' && *p != '\t' && *p != '\r') p++; if (*p) *p++ = '\0'; @@ -603,15 +604,17 @@ int sie_import_content(sqlite3 *db, int64_t org_id, int64_t user_id, continue; } if (toks[0][0] != '#') { - if (in_ver && strcmp(toks[0], "}") == 0) + if (in_ver && toks[0][0] == '}') in_ver = 0; line = strtok_r(NULL, "\n", &save); continue; } if (strcmp(toks[0], "#RAR") == 0 && n >= 4) { - if (sie_date(toks[2], fy_start) != 0 || - sie_date(toks[3], fy_end) != 0) - FAIL("SIE line %d: bad #RAR dates", line_no); + if (strcmp(toks[1], "0") == 0) { + if (sie_date(toks[2], fy_start) != 0 || + sie_date(toks[3], fy_end) != 0) + FAIL("SIE line %d: bad #RAR dates", line_no); + } } else if (strcmp(toks[0], "#KONTO") == 0 && n >= 2) { const char *name = n >= 3 ? toks[2] : ""; if (konto_n == konto_cap) { @@ -747,7 +750,25 @@ int sie_import_content(sqlite3 *db, int64_t org_id, int64_t user_id, FAIL("could not create account %s", ib_rows[i].account); } + /* The #IB balances are the previous year's closing balances. When this + org already has earlier non-IB vouchers, those carry the opening + balances and a synthetic IB voucher would double count them. */ + int64_t prior_history = 0; if (ib_n > 0 && !has_ib_ver) { + if (sqlite3_prepare_v2( + db, + "SELECT count(*) FROM vouchers WHERE org_id=?1" + " AND series <> 'IB' AND date < ?2", + -1, &st, NULL) != SQLITE_OK) + FAIL("database error"); + sqlite3_bind_int64(st, 1, org_id); + sqlite3_bind_text(st, 2, fy_start, -1, SQLITE_TRANSIENT); + if (sqlite3_step(st) == SQLITE_ROW) + prior_history = sqlite3_column_int64(st, 0); + sqlite3_finalize(st); + } + + if (ib_n > 0 && !has_ib_ver && prior_history == 0) { struct import_ver ibver; memset(&ibver, 0, sizeof ibver); snprintf(ibver.series, sizeof ibver.series, "IB"); @@ -766,6 +787,9 @@ int sie_import_content(sqlite3 *db, int64_t org_id, int64_t user_id, update_sequence(db, org_id, fy_id, "IB", 1); free(ibver.rows); vouchers++; + } else if (ib_n > 0 && prior_history > 0) { + log_info("SIE: #IB skipped; %lld earlier vouchers carry the balances", + (long long)prior_history); } /* re-walk the text to post vouchers (rows were kept only per block) */ @@ -811,12 +835,16 @@ int sie_import_content(sqlite3 *db, int64_t org_id, int64_t user_id, } else if (m > 0 && strcmp(t2[0], "#TRANS") == 0 && in_ver && m >= 3) { int64_t amt = 0; + int got = 0; for (int i = 2; i < m; i++) { - if (parse_amount(t2[i], &amt) == 0) + if (parse_amount(t2[i], &amt) == 0) { + got = 1; break; + } } - ver_add_row(&ver, t2[1], amt > 0 ? amt : 0, - amt < 0 ? -amt : 0, ""); + if (got && amt != 0) + ver_add_row(&ver, t2[1], amt > 0 ? amt : 0, + amt < 0 ? -amt : 0, ""); } } l2 = strtok_r(NULL, "\n", &save2); -- cgit v1.3