diff options
Diffstat (limited to 'clients/screens_invoices.c')
| -rw-r--r-- | clients/screens_invoices.c | 328 |
1 files changed, 273 insertions, 55 deletions
diff --git a/clients/screens_invoices.c b/clients/screens_invoices.c index d685e83..352c184 100644 --- a/clients/screens_invoices.c +++ b/clients/screens_invoices.c @@ -457,9 +457,13 @@ struct invctx { int64_t id; int64_t number; int64_t customer_id; + int64_t total_ore; char customer[256]; + char paid_date[16]; }; +static int64_t invoices_new_from(struct app *a, int64_t id); + static void inv_detail_pdf(struct invctx *ctx) { char args[64]; @@ -523,6 +527,61 @@ static void inv_detail_send(struct invctx *ctx) free(r); } +/* Prefills the new-invoice form with the payment voucher: D the org's + bank account, K the invoice receivable account, both editable. */ +static void inv_detail_pay(struct invctx *ctx) +{ + struct app *a = ctx->a; + if (ctx->paid_date[0]) { + tui_message("Kvittera betalning", "Fakturan är redan betald."); + return; + } + char pay[16] = "1930", rec[16] = "1510"; + char *resp = client_rpc(&a->conn, "settings.get", a->session, a->org, + "{}"); + if (resp && client_ok(resp)) { + char *v = jstr_dup(resp, "result.bank_account"); + if (v && *v) + snprintf(pay, sizeof pay, "%s", v); + free(v); + v = jstr_dup(resp, "result.invoice_receivable_account"); + if (v && *v) + snprintf(rec, sizeof rec, "%s", v); + free(v); + } + free(resp); + char desc[160]; + snprintf(desc, sizeof desc, "Betalning faktura %lld %.120s", + (long long)ctx->number, ctx->customer); + struct voucher_prefill p; + memset(&p, 0, sizeof p); + char date[16]; + today_iso(date, sizeof date); + p.date = date; + p.description = desc; + p.bank_account = pay; + p.counter_account = rec; + p.amount_ore = ctx->total_ore; + int64_t vid = vouchers_new_prefill(a, &p); + if (vid <= 0) + return; + char args[96]; + snprintf(args, sizeof args, "{\"id\":%lld,\"voucher_id\":%lld}", + (long long)ctx->id, (long long)vid); + resp = client_rpc(&a->conn, "invoice.pay", a->session, a->org, args); + if (resp && client_ok(resp)) { + char *ser = jstr_dup(resp, "result.voucher_series"); + int64_t num = jint_val(resp, "result.voucher_number", 0); + tui_message("Kvittera betalning", "Faktura %lld kvitterad (%s%lld).", + (long long)ctx->number, ser ? ser : "", + (long long)num); + free(ser); + } else { + show_error("Kvittera betalning", resp); + } + free(resp); +} + static int inv_key(void *ud, int ch) { struct invctx *ctx = ud; @@ -534,6 +593,14 @@ static int inv_key(void *ud, int ch) inv_detail_send(ctx); return TUI_HOOK_STAY; } + if (ch == 'u') { + invoices_new_from(ctx->a, ctx->id); + return TUI_HOOK_REFRESH; + } + if (ch == 'b') { + inv_detail_pay(ctx); + return TUI_HOOK_REFRESH; + } return TUI_HOOK_NONE; } @@ -565,10 +632,19 @@ static void invoices_detail(struct app *a, int64_t id) char *ocr = jstr_dup(resp, "result.ocr"); char *sent_at = jstr_dup(resp, "result.last_sent_at"); char *sent_to = jstr_dup(resp, "result.last_sent_to"); + char *paid_date = jstr_dup(resp, "result.paid_date"); + char *paid_ser = jstr_dup(resp, "result.paid_voucher_series"); + int64_t paid_num = jint_val(resp, "result.paid_voucher_number", 0); + char statbuf[96]; + if (paid_date && *paid_date) + snprintf(statbuf, sizeof statbuf, "betald %s", paid_date); + else + snprintf(statbuf, sizeof statbuf, "%s", + invoice_status_sv(status)); struct buf t; buf_init(&t); buf_line(&t, MK_HEAD "Faktura %lld %s\n", (long long)number, - invoice_status_sv(status)); + statbuf); buf_line(&t, MK_DIM "%s\n\n", cust ? cust : ""); buf_line(&t, "Fakturadatum: %s\n", date ? date : ""); buf_line(&t, "Förfallodatum: %s\n", due ? due : ""); @@ -584,6 +660,9 @@ static void invoices_detail(struct app *a, int64_t id) buf_line(&t, "Skickad: %s%s%s\n", sent_at, sent_to && *sent_to ? " till " : "", sent_to && *sent_to ? sent_to : ""); + if (paid_ser && *paid_ser && paid_num > 0) + buf_line(&t, "Betalningsverifikat: %s%lld\n", paid_ser, + (long long)paid_num); if (notes && *notes) buf_line(&t, "\nFritext:\n%s\n", notes); buf_line(&t, "\n"); @@ -609,6 +688,18 @@ static void invoices_detail(struct app *a, int64_t id) char *note = jstr_dup(resp, path); snprintf(path, sizeof path, "result.rows.%zu.amount_ore", i); int64_t amount = jint_val(resp, path, 0); + snprintf(path, sizeof path, "result.rows.%zu.is_text", i); + if (jbool_val(resp, path, 0)) { + char dbuf[256]; + snprintf(dbuf, sizeof dbuf, "%s", desc ? desc : ""); + tui_pad_field(dbuf, sizeof dbuf, 34); + buf_line(&t, "%s\n", dbuf); + free(desc); + free(unit); + free(vat_code); + free(note); + continue; + } char dbuf[256], qbuf[24], qcol[24], ubuf[24], pcol[40], vbuf[24], nbuf[128], acol[40]; snprintf(dbuf, sizeof dbuf, "%s", desc ? desc : ""); @@ -644,10 +735,16 @@ static void invoices_detail(struct app *a, int64_t id) ctx.id = id; ctx.number = number; ctx.customer_id = customer_id; + ctx.total_ore = total; snprintf(ctx.customer, sizeof ctx.customer, "%s", cust ? cust : ""); - int ret = tui_pager_hook("Faktura", (const char *)t.p, - "p = visa PDF s = skicka", 0, inv_key, - &ctx); + if (paid_date && *paid_date) + snprintf(ctx.paid_date, sizeof ctx.paid_date, "%s", paid_date); + char ahint[192]; + snprintf(ahint, sizeof ahint, + "p = visa PDF s = skicka u = duplicera%s", + ctx.paid_date[0] ? "" : " b = kvittera betalning"); + int ret = tui_pager_hook("Faktura", (const char *)t.p, ahint, 0, + inv_key, &ctx); free(cust); free(status); free(date); @@ -659,6 +756,8 @@ static void invoices_detail(struct app *a, int64_t id) free(ocr); free(sent_at); free(sent_to); + free(paid_date); + free(paid_ser); buf_free(&t); free(resp); if (ret != 1) @@ -890,7 +989,7 @@ static char *iform_draft_args(struct iform *f, char *err, size_t errn) yyjson_mut_obj_add_strcpy(d, o, "our_ref", f->our_ref); yyjson_mut_obj_add_strcpy(d, o, "notes", f->notes); yyjson_mut_val *arr = yyjson_mut_arr(d); - int used = 0; + int used = 0, priced = 0; for (int i = 0; i < f->rt.nrows; i++) { struct irow *r = &f->rows[i]; if (!r->description[0] && !r->quantity[0] && !r->unit[0] && @@ -901,6 +1000,13 @@ static char *iform_draft_args(struct iform *f, char *err, size_t errn) yyjson_mut_doc_free(d); return NULL; } + if (!r->quantity[0] && !r->unit[0] && !r->price[0] && !r->vat[0]) { + yyjson_mut_val *ro = yyjson_mut_arr_add_obj(d, arr); + yyjson_mut_obj_add_strcpy(d, ro, "description", r->description); + yyjson_mut_obj_add_bool(d, ro, "text", true); + used++; + continue; + } const char *qty = r->quantity[0] ? r->quantity : "1"; if (!qty_is_valid(qty)) { snprintf(err, errn, @@ -937,12 +1043,19 @@ static char *iform_draft_args(struct iform *f, char *err, size_t errn) yyjson_mut_obj_add_strcpy(d, ro, "note", r->note); yyjson_mut_obj_add_strcpy(d, ro, "vat_code", vat); used++; + priced++; } if (used == 0) { snprintf(err, errn, "Minst en rad krävs."); yyjson_mut_doc_free(d); return NULL; } + if (priced == 0) { + snprintf(err, errn, + "Minst en prissatt rad krävs (fritextrader saknar belopp)."); + yyjson_mut_doc_free(d); + return NULL; + } yyjson_mut_obj_add_val(d, o, "rows", arr); char *out = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); @@ -1021,33 +1134,8 @@ static int64_t iform_issue(struct iform *f) return id; } -static int64_t invoices_new(struct app *a) +static int64_t invoices_form_run(struct iform *f) { - struct iform f; - memset(&f, 0, sizeof f); - f.a = a; - today_iso(f.date, sizeof f.date); - snprintf(f.delivery, sizeof f.delivery, "%s", f.date); - snprintf(f.due, sizeof f.due, "%s", f.date); - snprintf(f.auto_due, sizeof f.auto_due, "%s", f.due); - if (iform_load_customers(&f) != 0) { - iform_customers_free(&f); - return 0; - } - char *resp = - client_rpc(&a->conn, "settings.get", a->session, a->org, "{}"); - if (resp && client_ok(resp)) { - char *v = jstr_dup(resp, "result.invoice_our_ref"); - if (v) { - snprintf(f.our_ref, sizeof f.our_ref, "%s", v); - free(v); - } - } - free(resp); - if (f.ncust > 0) { - f.cust_sel = 0; - iform_customer_changed(&f); - } struct tui_rt_col cols[6] = { { "Beskrivning", 2, 34, TUI_RT_EDIT }, { "Antal", 37, 8, TUI_RT_EDIT }, @@ -1056,58 +1144,181 @@ static int64_t invoices_new(struct app *a) { "Moms", 68, 14, TUI_RT_EDIT }, { "Anm", 83, 32, TUI_RT_EDIT }, }; - tui_rt_init(&f.rt, 1, IFORM_ROWS, 6, cols, 0, iform_cell, NULL, &f); - tui_rt_set_key(&f.rt, iform_key); + tui_rt_init(&f->rt, 1, IFORM_ROWS, 6, cols, 0, iform_cell, NULL, f); + tui_rt_set_key(&f->rt, iform_key); struct tui_form_field ff[7]; memset(ff, 0, sizeof ff); ff[0].label = "Kund"; ff[0].kind = TUI_F_CHOICE; - ff[0].choices = (const char *const *)f.cust_names; - ff[0].nchoices = (int)f.ncust; - ff[0].choice = &f.cust_sel; + ff[0].choices = (const char *const *)f->cust_names; + ff[0].nchoices = (int)f->ncust; + ff[0].choice = &f->cust_sel; ff[1].label = "Fakturadatum"; - ff[1].value = f.date; - ff[1].cap = sizeof f.date; + ff[1].value = f->date; + ff[1].cap = sizeof f->date; ff[1].kind = TUI_F_DATE; ff[2].label = "Förfallodatum"; - ff[2].value = f.due; - ff[2].cap = sizeof f.due; + ff[2].value = f->due; + ff[2].cap = sizeof f->due; ff[2].kind = TUI_F_DATE; ff[3].label = "Leveransdatum"; - ff[3].value = f.delivery; - ff[3].cap = sizeof f.delivery; + ff[3].value = f->delivery; + ff[3].cap = sizeof f->delivery; ff[3].kind = TUI_F_DATE; ff[4].label = "Er referens"; - ff[4].value = f.your_ref; - ff[4].cap = sizeof f.your_ref; + ff[4].value = f->your_ref; + ff[4].cap = sizeof f->your_ref; ff[4].kind = TUI_F_TEXT; ff[5].label = "Vår referens"; - ff[5].value = f.our_ref; - ff[5].cap = sizeof f.our_ref; + ff[5].value = f->our_ref; + ff[5].cap = sizeof f->our_ref; ff[5].kind = TUI_F_TEXT; ff[6].label = "Fritext"; - ff[6].value = f.notes; - ff[6].cap = sizeof f.notes; + ff[6].value = f->notes; + ff[6].cap = sizeof f->notes; ff[6].kind = TUI_F_TEXT; - tui_rt_set_fields(&f.rt, ff, 7); + tui_rt_set_fields(&f->rt, ff, 7); + tui_rt_normalize(&f->rt); const char *hint = "Enter = välj/ändra Tab = byta fält F5 = förhandsvisa" " F9 = utfärda Esc = avbryt"; int64_t out = 0; for (;;) { - int rr = tui_rt_run("Ny faktura", &f.rt, hint); + int rr = tui_rt_run("Ny faktura", &f->rt, hint); if (rr == -1) break; if (rr == -2) { - iform_preview(&f); + iform_preview(f); continue; } if (rr == -3) { - out = iform_issue(&f); + out = iform_issue(f); if (out > 0) break; } } + return out; +} + +static int64_t invoices_new(struct app *a) +{ + struct iform f; + memset(&f, 0, sizeof f); + f.a = a; + today_iso(f.date, sizeof f.date); + snprintf(f.delivery, sizeof f.delivery, "%s", f.date); + snprintf(f.due, sizeof f.due, "%s", f.date); + snprintf(f.auto_due, sizeof f.auto_due, "%s", f.due); + if (iform_load_customers(&f) != 0) { + iform_customers_free(&f); + return 0; + } + char *resp = + client_rpc(&a->conn, "settings.get", a->session, a->org, "{}"); + if (resp && client_ok(resp)) { + char *v = jstr_dup(resp, "result.invoice_our_ref"); + if (v) { + snprintf(f.our_ref, sizeof f.our_ref, "%s", v); + free(v); + } + } + free(resp); + if (f.ncust > 0) { + f.cust_sel = 0; + iform_customer_changed(&f); + } + int64_t out = invoices_form_run(&f); + iform_customers_free(&f); + return out; +} + +/* Duplicates an issued invoice: same customer, rows and references, with + the dates reset (invoice and delivery today, due today + payment days). */ +static int64_t invoices_new_from(struct app *a, int64_t id) +{ + char iargs[64]; + snprintf(iargs, sizeof iargs, "{\"id\":%lld}", (long long)id); + char *resp = + client_rpc(&a->conn, "invoice.get", a->session, a->org, iargs); + if (!resp || !client_ok(resp)) { + show_error("Faktura", resp); + free(resp); + return 0; + } + struct iform f; + memset(&f, 0, sizeof f); + f.a = a; + today_iso(f.date, sizeof f.date); + snprintf(f.delivery, sizeof f.delivery, "%s", f.date); + snprintf(f.due, sizeof f.due, "%s", f.date); + snprintf(f.auto_due, sizeof f.auto_due, "%s", f.due); + if (iform_load_customers(&f) != 0) { + iform_customers_free(&f); + free(resp); + return 0; + } + int64_t cid = jint_val(resp, "result.customer_id", 0); + int found = -1; + for (size_t i = 0; i < f.ncust; i++) + if (f.cust_ids[i] == cid) { + found = (int)i; + break; + } + if (found < 0) { + tui_message("Ny faktura", + "Kunden är arkiverad eller finns inte kvar."); + iform_customers_free(&f); + free(resp); + return 0; + } + f.cust_sel = found; + iform_customer_changed(&f); + char *v = jstr_dup(resp, "result.our_ref"); + if (v) { + snprintf(f.our_ref, sizeof f.our_ref, "%s", v); + free(v); + } + v = jstr_dup(resp, "result.notes"); + if (v) { + snprintf(f.notes, sizeof f.notes, "%s", v); + free(v); + } + size_t nrows = jarr_size(resp, "result.rows"); + if (nrows > IFORM_ROWS) + nrows = IFORM_ROWS; + for (size_t i = 0; i < nrows; i++) { + struct irow *r = &f.rows[i]; + char path[64]; + snprintf(path, sizeof path, "result.rows.%zu.description", i); + char *desc = jstr_dup(resp, path); + if (desc) + snprintf(r->description, sizeof r->description, "%s", desc); + snprintf(path, sizeof path, "result.rows.%zu.is_text", i); + if (jbool_val(resp, path, 0)) { + free(desc); + continue; + } + snprintf(path, sizeof path, "result.rows.%zu.quantity_milli", i); + int64_t qm = jint_val(resp, path, 0); + snprintf(path, sizeof path, "result.rows.%zu.unit", i); + char *unit = jstr_dup(resp, path); + snprintf(path, sizeof path, "result.rows.%zu.unit_price_ore", i); + int64_t price = jint_val(resp, path, 0); + snprintf(path, sizeof path, "result.rows.%zu.vat_code", i); + char *vat = jstr_dup(resp, path); + qty_sv(qm, r->quantity, sizeof r->quantity); + if (unit) + snprintf(r->unit, sizeof r->unit, "%s", unit); + char pbuf[24]; + tui_kr_format(price, pbuf, sizeof pbuf); + snprintf(r->price, sizeof r->price, "%s", pbuf); + snprintf(r->vat, sizeof r->vat, "%s", vat_label(vat)); + free(desc); + free(unit); + free(vat); + } + free(resp); + int64_t out = invoices_form_run(&f); iform_customers_free(&f); return out; } @@ -1187,18 +1398,25 @@ void invoices_screen(struct app *a) int64_t total = jint_val(resp, path, 0); snprintf(path, sizeof path, "result.items.%zu.status", i); char *status = jstr_dup(resp, path); - char nbuf[32], cbuf[256], abuf[40]; + snprintf(path, sizeof path, "result.items.%zu.paid_date", i); + char *paid = jstr_dup(resp, path); + char nbuf[32], cbuf[256], abuf[40], stbuf[64]; snprintf(nbuf, sizeof nbuf, "Faktura %lld", (long long)number); snprintf(cbuf, sizeof cbuf, "%s", cust ? cust : ""); tui_pad_field(cbuf, sizeof cbuf, 28); tui_amt_col(abuf, sizeof abuf, 14, total); + if (paid && *paid) + snprintf(stbuf, sizeof stbuf, "betald %s", paid); + else + snprintf(stbuf, sizeof stbuf, "%s", + invoice_status_sv(status)); snprintf(line, sizeof line, "%s %s %s %s %s", nbuf, - date ? date : "", cbuf, abuf, - invoice_status_sv(status)); + date ? date : "", cbuf, abuf, stbuf); items[i] = xstrdup(line); free(date); free(cust); free(status); + free(paid); } free(resp); items[n] = xstrdup("+ Ny faktura (^N)"); |
