Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions examples/app-showcase/src/data/objects/invoice.object.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,6 +185,40 @@ export const InvoiceLine = ObjectSchema.create({
// Thin, high-volume line items → the editable grid form factor.
inlineEdit: 'grid',
inlineTitle: 'Line Items',
/**
* The inline grid's columns, declared EXPLICITLY rather than auto-derived
* — and the declaration is load-bearing, not decoration.
*
* Auto-derivation curates: past `DEFAULT_MAX_INLINE_COLUMNS` (6) it keeps
* the primary + every required column, fills the rest by type usefulness,
* and marks the overflow `defaultHidden` (the column chooser reveals it).
* This line had exactly 6 editable fields, so nothing was curated. Adding
* `service_start` below makes 7, and the one that loses the tie-break is
* `receipt` — a `file` column, which the fill-priority table does not
* rank, so it sorts last. That column is objectui#2360's upload-in-grid
* fixture: demoting it out of the default view is precisely the cost that
* kept `time` out of objectui#3569, and it is not a cost worth paying.
*
* Declaring the set opts out of curation entirely (`deriveDetail` routes
* an author-supplied set through `hydrateColumns`, which never sets
* `defaultHidden`), so all seven stay default-visible and `receipt`'s
* visibility stops depending on a tie-break it happens to be losing.
*
* Bare `{ field }` entries on purpose: `hydrateColumns` fills label, type,
* options, lookup target, `readonlyWhen`/`requiredWhen` and the computed
* `expression` from the schema, so labels stay translatable and the
* columns cannot drift from the field definitions above. `position` is
* absent because it is the grid's drag-reorder sort field, never a cell.
*/
inlineColumns: [
{ field: 'product' },
{ field: 'description' },
{ field: 'service_start' },
{ field: 'quantity' },
{ field: 'unit_price' },
{ field: 'receipt' },
{ field: 'amount' },
],
}),
// Catalog lookup. Picking a product auto-fills `description` + `unit_price`
// (the grid copies same-named fields from the selected product record).
Expand All@@ -211,6 +245,29 @@ export const InvoiceLine = ObjectSchema.create({
maxLength: 200,
requiredWhen: P`record.quantity >= 100`,
}),
/**
* Clock time the billed work started — the inline grid's `time` fixture
* (objectui#3569). ⛔ Do not "tidy" this field away: it is the ONLY place in
* showcase where a `time` field sits inside an inline-edit grid, and the
* grid's time control has no other real-machine coverage. `field_zoo.f_time`
* seeds a `time` value but is not a master-detail child, so it never reaches
* this rendering path.
*
* objectui#3569 split `date` / `datetime` / `time` into three grid controls;
* a renderer that folds `time` back onto the `date` control feeds `HH:mm`
* into an `<input type="date">`, which shows nothing and writes the clock
* out of the record on the next save. Only a running grid can catch that.
*
* Honest on a T&M line, and deliberately scoped: a services line bills hours
* (`quantity`) at a rate (`unit_price`), so a start clock plus that duration
* describes the whole billed window — no second `service_end` field that
* would only restate it. Goods lines leave it empty, which is also the
* fixture's empty-cell case.
*
* Authored as a literal `{ type: 'time' }` because there is no `Field.time`
* builder — the same form `showcase_field_zoo.f_time` uses.
*/
service_start: { type: 'time', label: 'Service Start' },
quantity: Field.number({
label: 'Qty',
required: true,
Expand Down
14 changes: 12 additions & 2 deletions examples/app-showcase/src/data/seed/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -383,15 +383,25 @@ const invoices = defineSeed(Invoice, {
// Line items — `product` resolves by SKU (the Product seed's externalId), and
// `invoice` by invoice number. A contributor reaches a line only through its
// master invoice, so these inherit the invoice's owner scoping.
//
// `service_start` is the inline grid's `time` FIXTURE (objectui#3569) - the one
// place in showcase where a `time` field is rendered by an inline-edit grid.
// DO NOT blank these clocks in a seed tidy-up: a zeroed or missing value leaves
// the fixture unable to show the defect it exists to catch (a `time` column
// folded onto the `date` control renders empty and writes the clock out of the
// record on the next save), so the seeded values are deliberately NON-ZERO and
// recognisable. Only the T&M service lines carry one - hours billed from a
// start clock; the goods lines are empty on purpose, which is the fixture's
// empty-cell case.
const invoiceLines = defineSeed(InvoiceLine, {
mode: 'upsert',
externalId: 'description',
records: [
{ description: 'INV-1001 \u00b7 Consulting hours', invoice: 'INV-1001', product: 'SERVICE-HR', position: 0, quantity: 10, unit_price: 150, amount: 1500 },
{ description: 'INV-1001 \u00b7 Consulting hours', invoice: 'INV-1001', product: 'SERVICE-HR', position: 0, quantity: 10, unit_price: 150, amount: 1500, service_start: '09:15' },
{ description: 'INV-1001 \u00b7 Widget A units', invoice: 'INV-1001', product: 'WIDGET-A', position: 1, quantity: 4, unit_price: 29.99, amount: 119.96 },
{ description: 'INV-1002 \u00b7 Gadget X units', invoice: 'INV-1002', product: 'GADGET-X', position: 0, quantity: 2, unit_price: 99, amount: 198 },
{ description: 'INV-1003 \u00b7 Widget B units', invoice: 'INV-1003', product: 'WIDGET-B', position: 0, quantity: 6, unit_price: 49.99, amount: 299.94 },
{ description: 'INV-1004 \u00b7 Consulting hours', invoice: 'INV-1004', product: 'SERVICE-HR', position: 0, quantity: 3, unit_price: 150, amount: 450 },
{ description: 'INV-1004 \u00b7 Consulting hours', invoice: 'INV-1004', product: 'SERVICE-HR', position: 0, quantity: 3, unit_price: 150, amount: 450, service_start: '13:40' },
],
});

Expand Down
20 changes: 20 additions & 0 deletions examples/app-showcase/src/system/translations/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -140,6 +140,19 @@ export const ShowcaseTranslationBundle = {
incurred_at: { label: 'Incurred At' },
},
},
// Same rule, same reason as `showcase_expense_line` above: `service_start`
// is a NEW declared label (objectui#3569's inline-grid TIME fixture), so
// it must be translated at birth or check-i18n-coverage sees the example's
// untranslated count grow and fails. And DELIBERATELY only this one field
// — `product`, `description`, `quantity`, `unit_price`, `receipt` and
// `amount` predate the ratchet and are part of the frozen baseline;
// translating them here would push the count BELOW the baseline, which the
// same gate rejects as an un-ratcheted improvement.
showcase_invoice_line: {
fields: {
service_start: { label: 'Service Start' },
},
},
showcase_preference: {
label: 'Setting',
pluralLabel: 'Settings',
Expand DownExpand Up@@ -444,6 +457,13 @@ export const ShowcaseTranslationBundle = {
incurred_at: { label: '发生时间' },
},
},
// See the `en` side for why this entry translates exactly ONE field and
// no more (check-i18n-coverage is a two-sided ratchet).
showcase_invoice_line: {
fields: {
service_start: { label: '服务开始时间' },
},
},
showcase_preference: {
label: '设置',
pluralLabel: '设置',
Expand Down
Loading