aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--clients/screens_bokslut.c92
-rw-r--r--clients/screens_reports.c2
-rw-r--r--docs/PROTOCOL.md14
-rw-r--r--docs/STATE.md11
-rw-r--r--src/reports.c5
-rw-r--r--src/sru.c66
-rw-r--r--src/util.c5
-rw-r--r--src/util.h5
-rw-r--r--tests/test_core.c25
9 files changed, 160 insertions, 65 deletions
diff --git a/clients/screens_bokslut.c b/clients/screens_bokslut.c
index 96c6fe9..a4776c9 100644
--- a/clients/screens_bokslut.c
+++ b/clients/screens_bokslut.c
@@ -311,13 +311,17 @@ struct ar_year {
char label[8];
char from[16], to[16];
int64_t netto, ror, res_fin, skatt, res, disp;
+ int64_t res_exact;
int64_t fin_in, fin_out;
int64_t groups[AR_GROUPS];
};
/* Balance-sheet amounts are shown the way an annual report reads them:
- credit balances (equity, liabilities) as positive numbers. */
-static int64_t ar_bs_ib_sum(yyjson_val *res, int lo, int hi)
+ credit balances (equity, liabilities) as positive numbers. Each account
+ is rounded to whole kronor before it is summed (util_round_kr), so every
+ line and total adds up; exact keeps öre for the rounding balance. */
+static int64_t ar_bs_total(yyjson_val *res, int lo, int hi, const char *field,
+ int exact)
{
int64_t s = 0;
if (!res)
@@ -330,37 +334,45 @@ static int64_t ar_bs_ib_sum(yyjson_val *res, int lo, int hi)
yyjson_val *row = yyjson_arr_get(arr, i);
if (!acct_in(vstr(row, "account"), lo, hi))
continue;
- int64_t ib = vint(row, "ib_ore");
- s += lo >= 2000 ? -ib : ib;
+ int64_t v = vint(row, field);
+ if (lo >= 2000)
+ v = -v;
+ s += exact ? v : util_round_kr(v);
}
}
return s;
}
+static int64_t ar_bs_ib_sum(yyjson_val *res, int lo, int hi)
+{
+ return ar_bs_total(res, lo, hi, "ib_ore", 0);
+}
+
static int64_t ar_bs_sum(yyjson_val *res, int lo, int hi)
{
- int64_t s = 0;
- if (!res)
- return 0;
- for (int k = 0; k < 3; k++) {
- yyjson_val *sec = yyjson_obj_get(res, BS_KEYS[k]);
- yyjson_val *arr = sec ? yyjson_obj_get(sec, "accounts") : NULL;
- size_t n = yyjson_arr_size(arr);
- for (size_t i = 0; i < n; i++) {
- yyjson_val *row = yyjson_arr_get(arr, i);
- if (!acct_in(vstr(row, "account"), lo, hi))
- continue;
- int64_t ub = vint(row, "ub_ore");
- s += lo >= 2000 ? -ub : ub;
- }
- }
- return s;
+ return ar_bs_total(res, lo, hi, "ub_ore", 0);
+}
+
+/* The krona that rounding leaves between the balance sheet's two sides, to
+ be carried by Balanserat resultat. 2099 is left out: Årets resultat is
+ the income statement's figure. A real imbalance stays visible. */
+static int64_t ar_bs_rounding(yyjson_val *res, const char *field,
+ int64_t result_kr, int64_t result_exact)
+{
+ int64_t a_kr = ar_bs_total(res, 1000, 1999, field, 0);
+ int64_t a_ex = ar_bs_total(res, 1000, 1999, field, 1);
+ int64_t el_kr = ar_bs_total(res, 2000, 2098, field, 0) +
+ ar_bs_total(res, 2100, 2999, field, 0) + result_kr;
+ int64_t el_ex = ar_bs_total(res, 2000, 2098, field, 1) +
+ ar_bs_total(res, 2100, 2999, field, 1) + result_exact;
+ return (a_kr - el_kr) - util_round_kr(a_ex - el_ex);
}
-/* One year's income statement from its general ledger. Vouchers whose
- description starts with "Stäng" are the source system's closings in
- imported history years and are skipped, so those years show their real
- figures instead of a zeroed P&L. */
+/* One year's income statement from its general ledger. SIE-imported
+ vouchers whose description starts with "Stäng" are the source system's
+ closings and are skipped (the same rule as report.income_statement), so
+ those years show their real figures instead of a zeroed P&L. Each account
+ is rounded to whole kronor before it is summed. */
static void ar_load_year(struct ar_year *y, yyjson_val *gl)
{
memset(y, 0, sizeof *y);
@@ -381,15 +393,18 @@ static void ar_load_year(struct ar_year *y, yyjson_val *gl)
size_t nr = yyjson_arr_size(rows);
for (size_t k = 0; k < nr; k++) {
yyjson_val *row = yyjson_arr_get(rows, k);
- if (strncmp(vstr(row, "description"), "Stäng", 7) == 0)
+ if (strcmp(vstr(row, "source"), "sie_import") == 0 &&
+ strncmp(vstr(row, "description"), "Stäng",
+ strlen("Stäng")) == 0)
continue;
net += vint(row, "debit_ore") - vint(row, "credit_ore");
}
- int64_t disp = -net;
+ int64_t disp = util_round_kr(-net);
int done = 0;
for (size_t g = 0; g < AR_GROUPS; g++) {
if (acct_in(num, IS_GROUPS[g].lo, IS_GROUPS[g].hi)) {
y->groups[g] += disp;
+ y->res_exact -= net;
done = 1;
break;
}
@@ -404,6 +419,9 @@ static void ar_load_year(struct ar_year *y, yyjson_val *gl)
y->disp += disp;
else if (n >= 8900 && n <= 8989)
y->skatt += disp;
+ else
+ continue;
+ y->res_exact -= net;
}
int64_t rev = 0, cost = 0;
for (size_t g = 0; g < AR_GROUPS; g++)
@@ -590,8 +608,11 @@ static void fmt_arsredovisning(struct buf *t, const struct app *a,
int64_t ak_ib = ar_bs_ib_sum(bs, 2081, 2089);
int64_t ak_ub = ar_bs_sum(bs, 2081, 2089);
- int64_t br_ib = ar_bs_ib_sum(bs, 2091, 2098);
- int64_t br_ub = ar_bs_sum(bs, 2091, 2098);
+ int64_t br_ib = ar_bs_ib_sum(bs, 2091, 2098) +
+ ar_bs_rounding(bs, "ib_ore", prev_ars,
+ prev ? prev->res_exact : 0);
+ int64_t br_ub = ar_bs_sum(bs, 2091, 2098) +
+ ar_bs_rounding(bs, "ub_ore", ars, cur->res_exact);
int64_t movement = br_ub - br_ib;
int64_t paid_div = prev_ars - movement;
if (paid_div < 0)
@@ -749,13 +770,10 @@ static void fmt_arsredovisning(struct buf *t, const struct app *a,
buf_line(t, MK_HEAD "Fritt eget kapital\n");
ar_row(t, "Balanserat resultat", br_ub, br_ib, 1);
ar_row(t, "Årets resultat", ars, prev_ars, 1);
- ar_row(t, "Summa fritt eget kapital", ar_bs_sum(bs, 2091, 2098) + ars,
- ar_bs_ib_sum(bs, 2091, 2098) + prev_ars, 1);
- ar_row(t, "Summa eget kapital",
- ar_bs_sum(bs, 2000, 2090) + ar_bs_sum(bs, 2091, 2098) + ars,
- ar_bs_ib_sum(bs, 2000, 2090) + ar_bs_ib_sum(bs, 2091, 2098) +
- prev_ars,
- 0);
+ ar_row(t, "Summa fritt eget kapital", br_ub + ars, br_ib + prev_ars, 1);
+ int64_t eq_ub = ar_bs_sum(bs, 2000, 2090) + br_ub + ars;
+ int64_t eq_ib = ar_bs_ib_sum(bs, 2000, 2090) + br_ib + prev_ars;
+ ar_row(t, "Summa eget kapital", eq_ub, eq_ib, 0);
buf_line(t, "\n");
ar_row(t, "Obeskattade reserver", ar_bs_sum(bs, 2100, 2199),
ar_bs_ib_sum(bs, 2100, 2199), 0);
@@ -775,8 +793,8 @@ static void fmt_arsredovisning(struct buf *t, const struct app *a,
int64_t skuld_p = ar_bs_ib_sum(bs, 2400, 2999);
ar_row(t, "Summa kortfristiga skulder", skuld, skuld_p, 1);
ar_row(t, "Summa eget kapital, avsättningar och skulder",
- ar_bs_sum(bs, 2000, 2999) + ars,
- ar_bs_ib_sum(bs, 2000, 2999) + prev_ars, 0);
+ eq_ub + ar_bs_sum(bs, 2100, 2999),
+ eq_ib + ar_bs_ib_sum(bs, 2100, 2999), 0);
buf_line(t, "\n" MK_HEAD "Noter\n\nNot 1 Redovisnings- och"
" värderingsprinciper\n");
diff --git a/clients/screens_reports.c b/clients/screens_reports.c
index e6263a2..a06e914 100644
--- a/clients/screens_reports.c
+++ b/clients/screens_reports.c
@@ -819,7 +819,7 @@ static const struct {
{ "7366", "2.46 Kortfristiga skulder Växelskulder" },
{ "7367", "2.47 Kortfristiga skulder Skulder till koncern-, intresse- och gemensamt styrda företag" },
{ "7369", "2.48 Skulder till övriga företag som det finns ett ägarintresse i och övriga skulder" },
- { "7368", "2.49 Kortfristiga skulder Skattesskulder" },
+ { "7368", "2.49 Kortfristiga skulder Skatteskulder" },
{ "7370", "2.50 Kortfristiga skulder Upplupna kostnader och förutbetalda intäkter" },
{ "7410", "3.1 Nettoomsättning" },
{ "7411", "3.2 Förändring av lager av produkter i arbete, färdiga varor och pågående arbete för annans räkning. +" },
diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md
index 0b69d53..3ddb03b 100644
--- a/docs/PROTOCOL.md
+++ b/docs/PROTOCOL.md
@@ -465,8 +465,9 @@ closed straight to 2099 and would otherwise net to zero; the TUI
returns account blocks:
`{"fiscal_year","from","to","last_voucher":{...},"accounts":[{"account",
"name","ib_ore","debit_ore","credit_ore","ub_ore","rows":[{"series",
-"number","date","description","row_description","debit_ore","credit_ore",
-"saldo_ore"}]}]}`; accounts without IB or period movement are omitted, and
+"number","date","description","row_description","source","debit_ore",
+"credit_ore","saldo_ore"}]}]}` (`source` is the voucher's origin, e.g.
+`sie_import`); accounts without IB or period movement are omitted, and
`accounts` (array of account numbers) filters the list.
`report.voucher_list` (verifikationslista) returns `{"fiscal_year","from",
"to","last_voucher":{...},"vouchers":[{"id","series","number","date",
@@ -476,8 +477,13 @@ returns account blocks:
`INFO.SRU` (submitter, defaults to the org) and `BLANKETTER.SRU` with one
INK2, INK2R and INK2S block each. The blankett type is derived from the
fiscal year end (`<inkomstår>P1`-`P4`), the org number is written as 12
-digits, amounts are whole kronor with öre truncated and the blankett's
-printed sign, and zero fields are omitted. INK2R is mapped from the ledger
+digits, amounts are whole kronor with the blankett's printed sign, and zero
+fields are omitted. Every account is rounded to whole kronor (half away
+from zero) before it is summed into its field, the same rule as the TUI
+årsredovisning, so the two agree to the krona and the fields add up; 2099
+is replaced by the income statement's result in 7302, and the krona that
+rounding can leave between the balance sheet's sides is carried by 7302
+(a real imbalance in the ledger stays visible). INK2R is mapped from the ledger
via the official BAS ranges; INK2S takes the derived årets resultat and
skatt plus `adjustments[]{code,amount_ore}` for manual tax adjustments and
computes 7670/7770; INK2 carries 7104/7114 and the optional 8040-8045
diff --git a/docs/STATE.md b/docs/STATE.md
index f481524..7f90303 100644
--- a/docs/STATE.md
+++ b/docs/STATE.md
@@ -110,6 +110,17 @@ unit tests and the docs consistency check.
exactly on the filed 2025/26 UB; only öre diffs remain in history (2512
1,63, 2099 1,07, moms 0,56). 2021/22 was reopened for V 25 and must be
closed again by the owner.
+- **Whole-krona rounding (2026-09-22, not deployed)**: the SRU export and
+ the TUI årsredovisning round every account to whole kronor
+ (`util_round_kr`) and sum those, so their lines, totals and INK2 fields
+ agree to the krona (the SRU export used to truncate each field, the
+ årsredovisning rounded each line). The balance sheet's rounding krona goes
+ to Balanserat resultat / 7302, and 2099 is replaced by the year's result
+ in both. The årsredovisning's "Stäng" skip never matched (`strncmp` with 7
+ bytes for the 6-byte "Stäng"), so imported years showed a zero result; it
+ now follows the server rule (SIE-imported only) via the new `source` field
+ on `report.general_ledger` rows. The total "Summa eget kapital,
+ avsättningar och skulder" no longer counts 2099 on top of the result.
- **Mail configuration**: Makandra AB (org 2) has **no** `smtp_*` settings
in bokf, so `invoice.send` there is `SMTP_NOT_CONFIGURED` (fine if
invoices are sent elsewhere — set them up when wanted). Mock AB (org 1)
diff --git a/src/reports.c b/src/reports.c
index 981d02c..265f2c2 100644
--- a/src/reports.c
+++ b/src/reports.c
@@ -574,7 +574,7 @@ yyjson_mut_val *report_general_ledger(yyjson_mut_doc *doc, sqlite3 *db,
if (sqlite3_prepare_v2(
db,
"SELECT v.series,v.number,v.date,COALESCE(v.description,''),"
- "r.debit_ore,r.credit_ore,COALESCE(r.description,'')"
+ "r.debit_ore,r.credit_ore,COALESCE(r.description,''),v.source"
" FROM voucher_rows r"
" JOIN vouchers v ON v.org_id=r.org_id AND v.id=r.voucher_id"
" JOIN accounts a ON a.org_id=r.org_id AND a.id=r.account_id"
@@ -632,6 +632,9 @@ yyjson_mut_val *report_general_ledger(yyjson_mut_doc *doc, sqlite3 *db,
yyjson_mut_obj_add_strcpy(
doc, ro, "row_description",
(const char *)sqlite3_column_text(rs, 6));
+ yyjson_mut_obj_add_strcpy(
+ doc, ro, "source",
+ (const char *)sqlite3_column_text(rs, 7));
yyjson_mut_obj_add_int(doc, ro, "saldo_ore", saldo);
}
sqlite3_reset(rs);
diff --git a/src/sru.c b/src/sru.c
index 49916dc..8634c68 100644
--- a/src/sru.c
+++ b/src/sru.c
@@ -239,9 +239,15 @@ static int in_list(const char *const *list, size_t n, const char *code)
return 0;
}
+/* Every account is rounded to whole kronor before it is added to its field,
+ so fields and totals agree to the krona with the annual report. 2099 is
+ left out: the equity carries the year's result from the income statement
+ instead, as the annual report does. *exact and *rounded (optional) sum the
+ mapped amounts before and after rounding. */
static void map_balance(yyjson_mut_val *arr, const struct bmap *tab,
size_t ntab, struct fldset *out,
- struct fldset *unmapped)
+ struct fldset *unmapped, int64_t *exact,
+ int64_t *rounded)
{
size_t n = yyjson_mut_arr_size(arr);
for (size_t i = 0; i < n; i++) {
@@ -251,6 +257,8 @@ static void map_balance(yyjson_mut_val *arr, const struct bmap *tab,
if (!acc || !ore)
continue;
int num = atoi(acc);
+ if (num == 2099)
+ continue;
const char *code = NULL;
for (size_t t = 0; t < ntab; t++) {
if (num >= tab[t].lo && num <= tab[t].hi) {
@@ -258,16 +266,23 @@ static void map_balance(yyjson_mut_val *arr, const struct bmap *tab,
break;
}
}
- if (code)
- fld_add(out, code, ore);
- else
+ if (!code) {
fld_add(unmapped, acc, ore);
+ continue;
+ }
+ int64_t kr = util_round_kr(ore);
+ fld_add(out, code, kr);
+ if (exact)
+ *exact += ore;
+ if (rounded)
+ *rounded += kr;
}
}
+/* Returns the rounded total; *exact gets the unrounded one. */
static int64_t map_income(yyjson_mut_val *arr, const struct imap *tab,
size_t ntab, struct fldset *out,
- struct fldset *unmapped)
+ struct fldset *unmapped, int64_t *exact)
{
int64_t total = 0;
size_t n = yyjson_mut_arr_size(arr);
@@ -291,13 +306,15 @@ static int64_t map_income(yyjson_mut_val *arr, const struct imap *tab,
fld_add(unmapped, acc, ore);
continue;
}
+ int64_t kr = util_round_kr(ore);
if (strcmp(m->plus, m->minus) == 0)
- fld_add(out, m->plus, ore);
- else if (ore >= 0)
- fld_add(out, m->plus, ore);
+ fld_add(out, m->plus, kr);
+ else if (kr >= 0)
+ fld_add(out, m->plus, kr);
else
- fld_add(out, m->minus, -ore);
- total += ore;
+ fld_add(out, m->minus, -kr);
+ total += kr;
+ *exact += ore;
}
return total;
}
@@ -466,28 +483,43 @@ yyjson_mut_val *sru_export(yyjson_mut_doc *doc, sqlite3 *db, int64_t org_id,
struct fldset ink2r = { 0 }, ink2s = { 0 }, ink2 = { 0 };
struct fldset unmapped = { 0 };
+ int64_t a_exact = 0, a_kr = 0, el_exact = 0, el_kr = 0;
map_balance(yyjson_mut_obj_get(yyjson_mut_obj_get(bal, "assets"),
"accounts"),
BAL_ASSETS, sizeof BAL_ASSETS / sizeof BAL_ASSETS[0], &ink2r,
- &unmapped);
+ &unmapped, &a_exact, &a_kr);
map_balance(yyjson_mut_obj_get(yyjson_mut_obj_get(bal, "equity"),
"accounts"),
- BAL_EL, sizeof BAL_EL / sizeof BAL_EL[0], &ink2r, &unmapped);
+ BAL_EL, sizeof BAL_EL / sizeof BAL_EL[0], &ink2r, &unmapped,
+ &el_exact, &el_kr);
map_balance(yyjson_mut_obj_get(yyjson_mut_obj_get(bal, "liabilities"),
"accounts"),
- BAL_EL, sizeof BAL_EL / sizeof BAL_EL[0], &ink2r, &unmapped);
+ BAL_EL, sizeof BAL_EL / sizeof BAL_EL[0], &ink2r, &unmapped,
+ &el_exact, &el_kr);
+ int64_t rev_exact = 0, exp_exact = 0;
int64_t rev_total = map_income(
yyjson_mut_obj_get(yyjson_mut_obj_get(inc, "revenue"), "accounts"),
- INC_REV, sizeof INC_REV / sizeof INC_REV[0], &ink2r, &unmapped);
+ INC_REV, sizeof INC_REV / sizeof INC_REV[0], &ink2r, &unmapped,
+ &rev_exact);
int64_t exp_total = map_income(
yyjson_mut_obj_get(yyjson_mut_obj_get(inc, "expenses"), "accounts"),
- INC_EXP, sizeof INC_EXP / sizeof INC_EXP[0], &ink2r, &unmapped);
+ INC_EXP, sizeof INC_EXP / sizeof INC_EXP[0], &ink2r, &unmapped,
+ &exp_exact);
int64_t result = rev_total - exp_total;
if (result > 0)
fld_add(&ink2r, "7450", result);
else if (result < 0)
fld_add(&ink2r, "7550", -result);
+ /* Fritt eget kapital carries the year's result; the rounding difference
+ (never a real imbalance) goes there too so the balance sheet balances
+ exactly like the annual report's. */
+ int64_t res_exact = rev_exact - exp_exact;
+ int64_t plug = (a_kr - el_kr - result) -
+ util_round_kr(a_exact - el_exact - res_exact);
+ if (result || plug)
+ fld_add(&ink2r, "7302", result + plug);
+
if (unmapped.n > 0) {
int ignore = 0;
yyjson_val *iv = yyjson_obj_get(args, "ignore_unmapped");
@@ -517,7 +549,7 @@ yyjson_mut_val *sru_export(yyjson_mut_doc *doc, sqlite3 *db, int64_t org_id,
fld_add(&ink2s, "7750", -result_kr * 100);
int64_t tax = fld_get(&ink2r, "7528");
if (tax)
- fld_add(&ink2s, "7651", (tax / 100) * 100);
+ fld_add(&ink2s, "7651", tax);
yyjson_val *adj = yyjson_obj_get(args, "adjustments");
if (adj) {
if (!yyjson_is_arr(adj)) {
@@ -537,7 +569,7 @@ yyjson_mut_val *sru_export(yyjson_mut_doc *doc, sqlite3 *db, int64_t org_id,
set_err(err, "adjustments: unknown INK2S field code");
return NULL;
}
- fld_add(&ink2s, code, (ore / 100) * 100);
+ fld_add(&ink2s, code, util_round_kr(ore));
}
}
int64_t overskott = 0;
diff --git a/src/util.c b/src/util.c
index 7882642..060d54c 100644
--- a/src/util.c
+++ b/src/util.c
@@ -575,3 +575,8 @@ size_t util_utf8_to_cp437(const char *in, unsigned char *out, size_t out_sz)
}
return o;
}
+
+int64_t util_round_kr(int64_t ore)
+{
+ return ore >= 0 ? (ore + 50) / 100 * 100 : -((-ore + 50) / 100 * 100);
+}
diff --git a/src/util.h b/src/util.h
index a94cf16..ccb0210 100644
--- a/src/util.h
+++ b/src/util.h
@@ -47,6 +47,11 @@ void util_date_add_months(const char *date, int months, char *out, size_t n);
void util_date_add_days(const char *date, int days, char *out, size_t n);
int util_date_valid(const char *s);
+/* Öre rounded to whole kronor (half away from zero), still in öre. The
+ annual report and the SRU export round every account with it and sum the
+ results, so their lines, totals and fields agree to the krona. */
+int64_t util_round_kr(int64_t ore);
+
/* encoding conversion for SIE files (IBM PC8 / CP437) */
size_t util_utf8_to_cp437(const char *in, unsigned char *out, size_t out_sz);
char *util_cp437_to_utf8(const unsigned char *in, size_t n);
diff --git a/tests/test_core.c b/tests/test_core.c
index c9c969e..071b5ce 100644
--- a/tests/test_core.c
+++ b/tests/test_core.c
@@ -1654,6 +1654,15 @@ static void test_imported_closings(struct tctx *t)
free(sru);
yyjson_doc_free(d);
+ /* the huvudbok tells imported rows apart, for the TUI årsredovisning */
+ d = call(reqf("{\"v\":1,\"id\":\"ic9a\",\"cmd\":\"report.general_ledger\","
+ "\"session\":\"%s\",\"org\":%d,\"args\":{\"accounts\":"
+ "[\"3001\"]}}",
+ g_session, (int)org));
+ CHECK_OK(d);
+ CHECK_STR(d, "result.accounts.0.rows.0.source", "sie_import");
+ yyjson_doc_free(d);
+
/* a voucher entered in bokf is never skipped, whatever its text */
d = call(reqf("{\"v\":1,\"id\":\"ic10\",\"cmd\":\"voucher.post\","
"\"session\":\"%s\",\"org\":%d,\"args\":{\"date\":"
@@ -4520,22 +4529,28 @@ static void test_sru(struct tctx *t)
CHECK(strstr(sru, "#UPPGIFT 7410 1500") != NULL);
CHECK(strstr(sru, "#UPPGIFT 7513 80") != NULL);
CHECK(strstr(sru, "#UPPGIFT 7525 500") != NULL);
- CHECK(strstr(sru, "#UPPGIFT 7528 168") != NULL);
+ /* every account rounds to whole kronor before it is summed, so the
+ fields add up: 1500 - 100 - 80 - 500 - 169 = 651 and
+ 1875 = 651 + 500 + 200 + 355 + 169 */
+ CHECK(strstr(sru, "#UPPGIFT 7528 169") != NULL);
CHECK(strstr(sru, "#UPPGIFT 7450 651") != NULL);
CHECK(strstr(sru, "#UPPGIFT 7281 1875") != NULL);
CHECK(strstr(sru, "#UPPGIFT 7369 355") != NULL);
CHECK(strstr(sru, "#UPPGIFT 7321 500") != NULL);
CHECK(strstr(sru, "#UPPGIFT 7302 651") != NULL);
CHECK(strstr(sru, "#UPPGIFT 7650 651") != NULL);
- CHECK(strstr(sru, "#UPPGIFT 7651 168") != NULL);
+ CHECK(strstr(sru, "#UPPGIFT 7651 169") != NULL);
CHECK(strstr(sru, "#UPPGIFT 7653 100") != NULL);
- CHECK(strstr(sru, "#UPPGIFT 7670 919") != NULL);
- CHECK(strstr(sru, "#UPPGIFT 7104 919") != NULL);
+ CHECK(strstr(sru, "#UPPGIFT 7368 169") != NULL);
+ CHECK(strstr(sru, "#UPPGIFT 7670 920") != NULL);
+ CHECK(strstr(sru, "#UPPGIFT 7104 920") != NULL);
CHECK(strstr(sru, "#FIL_SLUT") != NULL);
free(sru);
CHECK_STR(d, "result.from", "2026-01-01");
CHECK(find_amount(d, "result.ink2r", "code", "7281", "amount") == 1875);
- CHECK(find_amount(d, "result.ink2s", "code", "7670", "amount") == 919);
+ CHECK(find_amount(d, "result.ink2s", "code", "7670", "amount") == 920);
+ CHECK(util_round_kr(150) == 200 && util_round_kr(149) == 100);
+ CHECK(util_round_kr(-150) == -200 && util_round_kr(-149) == -100);
yyjson_doc_free(d);
d = call(reqf("{\"v\":1,\"id\":\"126\",\"cmd\":\"sru.export\","