#include "sru.h" #include #include #include #include "reports.h" #include "util.h" #include "version.h" static void set_err(char **err, const char *msg) { if (err && !*err) *err = xstrdup(msg); } static const char *jstr(yyjson_val *o, const char *k) { yyjson_val *v = o ? yyjson_obj_get(o, k) : NULL; return v && yyjson_is_str(v) ? yyjson_get_str(v) : NULL; } static int64_t jint(yyjson_val *o, const char *k) { yyjson_val *v = o ? yyjson_obj_get(o, k) : NULL; return v && yyjson_is_int(v) ? (int64_t)yyjson_get_int(v) : 0; } static const char *mjstr(yyjson_mut_val *o, const char *k) { yyjson_mut_val *v = o ? yyjson_mut_obj_get(o, k) : NULL; return v && yyjson_mut_is_str(v) ? yyjson_mut_get_str(v) : NULL; } static int64_t mjint(yyjson_mut_val *o, const char *k) { yyjson_mut_val *v = o ? yyjson_mut_obj_get(o, k) : NULL; return v && yyjson_mut_is_int(v) ? (int64_t)yyjson_mut_get_int(v) : 0; } static void add(struct buf *b, const char *s) { buf_append(b, s, strlen(s)); } /* ISO-8859-1 text; only the code points Latin-1 can hold are allowed. */ static int add_latin1(struct buf *b, const char *utf8, char **err) { for (const unsigned char *p = (const unsigned char *)utf8; *p;) { uint32_t cp; int len; if (*p < 0x80) { cp = *p; len = 1; } else if ((*p & 0xE0) == 0xC0) { cp = *p & 0x1F; len = 2; } else if ((*p & 0xF0) == 0xE0) { cp = *p & 0x0F; len = 3; } else { set_err(err, "text is not valid UTF-8"); return -1; } for (int i = 1; i < len; i++) { if ((p[i] & 0xC0) != 0x80) { set_err(err, "text is not valid UTF-8"); return -1; } cp = (cp << 6) | (uint32_t)(p[i] & 0x3F); } if (cp > 0xFF) { set_err(err, "text has a character outside ISO-8859-1"); return -1; } unsigned char c = (unsigned char)cp; buf_append(b, &c, 1); p += len; } return 0; } struct fld { char code[8]; int64_t ore; }; struct fldset { struct fld v[64]; size_t n; }; static void fld_add(struct fldset *s, const char *code, int64_t ore) { if (!ore) return; for (size_t i = 0; i < s->n; i++) { if (strcmp(s->v[i].code, code) == 0) { s->v[i].ore += ore; return; } } if (s->n >= sizeof s->v / sizeof s->v[0]) return; snprintf(s->v[s->n].code, sizeof s->v[s->n].code, "%s", code); s->v[s->n].ore = ore; s->n++; } static int64_t fld_get(const struct fldset *s, const char *code) { for (size_t i = 0; i < s->n; i++) if (strcmp(s->v[i].code, code) == 0) return s->v[i].ore; return 0; } /* ---------------- BAS ranges to INK2R field codes ---------------- */ struct bmap { int lo, hi; const char *code; }; static const struct bmap BAL_ASSETS[] = { { 1010, 1079, "7201" }, { 1090, 1099, "7201" }, { 1080, 1089, "7202" }, { 1100, 1119, "7214" }, { 1130, 1179, "7214" }, { 1190, 1199, "7214" }, { 1120, 1129, "7216" }, { 1180, 1189, "7217" }, { 1200, 1299, "7215" }, { 1311, 1316, "7230" }, { 1330, 1338, "7231" }, { 1350, 1359, "7233" }, { 1380, 1389, "7233" }, { 1320, 1329, "7232" }, { 1340, 1349, "7232" }, { 1360, 1369, "7234" }, { 1370, 1379, "7235" }, { 1390, 1399, "7235" }, { 1410, 1419, "7241" }, { 1440, 1449, "7242" }, { 1450, 1469, "7243" }, { 1470, 1489, "7244" }, { 1490, 1499, "7245" }, { 1400, 1409, "7246" }, { 1500, 1519, "7251" }, { 1560, 1579, "7252" }, { 1620, 1620, "7262" }, { 1520, 1559, "7261" }, { 1580, 1599, "7261" }, { 1600, 1699, "7261" }, { 1700, 1799, "7263" }, { 1860, 1869, "7270" }, { 1800, 1859, "7271" }, { 1870, 1899, "7271" }, { 1900, 1999, "7281" }, }; static const struct bmap BAL_EL[] = { { 2010, 2089, "7301" }, { 2090, 2099, "7302" }, { 2110, 2129, "7321" }, { 2150, 2159, "7322" }, { 2130, 2149, "7323" }, { 2160, 2199, "7323" }, { 2210, 2219, "7331" }, { 2220, 2229, "7332" }, { 2230, 2299, "7333" }, { 2320, 2329, "7350" }, { 2330, 2339, "7351" }, { 2340, 2359, "7352" }, { 2360, 2379, "7353" }, { 2380, 2399, "7354" }, { 2410, 2419, "7360" }, { 2420, 2439, "7361" }, { 2400, 2409, "7362" }, { 2450, 2459, "7363" }, { 2460, 2469, "7364" }, { 2440, 2449, "7365" }, { 2490, 2490, "7366" }, { 2470, 2479, "7367" }, { 2480, 2489, "7369" }, { 2491, 2499, "7369" }, { 2600, 2799, "7369" }, { 2800, 2899, "7369" }, { 2500, 2599, "7368" }, { 2900, 2999, "7370" }, }; struct imap { int lo, hi; const char *plus; const char *minus; }; static const struct imap INC_REV[] = { { 3000, 3799, "7410", "7410" }, { 3800, 3899, "7412", "7412" }, { 3900, 3999, "7413", "7413" }, { 4900, 4999, "7411", "7510" }, { 8000, 8099, "7414", "7518" }, { 8100, 8199, "7415", "7519" }, { 8200, 8269, "7423", "7530" }, { 8270, 8299, "7416", "7520" }, { 8300, 8399, "7417", "7417" }, { 8820, 8820, "7419", "7419" }, { 8819, 8819, "7420", "7420" }, { 8850, 8850, "7421", "7526" }, { 8860, 8899, "7422", "7527" }, }; static const struct imap INC_EXP[] = { { 4000, 4599, "7511", "7511" }, { 4600, 4699, "7512", "7512" }, { 5000, 6999, "7513", "7513" }, { 7000, 7699, "7514", "7514" }, { 7700, 7799, "7516", "7516" }, { 7800, 7899, "7515", "7515" }, { 7900, 7999, "7517", "7517" }, { 8400, 8499, "7522", "7522" }, { 8500, 8599, "7521", "7521" }, { 8830, 8830, "7524", "7524" }, { 8811, 8811, "7525", "7525" }, { 8910, 8989, "7528", "7528" }, }; static const char *const INK2R_ORDER[] = { "7011", "7012", "7201", "7202", "7214", "7215", "7216", "7217", "7230", "7231", "7233", "7232", "7234", "7235", "7241", "7242", "7243", "7244", "7245", "7246", "7251", "7252", "7261", "7262", "7263", "7270", "7271", "7281", "7301", "7302", "7321", "7322", "7323", "7331", "7332", "7333", "7350", "7351", "7352", "7353", "7354", "7360", "7361", "7362", "7363", "7364", "7365", "7366", "7367", "7369", "7368", "7370", "7410", "7411", "7510", "7412", "7413", "7511", "7512", "7513", "7514", "7515", "7516", "7517", "7414", "7518", "7415", "7519", "7423", "7530", "7416", "7520", "7417", "7521", "7522", "7524", "7419", "7420", "7525", "7421", "7526", "7422", "7527", "7528", "7450", "7550", }; static const char *const INK2S_ORDER[] = { "7011", "7012", "7650", "7750", "7651", "7652", "7653", "7751", "7764", "7752", "7753", "7754", "7654", "7668", "7655", "7673", "7665", "7755", "7656", "7756", "7657", "7658", "7757", "7758", "7659", "7660", "7759", "7666", "7765", "7661", "7760", "7761", "7662", "7663", "7762", "7763", "7671", "7672", "7670", "7770", "8020", "8021", "8023", "8026", "8022", "8028", "8040", "8041", "8044", "8045", }; static const char *const INK2_ORDER[] = { "7011", "7012", "7104", "7114", "7131", "7132", "7133", "7153", "7154", "7155", "7156", "80", "93", "84", "86", "95", "96", "97", "98", "90", "1582", }; /* INK2S fields that add to / subtract from the överskott (7670). */ static const char *const INK2S_PLUS[] = { "7650", "7651", "7652", "7653", "7654", "7668", "7655", "7673", "7665", "7656", "7657", "7658", "7659", "7660", "7666", "7661", "7662", "7663", "7671", "7672", }; static const char *const INK2S_MINUS[] = { "7750", "7751", "7764", "7752", "7753", "7754", "7755", "7756", "7757", "7758", "7759", "7765", "7760", "7761", "7762", "7763", }; static int in_list(const char *const *list, size_t n, const char *code) { for (size_t i = 0; i < n; i++) if (strcmp(list[i], code) == 0) return 1; return 0; } static void map_balance(yyjson_mut_val *arr, const struct bmap *tab, size_t ntab, struct fldset *out, struct fldset *unmapped) { size_t n = yyjson_mut_arr_size(arr); for (size_t i = 0; i < n; i++) { yyjson_mut_val *row = yyjson_mut_arr_get(arr, i); const char *acc = mjstr(row, "account"); int64_t ore = mjint(row, "amount_ore"); if (!acc || !ore) continue; int num = atoi(acc); const char *code = NULL; for (size_t t = 0; t < ntab; t++) { if (num >= tab[t].lo && num <= tab[t].hi) { code = tab[t].code; break; } } if (code) fld_add(out, code, ore); else fld_add(unmapped, acc, ore); } } static int64_t map_income(yyjson_mut_val *arr, const struct imap *tab, size_t ntab, struct fldset *out, struct fldset *unmapped) { int64_t total = 0; size_t n = yyjson_mut_arr_size(arr); for (size_t i = 0; i < n; i++) { yyjson_mut_val *row = yyjson_mut_arr_get(arr, i); const char *acc = mjstr(row, "account"); int64_t ore = mjint(row, "amount_ore"); if (!acc || !ore) continue; int num = atoi(acc); if (num >= 8990 && num <= 8999) continue; /* the result transfer itself is not a field */ const struct imap *m = NULL; for (size_t t = 0; t < ntab; t++) { if (num >= tab[t].lo && num <= tab[t].hi) { m = &tab[t]; break; } } if (!m) { fld_add(unmapped, acc, ore); continue; } if (strcmp(m->plus, m->minus) == 0) fld_add(out, m->plus, ore); else if (ore >= 0) fld_add(out, m->plus, ore); else fld_add(out, m->minus, -ore); total += ore; } return total; } static void emit_fields(struct buf *b, const struct fldset *f, const char *const *order, size_t norder) { char line[64]; for (size_t i = 0; i < norder; i++) { int64_t kr = fld_get(f, order[i]) / 100; if (!kr) continue; snprintf(line, sizeof line, "#UPPGIFT %s %lld\n", order[i], (long long)kr); add(b, line); } } static void now_stamp(char date[16], char hms[16]) { time_t t = time(NULL); struct tm tm; localtime_r(&t, &tm); strftime(date, 16, "%Y%m%d", &tm); strftime(hms, 16, "%H%M%S", &tm); } /* 10 or 12 digits (hyphens/spaces ignored) to SSSS...; 10 gets a 16 prefix. */ static int org12(const char *raw, char out[13], char **err) { char digits[16]; size_t n = 0; for (const char *p = raw ? raw : ""; *p; p++) if (*p >= '0' && *p <= '9' && n < sizeof digits - 1) digits[n++] = *p; digits[n] = '\0'; if (n == 10) snprintf(out, 13, "16%.10s", digits); else if (n == 12) snprintf(out, 13, "%.12s", digits); else { set_err(err, "org_nr must be xxxxxx-xxxx; set it in Företagsuppgifter"); return -1; } return 0; } static void date_compact(const char *iso, char out[9]) { snprintf(out, 9, "%.4s%.2s%.2s", iso, iso + 5, iso + 8); } /* The emitted (non-zero) whole-krona fields, in blankett order. */ static yyjson_mut_val *fields_json(yyjson_mut_doc *doc, const struct fldset *f, const char *const *order, size_t norder) { yyjson_mut_val *arr = yyjson_mut_arr(doc); for (size_t i = 0; i < norder; i++) { int64_t kr = fld_get(f, order[i]) / 100; if (!kr) continue; yyjson_mut_val *o = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strcpy(doc, o, "code", order[i]); yyjson_mut_obj_add_int(doc, o, "amount", kr); } return arr; } static yyjson_mut_val *file_json(yyjson_mut_doc *doc, const char *fname, const struct buf *b) { unsigned char raw[32]; char sha[65]; util_sha256(b->p, b->len, raw); util_hex(raw, sizeof raw, sha); char *b64 = util_b64(b->p, b->len); yyjson_mut_val *o = yyjson_mut_obj(doc); yyjson_mut_obj_add_strcpy(doc, o, "filename", fname); yyjson_mut_obj_add_strcpy(doc, o, "content_base64", b64 ? b64 : ""); yyjson_mut_obj_add_strcpy(doc, o, "sha256", sha); yyjson_mut_obj_add_int(doc, o, "size", (int64_t)b->len); free(b64); return o; } yyjson_mut_val *sru_export(yyjson_mut_doc *doc, sqlite3 *db, int64_t org_id, int64_t fy_id, yyjson_val *args, char **err) { char fy_start[16] = "", fy_end[16] = ""; sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2(db, "SELECT COALESCE(name,''),COALESCE(org_nr,'')," "COALESCE(address,''),COALESCE(postal_code,'')," "COALESCE(city,''),COALESCE(email,'')," "COALESCE(phone,'') FROM orgs WHERE id=?1", -1, &st, NULL) != SQLITE_OK) { set_err(err, "database error"); return NULL; } sqlite3_bind_int64(st, 1, org_id); char org_name[128] = "", org_nr[32] = "", org_addr[128] = ""; char org_zip[16] = "", org_city[64] = "", org_mail[128] = ""; char org_phone[32] = ""; if (sqlite3_step(st) == SQLITE_ROW) { snprintf(org_name, sizeof org_name, "%s", (const char *)sqlite3_column_text(st, 0)); snprintf(org_nr, sizeof org_nr, "%s", (const char *)sqlite3_column_text(st, 1)); snprintf(org_addr, sizeof org_addr, "%s", (const char *)sqlite3_column_text(st, 2)); snprintf(org_zip, sizeof org_zip, "%s", (const char *)sqlite3_column_text(st, 3)); snprintf(org_city, sizeof org_city, "%s", (const char *)sqlite3_column_text(st, 4)); snprintf(org_mail, sizeof org_mail, "%s", (const char *)sqlite3_column_text(st, 5)); snprintf(org_phone, sizeof org_phone, "%s", (const char *)sqlite3_column_text(st, 6)); } sqlite3_finalize(st); if (sqlite3_prepare_v2(db, "SELECT label,start_date,end_date FROM fiscal_years" " WHERE org_id=?1 AND id=?2", -1, &st, NULL) != SQLITE_OK) { set_err(err, "database error"); return NULL; } sqlite3_bind_int64(st, 1, org_id); sqlite3_bind_int64(st, 2, fy_id); if (sqlite3_step(st) != SQLITE_ROW) { sqlite3_finalize(st); set_err(err, "fiscal year not found"); return NULL; } snprintf(fy_start, sizeof fy_start, "%s", (const char *)sqlite3_column_text(st, 1)); snprintf(fy_end, sizeof fy_end, "%s", (const char *)sqlite3_column_text(st, 2)); sqlite3_finalize(st); char org12buf[13]; if (org12(org_nr, org12buf, err) != 0) return NULL; int end_month = atoi(fy_end + 5); char suffix = end_month <= 4 ? '1' : end_month <= 6 ? '2' : end_month <= 8 ? '3' : '4'; int income_year = atoi(fy_end); char type_base[8], type_ink2[16], type_r[16], type_s[16]; snprintf(type_base, sizeof type_base, "%dP%c", income_year, suffix); snprintf(type_ink2, sizeof type_ink2, "INK2-%s", type_base); snprintf(type_r, sizeof type_r, "INK2R-%s", type_base); snprintf(type_s, sizeof type_s, "INK2S-%s", type_base); yyjson_mut_val *bal = report_balance_sheet(doc, db, org_id, fy_id, NULL, err); if (!bal) return NULL; yyjson_mut_val *inc = report_income_statement(doc, db, org_id, fy_id, NULL, NULL, err); if (!inc) return NULL; struct fldset ink2r = { 0 }, ink2s = { 0 }, ink2 = { 0 }; struct fldset unmapped = { 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); map_balance(yyjson_mut_obj_get(yyjson_mut_obj_get(bal, "equity"), "accounts"), BAL_EL, sizeof BAL_EL / sizeof BAL_EL[0], &ink2r, &unmapped); map_balance(yyjson_mut_obj_get(yyjson_mut_obj_get(bal, "liabilities"), "accounts"), BAL_EL, sizeof BAL_EL / sizeof BAL_EL[0], &ink2r, &unmapped); 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); 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); int64_t result = rev_total - exp_total; if (result > 0) fld_add(&ink2r, "7450", result); else if (result < 0) fld_add(&ink2r, "7550", -result); if (unmapped.n > 0) { int ignore = 0; yyjson_val *iv = yyjson_obj_get(args, "ignore_unmapped"); if (iv && yyjson_is_bool(iv)) ignore = yyjson_get_bool(iv); if (!ignore) { char msg[512]; snprintf(msg, sizeof msg, "unmapped accounts: "); size_t used = strlen(msg); for (size_t i = 0; i < unmapped.n && i < 10; i++) used += (size_t)snprintf(msg + used, sizeof msg - used, "%s%s", i ? ", " : "", unmapped.v[i].code); snprintf(msg + used, sizeof msg - used, " (pass ignore_unmapped:true to proceed)"); set_err(err, msg); return NULL; } } /* INK2S: derived result and tax, then the manual adjustments. The överskott is summed from the whole-krona fields, like the e-service recomputes it. */ int64_t result_kr = result / 100; if (result_kr > 0) fld_add(&ink2s, "7650", result_kr * 100); else if (result_kr < 0) fld_add(&ink2s, "7750", -result_kr * 100); int64_t tax = fld_get(&ink2r, "7528"); if (tax) fld_add(&ink2s, "7651", (tax / 100) * 100); yyjson_val *adj = yyjson_obj_get(args, "adjustments"); if (adj) { if (!yyjson_is_arr(adj)) { set_err(err, "adjustments must be an array"); return NULL; } size_t n = yyjson_arr_size(adj); for (size_t i = 0; i < n; i++) { yyjson_val *a = yyjson_arr_get(adj, i); const char *code = jstr(a, "code"); int64_t ore = jint(a, "amount_ore"); if (!code || !(in_list(INK2S_PLUS, sizeof INK2S_PLUS / sizeof INK2S_PLUS[0], code) || in_list(INK2S_MINUS, sizeof INK2S_MINUS / sizeof INK2S_MINUS[0], code))) { set_err(err, "adjustments: unknown INK2S field code"); return NULL; } fld_add(&ink2s, code, (ore / 100) * 100); } } int64_t overskott = 0; for (size_t i = 0; i < sizeof INK2S_PLUS / sizeof INK2S_PLUS[0]; i++) overskott += fld_get(&ink2s, INK2S_PLUS[i]) / 100; for (size_t i = 0; i < sizeof INK2S_MINUS / sizeof INK2S_MINUS[0]; i++) overskott -= fld_get(&ink2s, INK2S_MINUS[i]) / 100; if (overskott > 0) fld_add(&ink2s, "7670", overskott * 100); else if (overskott < 0) fld_add(&ink2s, "7770", -overskott * 100); if (fld_get(&ink2s, "7670")) fld_add(&ink2, "7104", fld_get(&ink2s, "7670")); if (fld_get(&ink2s, "7770")) fld_add(&ink2, "7114", fld_get(&ink2s, "7770")); /* Submitter for INFO.SRU; defaults to the org itself. */ yyjson_val *sub = yyjson_obj_get(args, "submitter"); const char *s_name = jstr(sub, "name"); const char *s_org = jstr(sub, "org_nr"); const char *s_addr = jstr(sub, "address"); const char *s_zip = jstr(sub, "postnr"); const char *s_city = jstr(sub, "postort"); const char *s_contact = jstr(sub, "contact"); const char *s_mail = jstr(sub, "email"); const char *s_phone = jstr(sub, "phone"); if (!s_name) s_name = org_name; if (!s_org) s_org = org_nr; if (!s_addr) s_addr = org_addr; if (!s_zip) s_zip = org_zip; if (!s_city) s_city = org_city; if (!s_mail) s_mail = org_mail; if (!s_phone) s_phone = org_phone; char sub12[13]; if (org12(s_org, sub12, err) != 0) return NULL; if (!s_name[0] || !s_zip[0] || !s_city[0]) { set_err(err, "submitter name, postnr and postort are required" " (set them in Företagsuppgifter or pass submitter)"); return NULL; } char dnow[16], tnow[16], dstart[9], dend[9]; now_stamp(dnow, tnow); date_compact(fy_start, dstart); date_compact(fy_end, dend); struct buf info; buf_init(&info); add(&info, "#DATABESKRIVNING_START\n#PRODUKT SRU\n"); { char line[64]; snprintf(line, sizeof line, "#SKAPAD %s %s\n", dnow, tnow); add(&info, line); snprintf(line, sizeof line, "#PROGRAM bokf %s\n", BOKF_VERSION); add(&info, line); } add(&info, "#FILNAMN BLANKETTER.SRU\n#DATABESKRIVNING_SLUT\n"); add(&info, "#MEDIELEV_START\n"); { char line[64]; snprintf(line, sizeof line, "#ORGNR %s\n", sub12); add(&info, line); } if (add_latin1(&info, "#NAMN ", err) != 0 || add_latin1(&info, s_name, err) != 0) goto fail; add(&info, "\n"); if (s_addr[0]) { if (add_latin1(&info, "#ADRESS ", err) != 0 || add_latin1(&info, s_addr, err) != 0) goto fail; add(&info, "\n"); } { char line[64]; snprintf(line, sizeof line, "#POSTNR %s\n", s_zip); add(&info, line); } if (add_latin1(&info, "#POSTORT ", err) != 0 || add_latin1(&info, s_city, err) != 0) goto fail; add(&info, "\n"); if (s_contact && s_contact[0]) { if (add_latin1(&info, "#KONTAKT ", err) != 0 || add_latin1(&info, s_contact, err) != 0) goto fail; add(&info, "\n"); } if (s_mail[0]) { if (add_latin1(&info, "#EMAIL ", err) != 0 || add_latin1(&info, s_mail, err) != 0) goto fail; add(&info, "\n"); } if (s_phone[0]) { if (add_latin1(&info, "#TELEFON ", err) != 0 || add_latin1(&info, s_phone, err) != 0) goto fail; add(&info, "\n"); } add(&info, "#MEDIELEV_SLUT\n"); int assisted = -1, audited = -1; yyjson_val *av = yyjson_obj_get(args, "assisted"); if (av && yyjson_is_bool(av)) assisted = yyjson_get_bool(av); yyjson_val *auv = yyjson_obj_get(args, "audited"); if (auv && yyjson_is_bool(auv)) audited = yyjson_get_bool(auv); struct buf bl; buf_init(&bl); struct { const char *type; const struct fldset *f; const char *const *order; size_t norder; } blocks[3] = { { type_ink2, &ink2, INK2_ORDER, sizeof INK2_ORDER / sizeof INK2_ORDER[0] }, { type_r, &ink2r, INK2R_ORDER, sizeof INK2R_ORDER / sizeof INK2R_ORDER[0] }, { type_s, &ink2s, INK2S_ORDER, sizeof INK2S_ORDER / sizeof INK2S_ORDER[0] }, }; for (size_t i = 0; i < 3; i++) { char line[128]; snprintf(line, sizeof line, "#BLANKETT %s\n", blocks[i].type); add(&bl, line); snprintf(line, sizeof line, "#IDENTITET %s %s %s\n", org12buf, dnow, tnow); add(&bl, line); if (add_latin1(&bl, "#NAMN ", err) != 0) goto fail; if (add_latin1(&bl, org_name, err) != 0) goto fail; add(&bl, "\n"); snprintf(line, sizeof line, "#UPPGIFT 7011 %s\n", dstart); add(&bl, line); snprintf(line, sizeof line, "#UPPGIFT 7012 %s\n", dend); add(&bl, line); emit_fields(&bl, blocks[i].f, blocks[i].order, blocks[i].norder); if (i == 2) { if (assisted >= 0) add(&bl, assisted ? "#UPPGIFT 8040 X\n" : "#UPPGIFT 8041 X\n"); if (audited >= 0) add(&bl, audited ? "#UPPGIFT 8044 X\n" : "#UPPGIFT 8045 X\n"); } add(&bl, "#BLANKETTSLUT\n"); } add(&bl, "#FIL_SLUT\n"); yyjson_mut_val *info_file = file_json(doc, "INFO.SRU", &info); yyjson_mut_val *blanketter_file = file_json(doc, "BLANKETTER.SRU", &bl); buf_free(&info); buf_free(&bl); yyjson_mut_val *o = yyjson_mut_obj(doc); yyjson_mut_obj_add_strcpy(doc, o, "period", type_base); yyjson_mut_obj_add_strcpy(doc, o, "blankett", type_ink2); yyjson_mut_obj_add_strcpy(doc, o, "from", fy_start); yyjson_mut_obj_add_strcpy(doc, o, "to", fy_end); yyjson_mut_obj_add_val(doc, o, "ink2", fields_json(doc, &ink2, INK2_ORDER, sizeof INK2_ORDER / sizeof INK2_ORDER[0])); yyjson_mut_obj_add_val(doc, o, "ink2r", fields_json(doc, &ink2r, INK2R_ORDER, sizeof INK2R_ORDER / sizeof INK2R_ORDER[0])); yyjson_mut_obj_add_val(doc, o, "ink2s", fields_json(doc, &ink2s, INK2S_ORDER, sizeof INK2S_ORDER / sizeof INK2S_ORDER[0])); yyjson_mut_obj_add_val(doc, o, "info", info_file); yyjson_mut_obj_add_val(doc, o, "blanketter", blanketter_file); if (unmapped.n > 0) { yyjson_mut_val *arr = yyjson_mut_arr(doc); for (size_t i = 0; i < unmapped.n; i++) { yyjson_mut_val *u = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strcpy(doc, u, "account", unmapped.v[i].code); yyjson_mut_obj_add_int(doc, u, "amount_ore", unmapped.v[i].ore); } yyjson_mut_obj_add_val(doc, o, "unmapped", arr); } return o; fail: buf_free(&info); return NULL; }