#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "client.h" #include "tui.h" #include "formula.h" #include "util.h" #include "version.h" #include "yyjson.h" #include "ui.h" /* ------------------------------------------------------------------ */ /* momsregler (the vat report's report_rules) */ /* ------------------------------------------------------------------ */ static const char *const RULE_TYPES[] = { "account", "range", "type" }; #define RULE_NTYPES ((int)(sizeof RULE_TYPES / sizeof RULE_TYPES[0])) static const char *const RULE_SIGNS[] = { "+", "-" }; #define RULE_NSIGNS ((int)(sizeof RULE_SIGNS / sizeof RULE_SIGNS[0])) struct rule { int64_t id; char box[16]; char match_type[16]; char pattern[128]; int sign; int64_t sort_order; }; /* F5 dry-runs; Ctrl+Enter creates/updates. Returns 1 when saved. */ static int rule_save(struct app *a, int64_t id, const char *box, int mti, const char *pattern, int signi, const char *sort, int dry) { char *end = NULL; long long sort_order = strtoll(sort, &end, 10); if (!sort[0] || !end || *end) { tui_message("Momsregel", "Sortering måste vara ett heltal."); return 0; } yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); yyjson_mut_val *o = yyjson_mut_obj(d); yyjson_mut_doc_set_root(d, o); if (id) yyjson_mut_obj_add_int(d, o, "id", id); yyjson_mut_obj_add_strcpy(d, o, "report", "vat"); yyjson_mut_obj_add_strcpy(d, o, "box", box); yyjson_mut_obj_add_strcpy(d, o, "match_type", RULE_TYPES[mti]); yyjson_mut_obj_add_strcpy(d, o, "pattern", pattern); yyjson_mut_obj_add_int(d, o, "sign", signi == 1 ? -1 : 1); yyjson_mut_obj_add_int(d, o, "sort_order", (int64_t)sort_order); char *args = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); const char *cmd = id ? "report.rule_update" : "report.rule_create"; char *resp = dry ? rpc_dry(a, cmd, args) : client_rpc(&a->conn, cmd, a->session, a->org, args); free(args); if (resp && client_ok(resp)) { if (dry) { tui_message("Validering OK", "Regeln är korrekt."); } else { tui_message("Momsregler", "Regeln sparad."); free(resp); return 1; } } else { show_error("Momsregler", resp); } free(resp); return 0; } static int rule_form(struct app *a, const struct rule *r) { static int sel = 0; char box[16] = "", pattern[128] = "", sort[16] = "10"; int mti = 0, signi = 0; if (r) { snprintf(box, sizeof box, "%s", r->box); for (int i = 0; i < RULE_NTYPES; i++) if (strcmp(RULE_TYPES[i], r->match_type) == 0) mti = i; snprintf(pattern, sizeof pattern, "%s", r->pattern); signi = r->sign < 0 ? 1 : 0; snprintf(sort, sizeof sort, "%lld", (long long)r->sort_order); } struct tui_form_field ff[5]; memset(ff, 0, sizeof ff); ff[0].label = "Ruta"; ff[0].value = box; ff[0].cap = sizeof box; ff[0].kind = TUI_F_TEXT; ff[1].label = "Typ"; ff[1].kind = TUI_F_CHOICE; ff[1].choices = RULE_TYPES; ff[1].nchoices = RULE_NTYPES; ff[1].choice = &mti; ff[2].label = "Mönster"; ff[2].value = pattern; ff[2].cap = sizeof pattern; ff[2].kind = TUI_F_TEXT; ff[3].label = "Tecken"; ff[3].kind = TUI_F_CHOICE; ff[3].choices = RULE_SIGNS; ff[3].nchoices = RULE_NSIGNS; ff[3].choice = &signi; ff[4].label = "Sortering"; ff[4].value = sort; ff[4].cap = sizeof sort; ff[4].kind = TUI_F_TEXT; const char *title = r ? "Redigera momsregel" : "Ny momsregel"; for (;;) { int rr = tui_form_run(title, ff, 5, 1, &sel); if (rr == TUI_FORM_BACK) return 0; if (rr == TUI_FORM_REFRESH) { rule_save(a, r ? r->id : 0, box, mti, pattern, signi, sort, 1); continue; } if (rr == TUI_FORM_SUBMIT) { if (rule_save(a, r ? r->id : 0, box, mti, pattern, signi, sort, 0)) return 1; continue; } } } static void rule_delete(struct app *a, const struct rule *r) { if (!tui_confirm("Momsregler", "Ta bort regeln %s %s %s?", r->box, r->match_type, r->pattern)) return; char args[64]; snprintf(args, sizeof args, "{\"id\":%lld}", (long long)r->id); char *resp = client_rpc(&a->conn, "report.rule_delete", a->session, a->org, args); if (resp && client_ok(resp)) tui_message("Momsregler", "Regeln borttagen."); else show_error("Momsregler", resp); free(resp); } void rules_screen(struct app *a) { int cursor = 0; for (;;) { if (g_quit) return; char *resp = client_rpc(&a->conn, "report.rule_list", a->session, a->org, "{\"report\":\"vat\"}"); if (!resp || !client_ok(resp)) { show_error("Momsregler", resp); free(resp); return; } size_t n = jarr_size(resp, "result.items"); struct rule *rules = xcalloc(n ? n : 1, sizeof *rules); char **items = xcalloc(n + 1, sizeof(char *)); for (size_t i = 0; i < n; i++) { char path[64], line[512], boxbuf[24], patbuf[96]; snprintf(path, sizeof path, "result.items.%zu.id", i); rules[i].id = jint_val(resp, path, 0); snprintf(path, sizeof path, "result.items.%zu.box", i); char *sbox = jstr_dup(resp, path); snprintf(rules[i].box, sizeof rules[i].box, "%s", sbox ? sbox : ""); free(sbox); snprintf(path, sizeof path, "result.items.%zu.match_type", i); char *mt = jstr_dup(resp, path); snprintf(rules[i].match_type, sizeof rules[i].match_type, "%s", mt ? mt : ""); free(mt); snprintf(path, sizeof path, "result.items.%zu.pattern", i); char *pat = jstr_dup(resp, path); snprintf(rules[i].pattern, sizeof rules[i].pattern, "%s", pat ? pat : ""); free(pat); snprintf(path, sizeof path, "result.items.%zu.sign", i); rules[i].sign = (int)jint_val(resp, path, 1); snprintf(path, sizeof path, "result.items.%zu.sort_order", i); rules[i].sort_order = jint_val(resp, path, 0); snprintf(boxbuf, sizeof boxbuf, "%s", rules[i].box); tui_pad_field(boxbuf, sizeof boxbuf, 6); snprintf(patbuf, sizeof patbuf, "%s", rules[i].pattern); tui_pad_field(patbuf, sizeof patbuf, 34); snprintf(line, sizeof line, "%s %-8s %s %-2s %6lld", boxbuf, rules[i].match_type, patbuf, rules[i].sign < 0 ? "-" : "+", (long long)rules[i].sort_order); items[i] = xstrdup(line); } items[n] = xstrdup("+ Ny regel (^N)"); int s = tui_select_list("Momsregler", items, (int)n + 1, cursor, 1, &cursor, 1, n > 0 ? "ta bort" : NULL, 0); for (size_t i = 0; i <= n; i++) free(items[i]); free(items); free(resp); if (s == -1) { free(rules); return; } if (s == -6) { if (cursor >= 0 && (size_t)cursor < n) rule_delete(a, &rules[cursor]); } else if (s == -4 || (s >= 0 && (size_t)s == n)) { rule_form(a, NULL); } else if (s >= 0 && (size_t)s < n) { rule_form(a, &rules[s]); } free(rules); } }