summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-21 21:43:44 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-21 21:43:44 +0200
commit62a92f0af0442c78277c3c97c9efe6fef8145625 (patch)
tree3802a62904880da22bf84a44e9ece3cdff9cd8aa /src
parentf1eeb15781a044a9451ee08b91c63feb395b4767 (diff)
downloadbokf-8d2a0dd146475d0fab9711ade0df070a4e013bc6.tar.gz
bokf-8d2a0dd146475d0fab9711ade0df070a4e013bc6.zip
settings: per-feature voucher series and console invoice number; tui: F9 hintsv0.1.60
Diffstat (limited to 'src')
-rw-r--r--src/cmd_bokslut.c9
-rw-r--r--src/cmd_invoices.c4
-rw-r--r--src/cmd_payroll.c8
-rw-r--r--src/cmd_settings.c44
-rw-r--r--src/db.c8
-rw-r--r--src/db.h3
-rw-r--r--src/ledger.c6
-rw-r--r--src/reports.c29
-rw-r--r--src/sie.c16
9 files changed, 106 insertions, 21 deletions
diff --git a/src/cmd_bokslut.c b/src/cmd_bokslut.c
index c813923..f0ddee9 100644
--- a/src/cmd_bokslut.c
+++ b/src/cmd_bokslut.c
@@ -88,6 +88,9 @@ static yyjson_mut_val *h_bokslut_post(struct req *r)
}
int64_t result_before = 0;
+ char ib_series[16];
+ db_setting_copy(r->db, r->org_id, "series_ib", "IB", ib_series,
+ sizeof ib_series);
if (sqlite3_prepare_v2(
r->db,
"SELECT COALESCE(SUM(r.credit_ore)-SUM(r.debit_ore),0)"
@@ -95,11 +98,13 @@ static yyjson_mut_val *h_bokslut_post(struct req *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"
" WHERE r.org_id=?1 AND v.fiscal_year_id=?2 AND v.series<>'IB'"
+ " AND v.series<>?3"
" AND a.type IN ('revenue','expense') AND a.number NOT LIKE '89%'",
-1, &st, NULL) != SQLITE_OK)
return db_error(r);
sqlite3_bind_int64(st, 1, r->org_id);
sqlite3_bind_int64(st, 2, fy_id);
+ sqlite3_bind_text(st, 3, ib_series, -1, SQLITE_TRANSIENT);
if (sqlite3_step(st) == SQLITE_ROW)
result_before = sqlite3_column_int64(st, 0);
sqlite3_finalize(st);
@@ -154,6 +159,9 @@ static yyjson_mut_val *h_bokslut_post(struct req *r)
plan[np++] = (typeof(plan[0])){ "Resultatdisposition", drows, 2 };
}
+ char series[16];
+ db_setting_copy(r->db, r->org_id, "series_bokslut", "Å", series,
+ sizeof series);
yyjson_mut_val *vouchers = yyjson_mut_arr(r->rdoc);
for (size_t i = 0; i < np; i++) {
struct ledger_post_opts o;
@@ -165,6 +173,7 @@ static yyjson_mut_val *h_bokslut_post(struct req *r)
o.description = plan[i].desc;
o.rows = plan[i].rows;
o.nrows = plan[i].nrows;
+ o.series = series;
o.dry_run = r->dry_run;
struct ledger_error e;
char *json = NULL;
diff --git a/src/cmd_invoices.c b/src/cmd_invoices.c
index 0892ac8..73273dc 100644
--- a/src/cmd_invoices.c
+++ b/src/cmd_invoices.c
@@ -821,6 +821,9 @@ static yyjson_mut_val *h_invoice_issue(struct req *r)
invoice_store_attachment(r, &v, pdf, pdf_len, &attachment_id) != 0)
goto done;
+ char series[16];
+ db_setting_copy(r->db, r->org_id, "series_invoice", "F", series,
+ sizeof series);
struct ledger_post_opts o;
memset(&o, 0, sizeof o);
o.org_id = r->org_id;
@@ -831,6 +834,7 @@ static yyjson_mut_val *h_invoice_issue(struct req *r)
o.rows = vrows;
o.nrows = vn;
o.source = "invoice";
+ o.series = series;
o.dry_run = r->dry_run;
o.already_in_tx = 1;
struct ledger_error e;
diff --git a/src/cmd_payroll.c b/src/cmd_payroll.c
index b5db4f1..84ae323 100644
--- a/src/cmd_payroll.c
+++ b/src/cmd_payroll.c
@@ -491,6 +491,9 @@ static yyjson_mut_val *h_payroll_run_post(struct req *r)
char description[64];
snprintf(description, sizeof description, "Lönekörning %s", period);
+ char series[16];
+ db_setting_copy(r->db, r->org_id, "series_payroll", "L", series,
+ sizeof series);
struct ledger_post_opts o;
memset(&o, 0, sizeof o);
o.org_id = r->org_id;
@@ -501,6 +504,7 @@ static yyjson_mut_val *h_payroll_run_post(struct req *r)
o.rows = vrows;
o.nrows = vn;
o.source = "payroll";
+ o.series = series;
o.dry_run = r->dry_run;
o.already_in_tx = 1;
struct ledger_error e;
@@ -1341,6 +1345,9 @@ static yyjson_mut_val *h_payroll_pay_tax(struct req *r)
if (db_exec(r->db, "BEGIN IMMEDIATE", NULL) != 0)
return fail(r, "DB_BUSY", "could not start transaction");
in_tx = 1;
+ char series[16];
+ db_setting_copy(r->db, r->org_id, "series_payroll", "L", series,
+ sizeof series);
struct ledger_post_opts o;
memset(&o, 0, sizeof o);
o.org_id = r->org_id;
@@ -1351,6 +1358,7 @@ static yyjson_mut_val *h_payroll_pay_tax(struct req *r)
o.rows = rows;
o.nrows = vn;
o.source = "payroll_tax";
+ o.series = series;
o.dry_run = r->dry_run;
o.already_in_tx = 1;
if (ledger_post(r->db, &o, &e, &voucher_json) != 0) {
diff --git a/src/cmd_settings.c b/src/cmd_settings.c
index 066dc55..28167de 100644
--- a/src/cmd_settings.c
+++ b/src/cmd_settings.c
@@ -20,6 +20,9 @@ static yyjson_mut_val *h_settings_get(struct req *r)
yyjson_mut_val *o = yyjson_mut_obj(r->rdoc);
int have_default = 0, have_bank = 0, have_receivable = 0, have_revenue = 0;
int have_password = 0, have_security = 0;
+ int have_voucher = 0, have_invoice = 0, have_payroll = 0;
+ int have_bokslut = 0, have_ib = 0;
+ char legacy_series[16] = "A";
sqlite3_stmt *st = NULL;
if (sqlite3_prepare_v2(
r->db, "SELECT key,value FROM settings WHERE org_id=?1", -1, &st,
@@ -35,22 +38,46 @@ static yyjson_mut_val *h_settings_get(struct req *r)
}
yyjson_mut_obj_add(o, yyjson_mut_strcpy(r->rdoc, k),
yyjson_mut_strcpy(r->rdoc, v ? v : ""));
- if (strcmp(k, "default_series") == 0)
+ if (strcmp(k, "default_series") == 0) {
have_default = 1;
- if (strcmp(k, "bank_account") == 0)
+ if (v && *v)
+ snprintf(legacy_series, sizeof legacy_series, "%s", v);
+ } else if (strcmp(k, "series_voucher") == 0) {
+ have_voucher = 1;
+ } else if (strcmp(k, "series_invoice") == 0) {
+ have_invoice = 1;
+ } else if (strcmp(k, "series_payroll") == 0) {
+ have_payroll = 1;
+ } else if (strcmp(k, "series_bokslut") == 0) {
+ have_bokslut = 1;
+ } else if (strcmp(k, "series_ib") == 0) {
+ have_ib = 1;
+ } else if (strcmp(k, "bank_account") == 0) {
have_bank = 1;
- if (strcmp(k, "invoice_receivable_account") == 0)
+ } else if (strcmp(k, "invoice_receivable_account") == 0) {
have_receivable = 1;
- if (strcmp(k, "invoice_revenue_account") == 0)
+ } else if (strcmp(k, "invoice_revenue_account") == 0) {
have_revenue = 1;
- if (strcmp(k, "smtp_security") == 0)
+ } else if (strcmp(k, "smtp_security") == 0) {
have_security = 1;
+ }
}
}
sqlite3_finalize(st);
}
if (!have_default)
yyjson_mut_obj_add_strcpy(r->rdoc, o, "default_series", "A");
+ if (!have_voucher)
+ yyjson_mut_obj_add_strcpy(r->rdoc, o, "series_voucher",
+ legacy_series);
+ if (!have_invoice)
+ yyjson_mut_obj_add_strcpy(r->rdoc, o, "series_invoice", "F");
+ if (!have_payroll)
+ yyjson_mut_obj_add_strcpy(r->rdoc, o, "series_payroll", "L");
+ if (!have_bokslut)
+ yyjson_mut_obj_add_strcpy(r->rdoc, o, "series_bokslut", "Å");
+ if (!have_ib)
+ yyjson_mut_obj_add_strcpy(r->rdoc, o, "series_ib", "IB");
if (!have_bank)
yyjson_mut_obj_add_strcpy(r->rdoc, o, "bank_account", "1930");
if (!have_receivable)
@@ -135,7 +162,12 @@ static yyjson_mut_val *h_settings_set(struct req *r)
}
size_t maxlen;
int digits_only = 0, bankgiro = 0, port = 0, security = 0;
- if (strcmp(key, "default_series") == 0)
+ if (strcmp(key, "default_series") == 0 ||
+ strcmp(key, "series_voucher") == 0 ||
+ strcmp(key, "series_invoice") == 0 ||
+ strcmp(key, "series_payroll") == 0 ||
+ strcmp(key, "series_bokslut") == 0 ||
+ strcmp(key, "series_ib") == 0)
maxlen = 8;
else if (strcmp(key, "attachment_dir") == 0 ||
strcmp(key, "smtp_host") == 0 ||
diff --git a/src/db.c b/src/db.c
index 95ecf3f..4618a73 100644
--- a/src/db.c
+++ b/src/db.c
@@ -1215,6 +1215,14 @@ char *db_setting(sqlite3 *db, int64_t org_id, const char *key)
return value;
}
+void db_setting_copy(sqlite3 *db, int64_t org_id, const char *key,
+ const char *def, char *out, size_t n)
+{
+ char *v = db_setting(db, org_id, key);
+ snprintf(out, n, "%s", v && *v ? v : def);
+ free(v);
+}
+
char *db_membership_role(sqlite3 *db, int64_t org_id, int64_t user_id)
{
sqlite3_stmt *st = NULL;
diff --git a/src/db.h b/src/db.h
index 4e16f62..e8d891b 100644
--- a/src/db.h
+++ b/src/db.h
@@ -2,6 +2,7 @@
#define BOKF_DB_H
#include <sqlite3.h>
+#include <stddef.h>
#include <stdint.h>
#define BOKF_SCHEMA_VERSION 11
@@ -14,6 +15,8 @@ int db_create_user(sqlite3 *db, const char *username, const char *display_name,
char **err);
char *db_membership_role(sqlite3 *db, int64_t org_id, int64_t user_id);
char *db_setting(sqlite3 *db, int64_t org_id, const char *key);
+void db_setting_copy(sqlite3 *db, int64_t org_id, const char *key,
+ const char *def, char *out, size_t n);
int64_t db_count(sqlite3 *db, const char *sql);
int64_t db_last_id(sqlite3 *db);
diff --git a/src/ledger.c b/src/ledger.c
index a22e15e..b9aad47 100644
--- a/src/ledger.c
+++ b/src/ledger.c
@@ -360,7 +360,11 @@ int ledger_post(sqlite3 *db, const struct ledger_post_opts *o,
char *series_owned = NULL;
const char *series = o->series;
if (!series || !*series) {
- series_owned = db_setting(db, o->org_id, "default_series");
+ series_owned = db_setting(db, o->org_id, "series_voucher");
+ if (!series_owned || !*series_owned) {
+ free(series_owned);
+ series_owned = db_setting(db, o->org_id, "default_series");
+ }
series = series_owned && *series_owned ? series_owned : "A";
}
if (!util_parse_iso_date(o->date)) {
diff --git a/src/reports.c b/src/reports.c
index 1d4efdd..2ca7e45 100644
--- a/src/reports.c
+++ b/src/reports.c
@@ -4,6 +4,7 @@
#include <stdlib.h>
#include <string.h>
+#include "db.h"
#include "util.h"
struct fy_info {
@@ -65,19 +66,22 @@ static yyjson_mut_val *balance_query(yyjson_mut_doc *doc, sqlite3 *db,
const char *from, const char *to,
const char *types_filter, char **err)
{
+ char ib_series[16];
+ db_setting_copy(db, org_id, "series_ib", "IB", ib_series,
+ sizeof ib_series);
char sql[1280];
snprintf(sql, sizeof sql,
"SELECT a.number,a.name,a.type,"
- " COALESCE(SUM(CASE WHEN v.series <> 'IB'"
+ " COALESCE(SUM(CASE WHEN v.series <> 'IB' AND v.series <> ?5"
" AND v.date BETWEEN ?2 AND ?3 THEN r.debit_ore END),0),"
- " COALESCE(SUM(CASE WHEN v.series <> 'IB'"
+ " COALESCE(SUM(CASE WHEN v.series <> 'IB' AND v.series <> ?5"
" AND v.date BETWEEN ?2 AND ?3 THEN r.credit_ore END),0),"
- " COALESCE(SUM(CASE WHEN (v.series = 'IB'"
+ " COALESCE(SUM(CASE WHEN ((v.series = 'IB' OR v.series = ?5)"
" AND v.fiscal_year_id = ?4) OR (v.series <> 'IB'"
- " AND v.date < ?2) THEN r.debit_ore END),0),"
- " COALESCE(SUM(CASE WHEN (v.series = 'IB'"
+ " AND v.series <> ?5 AND v.date < ?2) THEN r.debit_ore END),0),"
+ " COALESCE(SUM(CASE WHEN ((v.series = 'IB' OR v.series = ?5)"
" AND v.fiscal_year_id = ?4) OR (v.series <> 'IB'"
- " AND v.date < ?2) THEN r.credit_ore END),0)"
+ " AND v.series <> ?5 AND v.date < ?2) THEN 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)"
@@ -94,6 +98,7 @@ static yyjson_mut_val *balance_query(yyjson_mut_doc *doc, sqlite3 *db,
sqlite3_bind_text(st, 2, from, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(st, 3, to, -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(st, 4, fy_id);
+ sqlite3_bind_text(st, 5, ib_series, -1, SQLITE_TRANSIENT);
yyjson_mut_val *arr = yyjson_mut_arr(doc);
while (sqlite3_step(st) == SQLITE_ROW) {
const char *number = (const char *)sqlite3_column_text(st, 0);
@@ -377,6 +382,9 @@ yyjson_mut_val *report_vat(yyjson_mut_doc *doc, sqlite3 *db, int64_t org_id,
int64_t amount;
} acc[VAT_MAX_BOXES];
size_t nacc = 0;
+ char ib_series[16];
+ db_setting_copy(db, org_id, "series_ib", "IB", ib_series,
+ sizeof ib_series);
sqlite3_stmt *st = NULL;
if (sqlite3_prepare_v2(
db,
@@ -401,21 +409,22 @@ yyjson_mut_val *report_vat(yyjson_mut_doc *doc, sqlite3 *db, int64_t org_id,
" 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"
" WHERE r.org_id=?1 AND a.number=?2 AND v.series <> 'IB'"
- " AND v.date BETWEEN ?3 AND ?4";
+ " AND v.series <> ?5 AND v.date BETWEEN ?3 AND ?4";
else if (strcmp(mt, "type") == 0)
sql = "SELECT COALESCE(SUM(r.debit_ore),0),"
"COALESCE(SUM(r.credit_ore),0) 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"
" WHERE r.org_id=?1 AND a.type=?2 AND v.series <> 'IB'"
- " AND v.date BETWEEN ?3 AND ?4";
+ " AND v.series <> ?5 AND v.date BETWEEN ?3 AND ?4";
else
sql = "SELECT COALESCE(SUM(r.debit_ore),0),"
"COALESCE(SUM(r.credit_ore),0) 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"
" WHERE r.org_id=?1 AND CAST(a.number AS INTEGER) BETWEEN ?2"
- " AND ?3 AND v.series <> 'IB' AND v.date BETWEEN ?4 AND ?5";
+ " AND ?3 AND v.series <> 'IB' AND v.series <> ?6"
+ " AND v.date BETWEEN ?4 AND ?5";
if (sqlite3_prepare_v2(db, sql, -1, &qs, NULL) != SQLITE_OK)
break;
sqlite3_bind_int64(qs, 1, org_id);
@@ -423,6 +432,7 @@ yyjson_mut_val *report_vat(yyjson_mut_doc *doc, sqlite3 *db, int64_t org_id,
sqlite3_bind_text(qs, 2, pattern, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(qs, 3, from, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(qs, 4, to, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(qs, 5, ib_series, -1, SQLITE_TRANSIENT);
} else {
long lo = 0, hi = 0;
if (sscanf(pattern, "%ld-%ld", &lo, &hi) != 2) {
@@ -433,6 +443,7 @@ yyjson_mut_val *report_vat(yyjson_mut_doc *doc, sqlite3 *db, int64_t org_id,
sqlite3_bind_int64(qs, 3, hi);
sqlite3_bind_text(qs, 4, from, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(qs, 5, to, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(qs, 6, ib_series, -1, SQLITE_TRANSIENT);
}
if (sqlite3_step(qs) == SQLITE_ROW)
amount = (sqlite3_column_int64(qs, 0) -
diff --git a/src/sie.c b/src/sie.c
index ba4c1a8..6e0eab4 100644
--- a/src/sie.c
+++ b/src/sie.c
@@ -8,6 +8,7 @@
#include <string.h>
#include <time.h>
+#include "db.h"
#include "ledger.h"
#include "log.h"
#include "util.h"
@@ -135,18 +136,22 @@ int sie_export_file(sqlite3 *db, int64_t org_id, int64_t fy_id,
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;
+ /* accounts and balances: IB = this year's IB-series voucher(s) plus all
+ non-IB history before the year; UB = everything through the end;
RES = the year's non-IB movements. */
+ char ib_series[16];
+ db_setting_copy(db, org_id, "series_ib", "IB", ib_series,
+ sizeof ib_series);
if (sqlite3_prepare_v2(
db,
"SELECT a.number,a.name,"
- " COALESCE(SUM(CASE WHEN (v.series = 'IB'"
+ " COALESCE(SUM(CASE WHEN ((v.series = 'IB' OR v.series = ?5)"
" AND v.fiscal_year_id = ?4) OR (v.series <> 'IB'"
- " AND v.date < ?2) THEN r.debit_ore - r.credit_ore END),0),"
+ " AND v.series <> ?5 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'"
+ " COALESCE(SUM(CASE WHEN v.series <> 'IB' AND v.series <> ?5"
" 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"
@@ -163,6 +168,7 @@ int sie_export_file(sqlite3 *db, int64_t org_id, int64_t fy_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);
+ sqlite3_bind_text(st, 5, ib_series, -1, SQLITE_TRANSIENT);
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);