summaryrefslogtreecommitdiff
path: root/clients/screens_invoices.c
diff options
context:
space:
mode:
Diffstat (limited to 'clients/screens_invoices.c')
-rw-r--r--clients/screens_invoices.c328
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)");