diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-20 12:59:05 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-20 12:59:05 +0200 |
| commit | e5abd28a9806ee8f4239a1db068876be82363975 (patch) | |
| tree | 2af68cbd748e49f7547226c06b233240a2f252c5 /tests/test_core.c | |
| parent | 9ee8cb18b1017dce11e7c8c9b7fafad301cb766e (diff) | |
| download | bokf-e5abd28a9806ee8f4239a1db068876be82363975.tar.gz bokf-e5abd28a9806ee8f4239a1db068876be82363975.zip | |
bank: import SEB CSV and match against vouchers (schema v8)
Diffstat (limited to 'tests/test_core.c')
| -rw-r--r-- | tests/test_core.c | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/tests/test_core.c b/tests/test_core.c index 70a1ab7..a78a940 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -192,6 +192,23 @@ static yyjson_doc *call_sie_import(const char *session, int64_t org, return d; } +static yyjson_doc *call_bank_import(const char *session, int64_t org, + const char *csv, int dry) +{ + char *b64 = util_b64((const unsigned char *)csv, strlen(csv)); + size_t len = strlen(b64) + 1024; + char *req = xmalloc(len); + snprintf(req, len, + "{\"v\":1,\"id\":\"bank\",\"cmd\":\"bank.import\",\"session\":" + "\"%s\",\"org\":%lld%s,\"args\":{\"format\":\"seb\"," + "\"content_base64\":\"%s\"}}", + session, (long long)org, dry ? ",\"dry_run\":true" : "", b64); + yyjson_doc *d = call(req); + free(req); + free(b64); + return d; +} + static int login(const char *user, const char *pass) { yyjson_doc *d = call(reqf( @@ -521,6 +538,14 @@ static void test_pre_migration_snapshot(const char *tmpdir, " AND name='attachments_no_delete'"); CHECK(v && strcmp(v, "attachments_no_delete") == 0); free(v); + v = db_text(db, "SELECT name FROM sqlite_master WHERE type='table'" + " AND name='bank_transactions'"); + CHECK(v && strcmp(v, "bank_transactions") == 0); + free(v); + v = db_text(db, "SELECT name FROM sqlite_master WHERE type='table'" + " AND name='bank_matches'"); + CHECK(v && strcmp(v, "bank_matches") == 0); + free(v); int64_t user_id = 0; CHECK(db_create_user(db, "migrated", "Migrated", "secret123", 0, &user_id, &err) == 0); @@ -1878,6 +1903,7 @@ int main(void) g_session, (int)org_id)); CHECK_OK(d); CHECK_STR(d, "result.default_series", "A"); + CHECK_STR(d, "result.bank_account", "1930"); yyjson_doc_free(d); d = call(reqf("{\"v\":1,\"id\":\"96\",\"cmd\":\"settings.set\"," @@ -1945,6 +1971,234 @@ int main(void) CHECK_STR(d, "result.attachment_dir", "/tmp/bilagor"); yyjson_doc_free(d); + /* ---------------- bank reconciliation (schema v8) ----------------- */ + + static const char seb_csv[] = + "Bokförd;Valutadatum;Text;Typ;Insättningar;Uttag;Bokfört saldo\n" + "2026-05-10;2026-05-10;\"Faktura 123; kund \"\"AB\"\"\";Insättning;" + "1250,00;;10000,00\n" + "2026-05-11;2026-05-12;Kortköp;Kort;;-2 409,00;7.591,00\n" + "2026-05-20;2026-05-21;Lön;Insättning;799376,10;;806967,10\n"; + static const char seb_one[] = + "Bokförd;Valutadatum;Text;Typ;Insättningar;Uttag;Bokfört saldo\n" + "2026-06-01;2026-06-01;Test;Insättning;10,00;;10,00\n"; + static const char seb_bad_header[] = + "Bokförd;Datum;Text;Typ;Insättningar;Uttag;Bokfört saldo\n" + "2026-06-01;2026-06-01;Test;Insättning;10,00;;10,00\n"; + static const char seb_bad_amount[] = + "Bokförd;Valutadatum;Text;Typ;Insättningar;Uttag;Bokfört saldo\n" + "2026-06-01;2026-06-01;Fel;Insättning;12,abc;;10,00\n"; + + d = call_bank_import(g_session, org_id, seb_csv, 1); + CHECK_OK(d); + CHECK(jbool(d, "result.dry_run")); + CHECK_STR(d, "result.format", "seb"); + CHECK_STR(d, "result.account", "1930"); + CHECK(jint(d, "result.total") == 3); + CHECK(jint(d, "result.imported") == 3); + CHECK(jint(d, "result.duplicates") == 0); + CHECK_STR(d, "result.first_date", "2026-05-10"); + CHECK_STR(d, "result.last_date", "2026-05-20"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"200b\",\"cmd\":\"bank.list\"," + "\"session\":\"%s\",\"org\":%d}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK(yyjson_arr_size(jget(d, "result.items")) == 0); + CHECK(jint(d, "result.summary.unmatched") == 0); + CHECK(jint(d, "result.summary.matched") == 0); + yyjson_doc_free(d); + + d = call_bank_import(g_session, org_id, seb_csv, 0); + CHECK_OK(d); + CHECK(jint(d, "result.total") == 3); + CHECK(jint(d, "result.imported") == 3); + CHECK(jint(d, "result.duplicates") == 0); + yyjson_doc_free(d); + + d = call_bank_import(g_session, org_id, seb_csv, 0); + CHECK_OK(d); + CHECK(jint(d, "result.imported") == 0); + CHECK(jint(d, "result.duplicates") == 3); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"200d\",\"cmd\":\"bank.list\"," + "\"session\":\"%s\",\"org\":%d}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK(yyjson_arr_size(jget(d, "result.items")) == 3); + CHECK(jint(d, "result.items.0.amount_ore") == 125000); + CHECK_STR(d, "result.items.0.text", "Faktura 123; kund \"AB\""); + CHECK(jint(d, "result.items.0.balance_ore") == 1000000); + CHECK(jint(d, "result.items.1.amount_ore") == -240900); + CHECK(jint(d, "result.items.1.balance_ore") == 759100); + CHECK(jint(d, "result.items.2.amount_ore") == 79937610); + CHECK(jint(d, "result.summary.unmatched") == 3); + CHECK(jint(d, "result.summary.matched") == 0); + CHECK(jint(d, "result.summary.unmatched_amount_ore") == 79821710); + CHECK(yyjson_arr_size(jget(d, "result.items.0.suggestions")) == 0); + int64_t tx1 = jint(d, "result.items.0.id"); + CHECK(tx1 > 0); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"200e\",\"cmd\":\"bank.list\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"from\":\"2026-05-11\",\"to\":\"2026-05-19\"}}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK(yyjson_arr_size(jget(d, "result.items")) == 1); + CHECK_STR(d, "result.items.0.booked_at", "2026-05-11"); + CHECK(jint(d, "result.summary.unmatched") == 3); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"201\",\"cmd\":\"voucher.post\"," + "\"session\":\"%s\",\"org\":%d,\"args\":{\"date\":" + "\"2026-05-10\",\"description\":\"Kundbetalning\"," + "\"client_ref\":\"bankref1\",\"rows\":[" + "{\"account\":\"1930\",\"debit_ore\":125000}," + "{\"account\":\"3001\",\"credit_ore\":125000}]}}", + g_session, (int)org_id)); + CHECK_OK(d); + int64_t bank_voucher = jint(d, "result.id"); + CHECK(bank_voucher > 0); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"202\",\"cmd\":\"bank.list\"," + "\"session\":\"%s\",\"org\":%d}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK(yyjson_arr_size(jget(d, "result.items.0.suggestions")) == 1); + CHECK(jint(d, "result.items.0.suggestions.0.voucher_id") == bank_voucher); + CHECK(jint(d, "result.items.0.suggestions.0.amount_ore") == 125000); + CHECK_STR(d, "result.items.0.suggestions.0.date", "2026-05-10"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"203\",\"cmd\":\"bank.match\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"transaction_id\":%lld,\"voucher_id\":%lld}}", + g_session, (int)org_id, (long long)tx1, + (long long)bank_voucher)); + CHECK_OK(d); + CHECK(jint(d, "result.transaction_id") == tx1); + CHECK(jint(d, "result.voucher_id") == bank_voucher); + CHECK(jint(d, "result.difference_ore") == 0); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"204\",\"cmd\":\"bank.match\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"transaction_id\":%lld,\"voucher_id\":%lld}}", + g_session, (int)org_id, (long long)tx1, + (long long)bank_voucher)); + CHECK_STR(d, "error.code", "CONFLICT"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"205\",\"cmd\":\"bank.list\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"status\":\"matched\"}}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK(yyjson_arr_size(jget(d, "result.items")) == 1); + CHECK(jint(d, "result.items.0.id") == tx1); + CHECK(yyjson_arr_size(jget(d, "result.items.0.matches")) == 1); + CHECK(jint(d, "result.items.0.matches.0.voucher_id") == bank_voucher); + CHECK(jint(d, "result.items.0.matches.0.bank_amount_ore") == 125000); + CHECK_STR(d, "result.items.0.matches.0.series", "V-"); + CHECK_STR(d, "result.items.0.matches.0.date", "2026-05-10"); + CHECK(yyjson_arr_size(jget(d, "result.items.0.suggestions")) == 0); + CHECK(jint(d, "result.summary.matched") == 1); + CHECK(jint(d, "result.summary.unmatched") == 2); + CHECK(jint(d, "result.summary.unmatched_amount_ore") == 79696710); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"206\",\"cmd\":\"bank.unmatch\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"transaction_id\":%lld,\"voucher_id\":%lld}}", + g_session, (int)org_id, (long long)tx1, + (long long)bank_voucher)); + CHECK_OK(d); + CHECK(jbool(d, "result.unmatched")); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"207\",\"cmd\":\"bank.unmatch\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"transaction_id\":%lld,\"voucher_id\":%lld}}", + g_session, (int)org_id, (long long)tx1, + (long long)bank_voucher)); + CHECK_STR(d, "error.code", "NOT_FOUND"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"208\",\"cmd\":\"bank.match\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"transaction_id\":%lld,\"voucher_id\":%lld}}", + g_session, (int)org_id, (long long)tx1, + (long long)bank_voucher)); + CHECK_OK(d); + CHECK(jint(d, "result.difference_ore") == 0); + yyjson_doc_free(d); + + d = call_bank_import(g_session, org_id, seb_bad_header, 1); + CHECK_STR(d, "error.code", "INVALID_ARGS"); + CHECK(jstr(d, "error.message") && + strstr(jstr(d, "error.message"), "header mismatch") != NULL); + yyjson_doc_free(d); + + d = call_bank_import(g_session, org_id, seb_bad_amount, 1); + CHECK_STR(d, "error.code", "INVALID_ARGS"); + CHECK(jstr(d, "error.message") && + strstr(jstr(d, "error.message"), "line 2") != NULL); + yyjson_doc_free(d); + + { + char *bad_b64 = + util_b64((const unsigned char *)seb_one, strlen(seb_one)); + d = call(reqf("{\"v\":1,\"id\":\"209\",\"cmd\":\"bank.import\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"format\":\"seb\",\"content_base64\":\"%s\"," + "\"account\":\"9999\"}}", + g_session, (int)org_id, bad_b64)); + CHECK_STR(d, "error.code", "ACCOUNT_NOT_FOUND"); + yyjson_doc_free(d); + free(bad_b64); + } + + d = call(reqf("{\"v\":1,\"id\":\"210\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"bank_account\",\"value\":\"19a0\"}}", + g_session, (int)org_id)); + CHECK_STR(d, "error.code", "INVALID_ARGS"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"211\",\"cmd\":\"settings.set\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"key\":\"bank_account\",\"value\":\"1940\"}}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK_STR(d, "result.value", "1940"); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"212\",\"cmd\":\"settings.get\"," + "\"session\":\"%s\",\"org\":%d}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK_STR(d, "result.bank_account", "1940"); + yyjson_doc_free(d); + + d = call_bank_import(g_session, org_id, seb_one, 0); + CHECK_OK(d); + CHECK_STR(d, "result.account", "1940"); + CHECK(jint(d, "result.imported") == 1); + yyjson_doc_free(d); + + d = call(reqf("{\"v\":1,\"id\":\"213\",\"cmd\":\"bank.list\"," + "\"session\":\"%s\",\"org\":%d,\"args\":" + "{\"account\":\"1940\"}}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK(yyjson_arr_size(jget(d, "result.items")) == 1); + CHECK_STR(d, "result.items.0.account", "1940"); + yyjson_doc_free(d); + /* ---------------- moms rules: ranges merge per box ------------- */ d = call(reqf("{\"v\":1,\"id\":\"110\",\"cmd\":\"org.create\"," "\"session\":\"%s\",\"args\":{\"name\":\"Moms AB\"}}", |
