summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-19 20:13:02 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-19 20:13:02 +0200
commit77c298b3b64f2493e509734df0084068ad24441f (patch)
tree8a8182fbecdb0dc63cdc71842511cc6d00e21716 /src
parent30c309e15d3211e7d0e8a8a2752c3e9afa0a0638 (diff)
downloadbokf-77c298b3b64f2493e509734df0084068ad24441f.tar.gz
bokf-77c298b3b64f2493e509734df0084068ad24441f.zip
bokslut: TUI screen, K2 årsredovisning draft, storable dividend (schema v4)v0.1.33
Diffstat (limited to 'src')
-rw-r--r--src/commands.c70
-rw-r--r--src/db.c14
-rw-r--r--src/db.h2
3 files changed, 84 insertions, 2 deletions
diff --git a/src/commands.c b/src/commands.c
index f03e0e4..62de0be 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -1696,10 +1696,12 @@ static yyjson_mut_val *fy_json(struct req *r, sqlite3_stmt *st)
else
yyjson_mut_obj_add_strcpy(r->rdoc, o, "locked_until",
sq(sqlite3_column_text(st, 5)));
+ yyjson_mut_obj_add_int(r->rdoc, o, "dividend_ore",
+ sqlite3_column_int64(st, 6));
return o;
}
-#define FY_COLUMNS "id,label,start_date,end_date,status,locked_until"
+#define FY_COLUMNS "id,label,start_date,end_date,status,locked_until,dividend_ore"
static yyjson_mut_val *h_fiscal_year_list(struct req *r)
{
@@ -1878,6 +1880,70 @@ static yyjson_mut_val *h_fiscal_year_reopen(struct req *r)
return yyjson_mut_obj(r->rdoc);
}
+/* Fiscal-year metadata that is not a posting: today only the board's
+ proposed dividend, kept for the årsredovisning draft. */
+static yyjson_mut_val *h_fiscal_year_update(struct req *r)
+{
+ int64_t id = 0, dividend = 0;
+ if (!arg_int(r->args, "id", &id) || id <= 0)
+ return fail(r, "INVALID_ARGS", "id is required");
+ if (!arg_int(r->args, "dividend_ore", &dividend))
+ return fail(r, "INVALID_ARGS", "dividend_ore is required");
+ if (dividend < 0)
+ return fail(r, "INVALID_ARGS", "dividend_ore must be >= 0");
+ sqlite3_stmt *st = NULL;
+ if (sqlite3_prepare_v2(r->db,
+ "SELECT count(*) FROM fiscal_years"
+ " WHERE org_id=?1 AND id=?2",
+ -1, &st, NULL) != SQLITE_OK)
+ return fail(r, "INTERNAL", "database error");
+ sqlite3_bind_int64(st, 1, r->org_id);
+ sqlite3_bind_int64(st, 2, id);
+ int64_t exists = 0;
+ if (sqlite3_step(st) == SQLITE_ROW)
+ exists = sqlite3_column_int64(st, 0);
+ sqlite3_finalize(st);
+ if (!exists)
+ return fail(r, "NOT_FOUND", "fiscal year not found");
+ if (r->dry_run) {
+ yyjson_mut_val *o = yyjson_mut_obj(r->rdoc);
+ yyjson_mut_obj_add_bool(r->rdoc, o, "dry_run", true);
+ yyjson_mut_obj_add_int(r->rdoc, o, "id", id);
+ yyjson_mut_obj_add_int(r->rdoc, o, "dividend_ore", dividend);
+ return o;
+ }
+ if (sqlite3_prepare_v2(r->db,
+ "UPDATE fiscal_years SET dividend_ore=?3"
+ " WHERE org_id=?1 AND id=?2",
+ -1, &st, NULL) != SQLITE_OK)
+ return fail(r, "INTERNAL", "database error");
+ sqlite3_bind_int64(st, 1, r->org_id);
+ sqlite3_bind_int64(st, 2, id);
+ sqlite3_bind_int64(st, 3, dividend);
+ int rc = sqlite3_step(st);
+ sqlite3_finalize(st);
+ if (rc != SQLITE_DONE)
+ return fail(r, "INTERNAL", sqlite3_errmsg(r->db));
+ char *reqjson = audit_args_json(r->args);
+ audit_append(r->db, r->org_id, r->sess->user_id, r->sess->token_id,
+ "fiscal_year.update", reqjson, "OK", NULL);
+ free(reqjson);
+ if (sqlite3_prepare_v2(r->db,
+ "SELECT " FY_COLUMNS " FROM fiscal_years"
+ " WHERE org_id=?1 AND id=?2",
+ -1, &st, NULL) != SQLITE_OK)
+ return fail(r, "INTERNAL", "database error");
+ sqlite3_bind_int64(st, 1, r->org_id);
+ sqlite3_bind_int64(st, 2, id);
+ yyjson_mut_val *o = NULL;
+ if (sqlite3_step(st) == SQLITE_ROW)
+ o = fy_json(r, st);
+ sqlite3_finalize(st);
+ if (!o)
+ return fail(r, "NOT_FOUND", "fiscal year not found");
+ return o;
+}
+
static yyjson_mut_val *h_period_lock(struct req *r)
{
int64_t fy = 0;
@@ -4119,6 +4185,8 @@ const struct command g_commands[] = {
h_fiscal_year_close },
{ "fiscal_year.reopen", "Reopen a closed fiscal year", PERM_OWNER, 1, 1, 1,
h_fiscal_year_reopen },
+ { "fiscal_year.update", "Update fiscal-year metadata (dividend)",
+ PERM_WRITE, 1, 1, 1, h_fiscal_year_update },
{ "period.lock", "Lock a period through a date", PERM_OWNER, 1, 1, 1,
h_period_lock },
{ "period.unlock", "Remove a period lock", PERM_OWNER, 1, 1, 1,
diff --git a/src/db.c b/src/db.c
index 0de40ea..ebee37b 100644
--- a/src/db.c
+++ b/src/db.c
@@ -96,6 +96,7 @@ static const char SCHEMA_V1[] =
" end_date TEXT NOT NULL,"
" status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','closed')),"
" locked_until TEXT,"
+ " dividend_ore INTEGER NOT NULL DEFAULT 0,"
" created_at TEXT NOT NULL,"
" closed_at TEXT,"
" closed_by INTEGER REFERENCES users(id),"
@@ -362,6 +363,15 @@ static int db_upgrade_v3(sqlite3 *db, char **err)
return 0;
}
+/* v4: the årsredovisning keeps the board's proposed dividend per year. */
+static int db_upgrade_v4(sqlite3 *db, char **err)
+{
+ return db_exec(db,
+ "ALTER TABLE fiscal_years ADD COLUMN dividend_ore"
+ " INTEGER NOT NULL DEFAULT 0",
+ err);
+}
+
static int db_upgrade(sqlite3 *db, int from, char **err)
{
if (db_exec(db, "BEGIN IMMEDIATE", err) != 0)
@@ -374,6 +384,10 @@ static int db_upgrade(sqlite3 *db, int from, char **err)
db_exec(db, "ROLLBACK", NULL);
return -1;
}
+ if (from < 4 && db_upgrade_v4(db, err) != 0) {
+ db_exec(db, "ROLLBACK", NULL);
+ return -1;
+ }
char *sql = sqlite3_mprintf(
"UPDATE meta SET value='%d' WHERE key='schema_version'",
BOKF_SCHEMA_VERSION);
diff --git a/src/db.h b/src/db.h
index aa34c81..7353c3c 100644
--- a/src/db.h
+++ b/src/db.h
@@ -4,7 +4,7 @@
#include <sqlite3.h>
#include <stdint.h>
-#define BOKF_SCHEMA_VERSION 3
+#define BOKF_SCHEMA_VERSION 4
int db_open(const char *path, sqlite3 **out, char **err);
int db_migrate(sqlite3 *db, char **err);