Add user-defined invoice set structure dialog (ADR 0016)
Playwright Tests / test (push) Canceled after 0s
Playwright Tests / test (push) Canceled after 0s
Lets a back-office user correct/define which invoice line belongs to which "set" and how deep it is nested, instead of relying only on the implicit "everything after a Set header until the next Set" grouping fds__prepInvoice derives. Sets may contain sets; every change applies immediately to the cached draft session and is reflected in the editor and PDF (ADR 0006/0008). Backend: - InvoiceSetStructure: pure parent-link forest (assign, change level, create, place/move-sibling, depth-first linearise + level recompute). - InvoiceDraftSetStructureDeltas: new inv/dpatch targets set.assign, set.level, set.create, set.move. - InvoiceSetPricing.Build is now order-preserving and nesting-aware (governed by the nearest converted ancestor); the set-item switch sums a header's transitive descendants so nested conversions never double-count. - FuchsPdf indents nested set members per level, mirroring the editor. Frontend: - New fis.inv_sets.js: "Set-Struktur bearbeiten" dialog — per-block table of all lines with drag & drop reorder (before/after/into), ▲▼ order and ◀▶ level buttons, fold/unfold per set (view-only), and a create bar for new Set/Text lines. Posts one delta per action; the dialog is a pure view of the server state (ADR 0006). - _Layout.cshtml / fis_main.js: cache-bust the dynamically imported per-module bundles (web/fis.<mdl>.<locale>.js/.css) with an asset version derived from the newest bundle write time, so a gulp rebuild is picked up without a stale cached module. Docs: ADR 0016, live-draft-editing concept, INVOICE_SET_PRICING.md, USER_GUIDE.md, ARCHITECTURE.md updated. Tests: InvoiceSetStructureTests (new) + additions to InvoiceDraftServiceTests/InvoiceSetPricingTests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
.emailccfrm {
|
||||
position: absolute;
|
||||
font-size: 0.7rem;
|
||||
top: 2.2rem;
|
||||
left: 0.5rem;
|
||||
width: auto;
|
||||
height: auto;
|
||||
cursor: pointer;
|
||||
|
||||
.content {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* Unlike the other optional fields (plain "-" placeholder), this one names the action —
|
||||
there is no default "Zusatzempfänger" from MFR, so an empty state must read as an
|
||||
offer to add one, not as missing data. */
|
||||
.content:empty::before {
|
||||
content: 'Zusatzempfänger hinzufügen';
|
||||
min-width: 5rem;
|
||||
height: 1rem;
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
div.axf {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.admfrm {
|
||||
position: absolute;
|
||||
right: 3rem;
|
||||
@@ -250,6 +281,12 @@
|
||||
&.sethdr td.txt {
|
||||
font-weight: bold;
|
||||
}
|
||||
/* Nested set members (ADR 0016): indent per level, mirroring the PDF's per-level indent. */
|
||||
@for $i from 1 through 6 {
|
||||
&.lvl#{$i} td.txt {
|
||||
padding-left: calc(0.4rem + #{$i * 1.4rem});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> tfoot > tr {
|
||||
@@ -493,3 +530,71 @@
|
||||
color: #445;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Set structure dialog (ADR 0016, see $inv.sets in fis.inv_sets.js) ─────────────────── */
|
||||
.modal .setstruct {
|
||||
&.busy { opacity: 0.6; pointer-events: none; }
|
||||
|
||||
div.shint { font-size: 0.85rem; color: #666; margin: 0 0 0.8rem 0; }
|
||||
|
||||
div.sblock {
|
||||
margin: 0 0 1.4rem 0;
|
||||
|
||||
div.sbhd {
|
||||
font-weight: bold;
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-top: 1.5px solid #000;
|
||||
border-bottom: 1px solid #ddd;
|
||||
&.dropok { background-color: #e6f2e6; outline: 2px dashed #5a5; }
|
||||
}
|
||||
|
||||
table.stbl {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
th { text-align: left; font-size: 0.8rem; color: #666; padding: 0.3rem 0.4rem; border-bottom: 1px solid #ddd; }
|
||||
td { padding: 0.3rem 0.4rem; border-bottom: 1px solid #f0f0f0; vertical-align: middle; }
|
||||
td.grip { width: 1.6rem; cursor: grab; color: #999; }
|
||||
td.pos, td.qty { width: 4rem; white-space: nowrap; }
|
||||
td.typ { width: 7rem; white-space: nowrap; color: #666; font-size: 0.85rem;
|
||||
span.fold { cursor: pointer; margin-right: 0.35rem; color: #555; display: inline-block; width: 1rem; }
|
||||
}
|
||||
td.ttl {
|
||||
span.ind { display: inline-block; }
|
||||
span.seti { margin-right: 0.3rem; color: #888; }
|
||||
span.cnt { color: #999; font-weight: normal; font-size: 0.85rem; }
|
||||
}
|
||||
td.act { width: 7.5rem; white-space: nowrap; text-align: right;
|
||||
div.ibtn { display: inline-block; cursor: pointer; padding: 0 0.25rem; &.disabled { opacity: 0.25; cursor: default; } }
|
||||
}
|
||||
|
||||
tr.srow {
|
||||
&.isset td.ttl { font-weight: bold; }
|
||||
&.hid { display: none; }
|
||||
&.folded td.ttl { font-style: italic; }
|
||||
&.dragging { opacity: 0.4; }
|
||||
&.dropok td { background-color: #e6f2e6; }
|
||||
&.dropbefore td { border-top: 3px solid #5a5; }
|
||||
&.dropafter td { border-bottom: 3px solid #5a5; }
|
||||
}
|
||||
}
|
||||
|
||||
div.sroot {
|
||||
margin: 0.3rem 0 0.5rem 0;
|
||||
padding: 0.4rem 0.6rem;
|
||||
border: 1px dashed #bbb;
|
||||
color: #888;
|
||||
font-size: 0.85rem;
|
||||
&.dropok { background-color: #e6f2e6; border-color: #5a5; color: #363; }
|
||||
}
|
||||
|
||||
div.screate {
|
||||
display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem;
|
||||
padding: 0.4rem 0.6rem;
|
||||
background-color: #f7f7f7;
|
||||
span.lbl { font-size: 0.85rem; color: #666; }
|
||||
select, input[type="text"] { padding: 0.2rem 0.3rem; }
|
||||
input[type="text"] { flex: 1 1 14rem; min-width: 10rem; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user