added improvements on set proces and minor other improvements

This commit is contained in:
Stefan
2026-07-13 22:40:08 +02:00
parent 5c0fdc6c1d
commit 5f85b75c22
55 changed files with 1170 additions and 357 deletions
+102 -6
View File
@@ -82,6 +82,11 @@ $inv.iMn = (ix) => {
}
return $('#topbar').ocmsmenu(m);
}
/* Menu option CSV builder: 'setm' (Set-Preisanzeige) is only offered while the invoice has not
yet been explicitly switched to a set-display mode (admin.setmode empty/undefined) — once set,
changing it again is not supported (the user edits the line items directly instead); it
reappears only if 'Änderungen verwerfen' reverts to a DB state that was never switched. */
$inv.emOpt = (admin) => 'iss,p13b,ctp' + (((admin || {}).setmode || '') === '' ? ',setm' : '');
$inv.eM = (r, re, opt) => {
let m = [];
if (booln(r, false) === true || booln(re, false) === true) {
@@ -154,8 +159,24 @@ $inv.d = {
let tbl = $inv.d.tbl(); if (tbl.length < 1) { return; }
tbl.data('dver', state.version).data('serverSums', state.sums);
$inv.d.footer(tbl, state.sums || {}, state.admin || {});
$inv.d.notes(state.notes || []);
$inv.d.validation(state.validation || []);
$inv.d.applyPositions(tbl, state.req || []);
$inv.d.applySetDisplay(tbl, state.setDisplay || {});
/* Keep the menu's "Set-Preisanzeige" entry in sync with the authoritative admin.setmode:
hides it right after a mode switch, and restores it if a discard reverted the draft to
a DB state that was never switched (ADR 0006 backend-authoritative refresh). */
$inv.eM(false, true, $inv.emOpt(state.admin || {}));
},
/* Renders the backend-authoritative notice paragraphs (ADR: notice texts are
backend-authoritative) exactly as FuchsPdf.BuildInvoiceNotes emits them for the PDF,
so the online editor always shows precisely what will be printed. */
notes: function (notes) {
let frm = $inv.d.layout(); if (frm.length < 1) { return; }
let box = frm.children('.dnotes');
if (box.length < 1) { box = $$.dc('dnotes'); frm.append(box); }
box.empty();
(notes || []).forEach((n) => $$.dc('dnote').aC(n.style || 'InvoiceNotes').text(n.text));
},
/* Push the server's authoritative position numbers back onto the rendered rows so the online
editor and the PDF preview always agree (the server numbers priced lines continuously; the
@@ -167,6 +188,23 @@ $inv.d = {
if (cell.length) { cell.text(co.p != null ? co.p : ''); }
}));
},
/* Applies the backend-authoritative set-pricing display flags (see BuildSetDisplay /
InvoiceSetPricing) onto the rendered rows: blanks the price/total cells for lines the
server says should show no price (set members in SetPrice mode, the set header in
ItemPrices mode), and emphasises the set header line — so the online editor always
shows exactly what the PDF will print, without duplicating the pricing rules client-side.
Rows not present in the map (no set in that block, or standalone items) are left untouched. */
applySetDisplay: function (tbl, setDisplay) {
tbl.find('tr.itm').each((i, tr) => {
let rw = $(tr), dta = rw.data() || {}, id = (dta.Id || '').toString();
if (id === '' || !Object.prototype.hasOwnProperty.call(setDisplay, id)) { return; }
let flags = setDisplay[id] || {};
rw.tC('sethdr', bool(flags.h, false));
if (bool(flags.p, true) === false) {
rw.find('td.currency').text('');
}
});
},
/* Send one change to the server; the draftReady signal and this success both refresh. */
sync: function (delta) {
let t = $inv.d.token(); if (t === '') { return; }
@@ -354,8 +392,23 @@ $inv.rd = {
let tbl = $inv.rd.tbl(); if (tbl.length < 1) { return; }
tbl.data('rdver', state.version).data('serverSums', state.sums).data('remid', state.remid || '');
$inv.rd.footer(tbl, state.sums || {});
$inv.rd.notes(state.notes || {});
$inv.rd.validation(state.validation || []);
},
/* Renders the backend-authoritative intro/closing notice paragraphs (ADR: notice texts
are backend-authoritative) exactly as FuchsPdf.BuildReminderNotes emits them for the
PDF, so the online editor always shows precisely what will be printed. */
notes: function (notes) {
let frm = $inv.rd.layout(); if (frm.length < 1) { return; }
let rq = frm.find('.srq'); if (rq.length < 1) { return; }
rq.children('.dnote').remove();
let intro = (notes || {}).intro || [], first = rq.children().first();
intro.slice().reverse().forEach((t) => {
let p = $$[0]('p').aC('dnote intro').rwText(t);
if (first.length) { p.insertBefore(first); } else { p.appendTo(rq); }
});
((notes || {}).closing || []).forEach((t) => $$[0]('p').aC('dnote closing').rwText(t).appendTo(rq));
},
/* Send one change to the server; the draftReady signal and this success both refresh. */
sync: function (delta) {
let t = $inv.rd.token(); if (t === '') { return; }
@@ -379,10 +432,13 @@ $inv.rd = {
$inv.rd.sync({ Target: 'amount', Value: (amount != null ? amount : 0).toString() });
$inv.rd.sync({ Target: 'amount_payed', Value: (amount_payed != null ? amount_payed : 0).toString() });
},
/* Render the open-amount footer from the server sums. */
/* Render the open-amount footer from the server sums. Detail rows ($inv.rRemRw) have
6 columns (aux, invoiceid, date, amount, amount_payed, open); span the label over the
4 columns between aux and the open-amount column so the total lands in the same last
column as the per-invoice open amounts. */
footer: function (tbl, sums) {
let ft = tbl.children('tfoot').empty();
let tr = $$.tr(ft, { class: 'tsum' }).append([$$.tdc('aux'), $$.td({ colspan: 3 }).text('Offener Betrag')]);
let tr = $$.tr(ft, { class: 'tsum' }).append([$$.tdc('aux'), $$.td({ colspan: 4 }).text('Offener Betrag')]);
$$.tdc('currency', tr, fnum(sums.amount_open || 0, $rct.cst));
},
validation: function (msgs) {
@@ -625,7 +681,7 @@ $inv.ccInv = function (ev) { //normale rechnung
rif.tbl.children('tbody').each($inv.bdysort);
rif.tbl.trigger('fds.inv'); /* trigger calculations */
$inv.eM(false, true, 'iss,p13b,setm,ctp');
$inv.eM(false, true, $inv.emOpt(admin));
}, complete: () => {
o.c.trigger('modal_close');
}
@@ -770,7 +826,7 @@ $inv.cntInv = function (data) { //invoice continuation
rif.tbl.children('tbody').each($inv.bdysort);
rif.tbl.trigger('fds.inv'); /* trigger calculations */
$inv.eM(false, true, 'iss,p13b,setm,ctp');
$inv.eM(false, true, $inv.emOpt(response.admin));
}, complete: () => {
o.c.trigger('modal_close');
}
@@ -970,6 +1026,14 @@ $inv.rrw = function () {
if (ph === false) {
bc.unshift($$.dc('ibtn edit', { title: $rct.cP }).append(gi('pencil')).click(rw, $inv.eRow));
bc.push($$.dc('ibtn del', { title: $rct.dR }).append(gi('trash')).click(function (e) { if (confirm($rct.cD)) { rw.remove(); $inv.t_fds_inv(); } }));
/* "Auf Setpreis umstellen": only offered on a Set header item that is not yet
priced from its members (fds__prepInvoice's [SetItmId] is null exactly for a
Set header — either already set-priced, or not yet converted). Irreversible:
sets this row's price to the sum of its members (rows whose [SetItmId] equals
this row's id) and clears each member's price. */
if ((dta.Type || '').toLowerCase() === 'set' && (dta.SetItmId || '') === '') {
bc.push($$.dc('ibtn tosetp', { title: $rct.toSetP }).append(gi('scale')).click(rw, $inv.toSetPrice));
}
}
let axf = $$.dc('axf').append(bc);
if (ph === true) {
@@ -1015,6 +1079,36 @@ $inv.rrw = function () {
rw.empty().attr('class', ph ? 'placeholder' : 'itm').aC(co.Typ).tC('hidenote', hn).append(tda);
dta.co = co;
};
/* "Auf Setpreis umstellen" (see $inv.rrw): sets this Set-header row's price to the sum of
its member rows' totals (rows in the same block whose [SetItmId] equals this row's id,
the authoritative grouping from fds__prepInvoice), then clears each member's price so the
member items show without a price — matching the SetPrice presentation, but as an actual,
irreversible data change instead of a display-mode toggle (INVOICE_SET_PRICING.md's
setmode remains display-only and is unaffected by this). */
$inv.toSetPrice = function (ev) {
let rw = ev.data, dta = rw.data(), id = (dta.Id || '').toString();
if (id === '') { return; }
if (confirm($rct.toSetPc) === false) { return; }
let bdy = rw.closest('tbody'), members = bdy.find('tr.itm').filter(function () {
return (($(this).data('SetItmId') || '').toString()) === id;
});
let sum_net_val = 0, sum_vat_val = 0, sum_svcnet_val = 0, sum_svcvat_val = 0;
members.each(function () {
let m = $(this).data();
sum_net_val += (m.net_val || 0);
sum_vat_val += (m.vat_val || 0);
sum_svcnet_val += (m.svcnet_val || 0);
sum_svcvat_val += (m.svcvat_val || 0);
$.extend(m, { net: 0, net_val: 0, vat_val: 0, svcnet_val: 0, svcvat_val: 0, Discount: 0 });
$inv.rrw.call($(this));
});
$.extend(dta, {
net: sum_net_val, quantityhours: 1, Discount: 0,
net_val: sum_net_val, vat_val: sum_vat_val, svcnet_val: sum_svcnet_val, svcvat_val: sum_svcvat_val
});
$inv.rrw.call(rw);
$inv.t_fds_inv();
};
$inv.invSumUpdate = function () {
let tbl = $(this), ft = tbl.children('tfoot').empty(), p13b = bool((tbl.data().admin || {}).p13b || '', false);
tbl.nextAll('.fnote').remove();
@@ -1541,7 +1635,10 @@ $inv.ccRem_s2 = function (id, sets) { //reminder creation
//console.debug(response);
let rem = response.rm || {};
let rq = $$.dc('srq', frm);
$ict.remt[rem.type].forEach(r => $$[0]('p').rwText(r).appendTo(rq));
/* Intro/closing notice paragraphs are backend-authoritative (see $inv.rd.notes,
populated from FuchsPdf.BuildReminderNotes via rem/dstate after seeding below) —
they are appended/prepended into this same .srq container once the draft session
is open, so no client-side wording is constructed here. */
let rif = $$.tblset({ class: 'invi' }, rq);
rif.ft = $$[0]('tfoot');
rif.tbl.data($.extend({ invid: rem.invid, new: {} }, response));
@@ -1549,7 +1646,6 @@ $inv.ccRem_s2 = function (id, sets) { //reminder creation
$ict.remHR.forEach(h => $$.th(thr, h));
$inv.rRemRw.call($$.tr(rif.bdy), rif.tbl.data());
rif.ft.appendTo(rif.tbl);
$ict.remt2[rem.type].forEach(r => $$[0]('p').rwText(r).appendTo(rq));
let fm = (cls, txt, nme, hd, dta) => {
let fr = $$.dc('inpfrm', frm).aC(cls).append(typeof hd === 'string' ? $$.dc('ahd', hd) : (hd > 0 ? $$.dc('ahd', $rct.frm[nme]) : null)), fc = $$.dc('content', fr).rwText(txt);