#include "sie.h" #include #include #include #include #include #include #include #include "ledger.h" #include "util.h" #include "version.h" static void set_err(char **err, const char *fmt, ...) __attribute__((format(printf, 2, 3))); static void set_err(char **err, const char *fmt, ...) { if (!err || *err) return; char buf[512]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof buf, fmt, ap); va_end(ap); *err = xstrdup(buf); } /* ------------------------------------------------------------------ */ /* export */ /* ------------------------------------------------------------------ */ static void put(FILE *f, const char *s) { unsigned char buf[4096]; size_t n = util_utf8_to_cp437(s, buf, sizeof buf); fwrite(buf, 1, n, f); } static void put_quoted(FILE *f, const char *s) { fputc('"', f); for (const char *p = s; *p; p++) fputc(*p == '"' ? '\'' : *p, f); fputc('"', f); } static void put_amount(FILE *f, int64_t ore) { int64_t v = ore; if (v < 0) { fputc('-', f); v = -v; } fprintf(f, "%lld.%02lld", (long long)(v / 100), (long long)(v % 100)); } static void put_date(FILE *f, const char *iso) { if (!iso || strlen(iso) != 10) return; fwrite(iso, 4, 1, f); fwrite(iso + 5, 2, 1, f); fwrite(iso + 8, 2, 1, f); } int sie_export_file(sqlite3 *db, int64_t org_id, int64_t fy_id, const char *path, char **err) { char label[64] = "", start[16] = "", end[16] = ""; sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2( db, "SELECT label,start_date,end_date FROM fiscal_years" " WHERE org_id=?1 AND id=?2", -1, &st, NULL) != SQLITE_OK) { set_err(err, "database error"); return -1; } sqlite3_bind_int64(st, 1, org_id); sqlite3_bind_int64(st, 2, fy_id); if (sqlite3_step(st) != SQLITE_ROW) { sqlite3_finalize(st); set_err(err, "fiscal year not found"); return -1; } snprintf(label, sizeof label, "%s", sqlite3_column_text(st, 0)); snprintf(start, sizeof start, "%s", sqlite3_column_text(st, 1)); snprintf(end, sizeof end, "%s", sqlite3_column_text(st, 2)); sqlite3_finalize(st); char org_name[256] = "", org_nr[64] = ""; if (sqlite3_prepare_v2(db, "SELECT name,COALESCE(org_nr,'') FROM orgs WHERE id=?1", -1, &st, NULL) != SQLITE_OK) { set_err(err, "database error"); return -1; } sqlite3_bind_int64(st, 1, org_id); if (sqlite3_step(st) == SQLITE_ROW) { snprintf(org_name, sizeof org_name, "%s", sqlite3_column_text(st, 0)); snprintf(org_nr, sizeof org_nr, "%s", sqlite3_column_text(st, 1)); } sqlite3_finalize(st); FILE *f = fopen(path, "wb"); if (!f) { set_err(err, "cannot create %s", path); return -1; } time_t now = time(NULL); struct tm tm; gmtime_r(&now, &tm); char gendate[16]; strftime(gendate, sizeof gendate, "%Y%m%d", &tm); put(f, "#FLAGGA 0\n"); put(f, "#PROGRAM \"bokf\" \"" BOKF_VERSION "\"\n"); put(f, "#FORMAT PC8\n"); fprintf(f, "#GEN %s\n", gendate); put(f, "#SIETYP 4\n"); put(f, "#FNR "); put(f, label); put(f, "\n"); put(f, "#ORGNR "); put(f, org_nr); put(f, "\n"); put(f, "#FNAMN "); put_quoted(f, org_name); put(f, "\n"); put(f, "#RAR 0 "); put_date(f, start); put(f, " "); put_date(f, end); put(f, "\n"); /* accounts and balances: IB = this year's series "IB" voucher(s) plus all non-IB history before the year; UB = everything through the end; RES = the year's non-IB movements. */ if (sqlite3_prepare_v2( db, "SELECT a.number,a.name," " COALESCE(SUM(CASE WHEN (v.series = 'IB'" " AND v.fiscal_year_id = ?4) OR (v.series <> 'IB'" " AND v.date < ?2) THEN r.debit_ore - r.credit_ore END),0)," " COALESCE(SUM(CASE WHEN v.date <= ?3" " THEN r.debit_ore - r.credit_ore END),0)," " COALESCE(SUM(CASE WHEN v.series <> 'IB'" " AND v.date >= ?2 AND v.date <= ?3" " THEN r.debit_ore - r.credit_ore END),0)" " FROM accounts a LEFT JOIN (voucher_rows r JOIN vouchers v" " ON v.org_id=r.org_id AND v.id=r.voucher_id)" " ON r.org_id=a.org_id AND r.account_id=a.id" " WHERE a.org_id=?1 AND a.active=1" " GROUP BY a.id ORDER BY a.number", -1, &st, NULL) != SQLITE_OK) { fclose(f); set_err(err, "database error"); return -1; } sqlite3_bind_int64(st, 1, org_id); sqlite3_bind_text(st, 2, start, -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 3, end, -1, SQLITE_TRANSIENT); sqlite3_bind_int64(st, 4, fy_id); while (sqlite3_step(st) == SQLITE_ROW) { const char *number = (const char *)sqlite3_column_text(st, 0); const char *name = (const char *)sqlite3_column_text(st, 1); int64_t ib = sqlite3_column_int64(st, 2); int64_t ub = sqlite3_column_int64(st, 3); int64_t res = sqlite3_column_int64(st, 4); put(f, "#KONTO "); put(f, number); put(f, " "); put_quoted(f, name ? name : ""); put(f, "\n"); if (ib != 0) { put(f, "#IB 0 "); put(f, number); put(f, " "); put_amount(f, ib); put(f, "\n"); } if (ub != 0) { put(f, "#UB 0 "); put(f, number); put(f, " "); put_amount(f, ub); put(f, "\n"); } if (res != 0) { put(f, "#RES 0 "); put(f, number); put(f, " "); put_amount(f, res); put(f, "\n"); } } sqlite3_finalize(st); /* vouchers */ if (sqlite3_prepare_v2( db, "SELECT id,series,number,date,description FROM vouchers" " WHERE org_id=?1 AND fiscal_year_id=?2 ORDER BY series,number", -1, &st, NULL) != SQLITE_OK) { fclose(f); set_err(err, "database error"); return -1; } sqlite3_bind_int64(st, 1, org_id); sqlite3_bind_int64(st, 2, fy_id); int64_t current_id = 0; while (sqlite3_step(st) == SQLITE_ROW) { current_id = sqlite3_column_int64(st, 0); put(f, "#VER "); put_quoted(f, (const char *)sqlite3_column_text(st, 1)); fprintf(f, " %lld ", (long long)sqlite3_column_int64(st, 2)); put_date(f, (const char *)sqlite3_column_text(st, 3)); put(f, " "); put_quoted(f, (const char *)sqlite3_column_text(st, 4)); put(f, "\n{\n"); sqlite3_stmt *rs = NULL; if (sqlite3_prepare_v2( db, "SELECT a.number,r.debit_ore,r.credit_ore," "COALESCE(r.description,''),v.date FROM voucher_rows r" " JOIN accounts a ON a.org_id=r.org_id AND a.id=r.account_id" " JOIN vouchers v ON v.org_id=r.org_id AND v.id=r.voucher_id" " WHERE r.org_id=?1 AND r.voucher_id=?2 ORDER BY r.line_no", -1, &rs, NULL) == SQLITE_OK) { sqlite3_bind_int64(rs, 1, org_id); sqlite3_bind_int64(rs, 2, current_id); while (sqlite3_step(rs) == SQLITE_ROW) { put(f, "#TRANS "); put(f, (const char *)sqlite3_column_text(rs, 0)); put(f, " {} "); put_amount(f, sqlite3_column_int64(rs, 1) - sqlite3_column_int64(rs, 2)); put(f, " "); put_date(f, (const char *)sqlite3_column_text(rs, 4)); put(f, " "); put_quoted(f, (const char *)sqlite3_column_text(rs, 3)); put(f, "\n"); } sqlite3_finalize(rs); } put(f, "}\n"); } sqlite3_finalize(st); if (fclose(f) != 0) { set_err(err, "cannot write %s", path); return -1; } return 0; } /* ------------------------------------------------------------------ */ /* import */ /* ------------------------------------------------------------------ */ #define MAX_TOK 24 static int tokenize(char *line, char *toks[], int max, int *out_n) { int n = 0; char *p = line; while (*p) { while (*p == ' ' || *p == '\t') p++; if (!*p) break; if (n >= max) return -1; if (*p == '"') { p++; char *w = p; toks[n++] = w; while (*p) { if (*p == '"') { if (p[1] == '"') { *w++ = '"'; p += 2; continue; } p++; break; } *w++ = *p++; } *w = '\0'; } else if (*p == '{' || *p == '}') { toks[n++] = p; p++; if (n < max && *p == '}') { toks[n++] = p; p++; } } else { toks[n++] = p; while (*p && *p != ' ' && *p != '\t') p++; if (*p) *p++ = '\0'; } } *out_n = n; return 0; } static int parse_amount(const char *s, int64_t *out) { char *end = NULL; double v = strtod(s, &end); if (!end || *end || !isfinite(v)) return -1; *out = (int64_t)llround(v * 100.0); return 0; } static int sie_date(const char *s, char *out) { if (!s || strlen(s) != 8) return -1; for (int i = 0; i < 8; i++) if (!isdigit((unsigned char)s[i])) return -1; snprintf(out, 16, "%.4s-%.2s-%.2s", s, s + 4, s + 6); return 0; } static const char *type_for_number(const char *number) { int n = atoi(number); int cls = n / 1000; if (cls == 1) return "asset"; if (cls == 2) return (n >= 2000 && n <= 2099) ? "equity" : "liability"; if (cls == 3) return "revenue"; if (cls >= 4 && cls <= 8) return "expense"; return "asset"; } struct import_row { char account[16]; int64_t debit, credit; char desc[256]; }; struct import_ver { char series[16]; int64_t number; char date[16]; char desc[512]; struct import_row *rows; size_t nrows, cap; int line_no; }; static int ver_add_row(struct import_ver *v, const char *account, int64_t debit, int64_t credit, const char *desc) { if (v->nrows == v->cap) { v->cap = v->cap ? v->cap * 2 : 8; v->rows = xrealloc(v->rows, v->cap * sizeof *v->rows); } struct import_row *r = &v->rows[v->nrows++]; snprintf(r->account, sizeof r->account, "%s", account); r->debit = debit; r->credit = credit; snprintf(r->desc, sizeof r->desc, "%s", desc ? desc : ""); return 0; } static int account_id(sqlite3 *db, int64_t org_id, const char *number, int64_t *out) { sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2( db, "SELECT id FROM accounts WHERE org_id=?1 AND number=?2", -1, &st, NULL) != SQLITE_OK) return -1; sqlite3_bind_int64(st, 1, org_id); sqlite3_bind_text(st, 2, number, -1, SQLITE_TRANSIENT); int found = 0; if (sqlite3_step(st) == SQLITE_ROW) { *out = sqlite3_column_int64(st, 0); found = 1; } sqlite3_finalize(st); return found ? 0 : -1; } static int ensure_account(sqlite3 *db, int64_t org_id, const char *number, const char *name, int64_t *out, int *created) { if (account_id(db, org_id, number, out) == 0) return 0; char ts[32]; util_iso8601(util_now(), ts, sizeof ts); sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2( db, "INSERT INTO accounts(org_id,number,name,type,created_at)" " VALUES(?1,?2,?3,?4,?5)", -1, &st, NULL) != SQLITE_OK) return -1; sqlite3_bind_int64(st, 1, org_id); sqlite3_bind_text(st, 2, number, -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 3, name && *name ? name : number, -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 4, type_for_number(number), -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 5, ts, -1, SQLITE_TRANSIENT); int rc = sqlite3_step(st); sqlite3_finalize(st); if (rc != SQLITE_DONE) return -1; *out = sqlite3_last_insert_rowid(db); if (created) (*created)++; return 0; } static int insert_import_voucher(sqlite3 *db, int64_t org_id, int64_t user_id, int64_t fy_id, const char *fy_label, struct import_ver *v, char **err) { int64_t sum_d = 0, sum_c = 0; for (size_t i = 0; i < v->nrows; i++) { sum_d += v->rows[i].debit; sum_c += v->rows[i].credit; } if (v->nrows == 0) return 0; if (sum_d != sum_c) { set_err(err, "SIE line %d: voucher %s %lld does not balance", v->line_no, v->series, (long long)v->number); return -1; } if (!util_parse_iso_date(v->date)) { set_err(err, "SIE line %d: bad voucher date %s", v->line_no, v->date); return -1; } if (!v->desc[0]) snprintf(v->desc, sizeof v->desc, "Imported voucher %s %lld", v->series, (long long)v->number); unsigned char prev[32]; memset(prev, 0, sizeof prev); sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2( db, "SELECT hash FROM vouchers WHERE org_id=?1 ORDER BY id DESC LIMIT 1", -1, &st, NULL) != SQLITE_OK) return -1; sqlite3_bind_int64(st, 1, org_id); if (sqlite3_step(st) == SQLITE_ROW) { const void *b = sqlite3_column_blob(st, 0); if (b && sqlite3_column_bytes(st, 0) == 32) memcpy(prev, b, 32); } sqlite3_finalize(st); struct ledger_row *lrows = xcalloc(v->nrows, sizeof *lrows); for (size_t i = 0; i < v->nrows; i++) { lrows[i].account = v->rows[i].account; lrows[i].debit_ore = v->rows[i].debit; lrows[i].credit_ore = v->rows[i].credit; lrows[i].description = v->rows[i].desc[0] ? v->rows[i].desc : NULL; } unsigned char hash[32]; ledger_voucher_hash(prev, org_id, fy_label, v->series, v->number, v->date, v->desc, lrows, v->nrows, hash); free(lrows); char ts[32]; util_iso8601(util_now(), ts, sizeof ts); if (sqlite3_prepare_v2( db, "INSERT INTO vouchers(org_id,fiscal_year_id,series,number,date," "description,source,created_at,created_by_user,hash_prev,hash)" " VALUES(?1,?2,?3,?4,?5,?6,'sie_import',?7,?8,?9,?10)", -1, &st, NULL) != SQLITE_OK) return -1; sqlite3_bind_int64(st, 1, org_id); sqlite3_bind_int64(st, 2, fy_id); sqlite3_bind_text(st, 3, v->series, -1, SQLITE_TRANSIENT); sqlite3_bind_int64(st, 4, v->number); sqlite3_bind_text(st, 5, v->date, -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 6, v->desc, -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 7, ts, -1, SQLITE_TRANSIENT); sqlite3_bind_int64(st, 8, user_id); sqlite3_bind_blob(st, 9, prev, 32, SQLITE_TRANSIENT); sqlite3_bind_blob(st, 10, hash, 32, SQLITE_TRANSIENT); int rc = sqlite3_step(st); sqlite3_finalize(st); if (rc != SQLITE_DONE) { set_err(err, "SIE line %d: %s", v->line_no, sqlite3_errmsg(db)); return -1; } int64_t vid = sqlite3_last_insert_rowid(db); for (size_t i = 0; i < v->nrows; i++) { int64_t aid = 0; if (account_id(db, org_id, v->rows[i].account, &aid) != 0) { set_err(err, "SIE line %d: account %s does not exist", v->line_no, v->rows[i].account); return -1; } if (sqlite3_prepare_v2( db, "INSERT INTO voucher_rows(org_id,voucher_id,line_no,account_id," "debit_ore,credit_ore,description) VALUES(?1,?2,?3,?4,?5,?6,?7)", -1, &st, NULL) != SQLITE_OK) return -1; sqlite3_bind_int64(st, 1, org_id); sqlite3_bind_int64(st, 2, vid); sqlite3_bind_int64(st, 3, (int64_t)i + 1); sqlite3_bind_int64(st, 4, aid); sqlite3_bind_int64(st, 5, v->rows[i].debit); sqlite3_bind_int64(st, 6, v->rows[i].credit); if (v->rows[i].desc[0]) sqlite3_bind_text(st, 7, v->rows[i].desc, -1, SQLITE_TRANSIENT); else sqlite3_bind_null(st, 7); rc = sqlite3_step(st); sqlite3_finalize(st); if (rc != SQLITE_DONE) { set_err(err, "SIE line %d: %s", v->line_no, sqlite3_errmsg(db)); return -1; } } return 0; } static int update_sequence(sqlite3 *db, int64_t org_id, int64_t fy_id, const char *series, int64_t number) { sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2( db, "INSERT INTO sequences(org_id,fiscal_year_id,series,next_number)" " VALUES(?1,?2,?3,?4)" " ON CONFLICT(org_id,fiscal_year_id,series) DO UPDATE SET" " next_number=MAX(next_number, excluded.next_number)", -1, &st, NULL) != SQLITE_OK) return -1; sqlite3_bind_int64(st, 1, org_id); sqlite3_bind_int64(st, 2, fy_id); sqlite3_bind_text(st, 3, series, -1, SQLITE_TRANSIENT); sqlite3_bind_int64(st, 4, number + 1); int rc = sqlite3_step(st); sqlite3_finalize(st); return rc == SQLITE_DONE ? 0 : -1; } int sie_import_content(sqlite3 *db, int64_t org_id, int64_t user_id, const unsigned char *data, size_t len, int dry_run, int64_t *out_fy_id, int64_t *out_vouchers, int64_t *out_accounts, char **err) { char *text = util_cp437_to_utf8(data, len); char *save = NULL; char *line = strtok_r(text, "\n", &save); int line_no = 0; char fy_start[16] = "", fy_end[16] = ""; struct import_ver ver; memset(&ver, 0, sizeof ver); int in_ver = 0; int has_ib_ver = 0; struct import_row *ib_rows = NULL; size_t ib_n = 0, ib_cap = 0; char **konto_numbers = NULL, **konto_names = NULL; size_t konto_n = 0, konto_cap = 0; int64_t max_number_by_series_seen = 0; char last_series[16] = ""; int64_t vouchers = 0; int rc = -1; char *local_err = NULL; #define FAIL(...) \ do { \ set_err(&local_err, __VA_ARGS__); \ goto out; \ } while (0) while (line) { line_no++; if (line[0] == '\0') { line = strtok_r(NULL, "\n", &save); continue; } char *lbuf = line; char *toks[MAX_TOK]; int n = 0; if (tokenize(lbuf, toks, MAX_TOK, &n) != 0) FAIL("SIE line %d: too many tokens", line_no); if (n == 0) { line = strtok_r(NULL, "\n", &save); continue; } if (toks[0][0] != '#') { if (in_ver && strcmp(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); } else if (strcmp(toks[0], "#KONTO") == 0 && n >= 2) { const char *name = n >= 3 ? toks[2] : ""; if (konto_n == konto_cap) { konto_cap = konto_cap ? konto_cap * 2 : 32; konto_numbers = xrealloc(konto_numbers, konto_cap * sizeof(char *)); konto_names = xrealloc(konto_names, konto_cap * sizeof(char *)); } konto_numbers[konto_n] = xstrdup(toks[1]); konto_names[konto_n] = xstrdup(name); konto_n++; } else if (strcmp(toks[0], "#IB") == 0 && n >= 4) { if (strcmp(toks[1], "0") == 0 && strcmp(toks[2], "0") != 0) { int64_t amt = 0; if (parse_amount(toks[3], &amt) != 0) FAIL("SIE line %d: bad #IB amount", line_no); if (amt != 0) { if (ib_n == ib_cap) { ib_cap = ib_cap ? ib_cap * 2 : 16; ib_rows = xrealloc(ib_rows, ib_cap * sizeof *ib_rows); } struct import_row *r = &ib_rows[ib_n++]; memset(r, 0, sizeof *r); snprintf(r->account, sizeof r->account, "%s", toks[2]); if (amt > 0) r->debit = amt; else r->credit = -amt; } } } else if (strcmp(toks[0], "#VER") == 0 && n >= 4) { if (in_ver) FAIL("SIE line %d: #VER without closing }", line_no); memset(&ver, 0, sizeof ver); /* previous rows already freed */ snprintf(ver.series, sizeof ver.series, "%s", toks[1]); if (strcmp(ver.series, "IB") == 0) has_ib_ver = 1; ver.number = strtoll(toks[2], NULL, 10); ver.line_no = line_no; if (sie_date(toks[3], ver.date) != 0) FAIL("SIE line %d: bad #VER date", line_no); if (n >= 5) snprintf(ver.desc, sizeof ver.desc, "%s", toks[4]); in_ver = 1; } else if (strcmp(toks[0], "#TRANS") == 0 && n >= 3) { /* rows are collected in the second pass */ } line = strtok_r(NULL, "\n", &save); } if (in_ver) FAIL("SIE: unterminated #VER block"); if (!fy_start[0]) { FAIL("SIE file has no #RAR; cannot determine fiscal year"); } if (sqlite3_exec(db, "BEGIN IMMEDIATE", NULL, NULL, NULL) != SQLITE_OK) FAIL("database busy"); int64_t fy_id = 0; char fy_label[64] = ""; sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2( db, "SELECT id,label FROM fiscal_years WHERE org_id=?1 AND" " start_date=?2 AND end_date=?3", -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); sqlite3_bind_text(st, 3, fy_end, -1, SQLITE_TRANSIENT); if (sqlite3_step(st) == SQLITE_ROW) { fy_id = sqlite3_column_int64(st, 0); snprintf(fy_label, sizeof fy_label, "%s", sqlite3_column_text(st, 1)); } sqlite3_finalize(st); if (!fy_id) { int sy, sm, sd, ey, em, ed2; util_date_parse(fy_start, &sy, &sm, &sd); util_date_parse(fy_end, &ey, &em, &ed2); if (sm == 1 && sd == 1) snprintf(fy_label, sizeof fy_label, "%d", ey); else snprintf(fy_label, sizeof fy_label, "%d/%d", sy, ey); char ts[32]; util_iso8601(util_now(), ts, sizeof ts); if (sqlite3_prepare_v2( db, "INSERT INTO fiscal_years(org_id,label,start_date,end_date," "created_at) VALUES(?1,?2,?3,?4,?5)", -1, &st, NULL) != SQLITE_OK) FAIL("database error"); sqlite3_bind_int64(st, 1, org_id); sqlite3_bind_text(st, 2, fy_label, -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 3, fy_start, -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 4, fy_end, -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 5, ts, -1, SQLITE_TRANSIENT); if (sqlite3_step(st) != SQLITE_DONE) FAIL("could not create fiscal year"); sqlite3_finalize(st); fy_id = sqlite3_last_insert_rowid(db); if (!fy_label[0]) snprintf(fy_label, sizeof fy_label, "%s", fy_start); } int64_t existing = 0; if (sqlite3_prepare_v2( db, "SELECT count(*) FROM vouchers WHERE org_id=?1 AND fiscal_year_id=?2", -1, &st, NULL) != SQLITE_OK) FAIL("database error"); sqlite3_bind_int64(st, 1, org_id); sqlite3_bind_int64(st, 2, fy_id); if (sqlite3_step(st) == SQLITE_ROW) existing = sqlite3_column_int64(st, 0); sqlite3_finalize(st); if (existing > 0) FAIL("target fiscal year already has %lld vouchers; import refused", (long long)existing); int created_accounts = 0; for (size_t i = 0; i < konto_n; i++) { int64_t aid = 0; if (ensure_account(db, org_id, konto_numbers[i], konto_names[i], &aid, &created_accounts) != 0) FAIL("could not create account %s", konto_numbers[i]); } for (size_t i = 0; i < ib_n; i++) { int64_t aid = 0; if (ensure_account(db, org_id, ib_rows[i].account, NULL, &aid, &created_accounts) != 0) FAIL("could not create account %s", ib_rows[i].account); } if (ib_n > 0 && !has_ib_ver) { struct import_ver ibver; memset(&ibver, 0, sizeof ibver); snprintf(ibver.series, sizeof ibver.series, "IB"); ibver.number = 1; snprintf(ibver.date, sizeof ibver.date, "%s", fy_start); snprintf(ibver.desc, sizeof ibver.desc, "IngÄende balanser (SIE)"); for (size_t i = 0; i < ib_n; i++) { int64_t aid = 0; (void)aid; ver_add_row(&ibver, ib_rows[i].account, ib_rows[i].debit, ib_rows[i].credit, NULL); } if (insert_import_voucher(db, org_id, user_id, fy_id, fy_label, &ibver, &local_err) != 0) goto out; update_sequence(db, org_id, fy_id, "IB", 1); free(ibver.rows); vouchers++; } /* re-walk the text to post vouchers (rows were kept only per block) */ { char *text2 = util_cp437_to_utf8(data, len); char *save2 = NULL; char *l2 = strtok_r(text2, "\n", &save2); int recno = 0; memset(&ver, 0, sizeof ver); in_ver = 0; while (l2) { recno++; if (l2[0] && l2[0] != ' ' && l2[0] != '\t') { char *t2[MAX_TOK]; int m = 0; if (tokenize(l2, t2, MAX_TOK, &m) == 0 && m > 0 && strcmp(t2[0], "#VER") == 0 && m >= 4) { if (in_ver) { if (insert_import_voucher(db, org_id, user_id, fy_id, fy_label, &ver, &local_err) != 0) { free(text2); goto out; } if (strlen(ver.series) > 0) { snprintf(last_series, sizeof last_series, "%s", ver.series); max_number_by_series_seen = ver.number; } update_sequence(db, org_id, fy_id, ver.series, ver.number); vouchers++; free(ver.rows); memset(&ver, 0, sizeof ver); } snprintf(ver.series, sizeof ver.series, "%s", t2[1]); ver.number = strtoll(t2[2], NULL, 10); ver.line_no = recno; sie_date(t2[3], ver.date); if (m >= 5) snprintf(ver.desc, sizeof ver.desc, "%s", t2[4]); in_ver = 1; } else if (m > 0 && strcmp(t2[0], "#TRANS") == 0 && in_ver && m >= 3) { int64_t amt = 0; for (int i = 2; i < m; i++) { if (parse_amount(t2[i], &amt) == 0) break; } ver_add_row(&ver, t2[1], amt > 0 ? amt : 0, amt < 0 ? -amt : 0, ""); } } l2 = strtok_r(NULL, "\n", &save2); } if (in_ver) { if (insert_import_voucher(db, org_id, user_id, fy_id, fy_label, &ver, &local_err) != 0) { free(text2); goto out; } update_sequence(db, org_id, fy_id, ver.series, ver.number); vouchers++; free(ver.rows); memset(&ver, 0, sizeof ver); } (void)last_series; (void)max_number_by_series_seen; free(text2); } if (dry_run) { sqlite3_exec(db, "ROLLBACK", NULL, NULL, NULL); } else if (sqlite3_exec(db, "COMMIT", NULL, NULL, NULL) != SQLITE_OK) { FAIL("commit failed"); } if (out_fy_id) *out_fy_id = fy_id; if (out_vouchers) *out_vouchers = vouchers; if (out_accounts) *out_accounts = created_accounts; rc = 0; out: if (rc != 0) { sqlite3_exec(db, "ROLLBACK", NULL, NULL, NULL); if (err && local_err) *err = local_err; else free(local_err); } else { free(local_err); } for (size_t i = 0; i < ib_n; i++) (void)0; free(ib_rows); for (size_t i = 0; i < konto_n; i++) { free(konto_numbers[i]); free(konto_names[i]); } free(konto_numbers); free(konto_names); free(ver.rows); free(text); return rc; #undef FAIL }