Skip to content

Commit 1dd84c3

Browse files
os-zhuanghotlongclaude
authored
feat(showcase): lock invoice lines when the invoice is paid (#1581) (#1666)
Add parent-scoped readonlyWhen rules to showcase_invoice_line's product, quantity and unit_price: readonlyWhen "parent.status == 'paid'". The inline line-item grid evaluates these per row against the live header invoice, so a paid invoice's lines render read-only — the "paid invoice → lock lines" case. Pairs with objectui#1607 (MasterDetailForm binds the header as `parent`). Co-authored-by: Jack <zhuangjianguo@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c7427aa commit 1dd84c3

1 file changed

Lines changed: 25 additions & 6 deletions

File tree

examples/app-showcase/src/objects/invoice.object.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,20 +116,39 @@ export const InvoiceLine = ObjectSchema.create({
116116
// Line sort position — stamped by the grid on drag-reorder so the order
117117
// persists. Excluded from the editable columns (it's not hand-entered).
118118
position: Field.number({ label: 'Position', defaultValue: 0 }),
119-
product: Field.lookup('showcase_product', { label: 'Product', required: true }),
119+
// Conditional rule (B2 in grids, PARENT-scoped): once the header invoice is
120+
// Paid, its lines are frozen. `readonlyWhen` here references the header as
121+
// `parent`, so the inline grid evaluates it per row against the live invoice
122+
// record and locks the cell — the "paid invoice → lock lines" case (#1581).
123+
product: Field.lookup('showcase_product', {
124+
label: 'Product',
125+
required: true,
126+
readonlyWhen: "parent.status == 'paid'",
127+
}),
120128
// Conditional rule (B2 in grids): a bulk line (large quantity) must carry a
121129
// description note. `requiredWhen` here is ROW-scoped — it references the
122130
// line's own `record`, so the inline grid flags this cell required per row
123-
// as the quantity crosses the threshold. (Row-scoped rules need no header
124-
// context; a header-driven lock like "paid invoice → lock lines" is a
125-
// separate, deferred capability — see ADR-0036.)
131+
// as the quantity crosses the threshold. (A header-driven lock referencing
132+
// `parent` — see `product`/`quantity`/`unit_price` — is the parent-scoped
133+
// counterpart; both are evaluated by the inline grid. See ADR-0036 / #1581.)
126134
description: Field.text({
127135
label: 'Description',
128136
maxLength: 200,
129137
requiredWhen: 'record.quantity >= 100',
130138
}),
131-
quantity: Field.number({ label: 'Qty', required: true, min: 0, defaultValue: 1 }),
132-
unit_price: Field.currency({ label: 'Unit Price', scale: 2, min: 0 }),
139+
quantity: Field.number({
140+
label: 'Qty',
141+
required: true,
142+
min: 0,
143+
defaultValue: 1,
144+
readonlyWhen: "parent.status == 'paid'",
145+
}),
146+
unit_price: Field.currency({
147+
label: 'Unit Price',
148+
scale: 2,
149+
min: 0,
150+
readonlyWhen: "parent.status == 'paid'",
151+
}),
133152
// Amount = Qty × Unit Price. Kept as a *stored* currency column (so the
134153
// parent Invoice.total summary can roll it up — summary aggregation reads
135154
// stored columns, not on-read formula fields), but the `expression` makes

0 commit comments

Comments
 (0)