Skip to content

Commit 2a1308b

Browse files
os-zhuangclaude
andcommitted
fix(example-crm): make formula fields compute + allow historical closed deals; bump objectui
Release-prep browser test of example-crm surfaced three "passes build, silently wrong at runtime" defects: - Formula fields (`full_name`, `is_closed`, `expected_revenue`) referenced bare field identifiers (`amount`, `status`, `first_name`). Formula scope exposes only `{record, previous, input, os}` with no field flattening, so bare names resolve to nothing and every formula evaluated to null. Rewrite to `record.<field>`. - `expected_revenue` additionally divided by the int literal `100`. cel-js has no `double × int` arithmetic overload, so `<number field> / 100` faults and the formula returns null. Use the float literal `100.0` (both operands `double`). The systemic guardrail for this footgun is tracked separately. - `opp_close_date_not_past` validation fired on every insert, rejecting the legitimate `closed_won`/`closed_lost` seed rows that carry historical close dates (5 of 9 wins were dropped, breaking the pipeline dashboard's "Won This Quarter" KPIs). Scope the rule to open stages only. Also bumps `.objectui-sha` 26da1a2a3731 → ac2de168d487 and rebuilds the bundled @objectstack/console dist for the release. Verified live: opportunities seed 4 → 12; expected_revenue computes (84000, 180000, …); full_name / is_closed populate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent eaeb290 commit 2a1308b

4 files changed

Lines changed: 10 additions & 6 deletions

File tree

.objectui-sha

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
26da1a2a37312669c4ad9cffd3b1fb7714afd74b
1+
ac2de168d48712ce31213a5dae05fb5a29b0c5e2

examples/app-crm/src/objects/contact.object.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const Contact = ObjectSchema.create({
2323
}),
2424
full_name: Field.formula({
2525
label: 'Full Name',
26-
expression: cel`(first_name == null ? '' : first_name + ' ') + last_name`,
26+
expression: cel`(record.first_name == null ? '' : record.first_name + ' ') + record.last_name`,
2727
}),
2828
email: Field.email({
2929
label: 'Email',

examples/app-crm/src/objects/lead.object.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export const Lead = ObjectSchema.create({
7878
/** CEL formula: is this lead in a terminal converted/disqualified state? */
7979
is_closed: Field.formula({
8080
label: 'Is Closed',
81-
expression: cel`status == "converted" || status == "disqualified"`,
81+
expression: cel`record.status == "converted" || record.status == "disqualified"`,
8282
}),
8383
},
8484

examples/app-crm/src/objects/opportunity.object.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ export const Opportunity = ObjectSchema.create({
4545
}),
4646
expected_revenue: Field.formula({
4747
label: 'Expected Revenue',
48-
expression: cel`(amount == null ? 0 : amount) * (probability == null ? 0 : probability) / 100`,
48+
// NOTE: the divisor is the float literal `100.0`, not `100`. cel-js has no
49+
// `double <op> int` arithmetic overload, so `<currency/number field> / 100`
50+
// faults at runtime and the formula silently evaluates to null. Using a
51+
// float literal keeps both operands `double`. See objectstack-formula skill.
52+
expression: cel`(record.amount == null ? 0.0 : record.amount) * (record.probability == null ? 0.0 : record.probability) / 100.0`,
4953
}),
5054
close_date: Field.date({
5155
label: 'Close Date',
@@ -88,9 +92,9 @@ export const Opportunity = ObjectSchema.create({
8892
type: 'cross_field' as const,
8993
name: 'opp_close_date_not_past',
9094
label: 'Close Date Must Be Future',
91-
description: 'Prevent setting close_date to a date in the past on new records.',
95+
description: 'Prevent back-dating the close_date of an OPEN opportunity. Closed (won/lost) deals legitimately carry a historical close date, so they are exempt.',
9296
fields: ['close_date'],
93-
condition: P`has(record.close_date) && record.close_date < now()`,
97+
condition: P`has(record.close_date) && record.close_date < now() && record.stage != "closed_won" && record.stage != "closed_lost"`,
9498
message: 'Close Date must be today or a future date.',
9599
events: ['insert'],
96100
},

0 commit comments

Comments
 (0)