Skip to content

Commit 960f24d

Browse files
os-zhuangclaude
andauthored
fix(hr): correct document-expiry date formulas — CEL has no Timestamp+int (#93)
`hr_document.is_expiring_soon` and `expiry_status` used `today() + 30` / `today() + 60` to build the reminder windows. The formula engine is CEL, where dates are Timestamps and there is no `Timestamp + int` operator — so `today() + 30` evaluated to null and every comparison against it silently failed. Net effect: `is_expiring_soon` was always false and `expiry_status` never returned the `expiring_30d` / `expiring_60d` bands (only expired / valid / none), so the expiry-reminder flow had nothing to fire on. Fix — use the `daysFromNow(n)` builtin (which returns the Timestamp n days out) instead of `today() + n`: expires_at <= daysFromNow(30) // was (today() + 30) expires_at <= daysFromNow(60) // was (today() + 60) Boot-verified against a fresh dev server (@objectstack 15.1.1, 0 errors) — the seeded documents now band correctly: - expires in ~15d → is_expiring_soon = true, expiry_status = expiring_30d - expires next year → is_expiring_soon = false, expiry_status = valid - expired 10d ago → is_expired = true, expiry_status = expired - no expiry date → expiry_status = none Note: `hr_employee.tenure_years` has a related but distinct defect (`floor((today() - hire_date) / 365)` — CEL has neither `Timestamp - Timestamp` → number nor a `floor` builtin, and the intended `daysBetween()` primitive is declared in @objectstack/formula's type list but not wired into the runtime, so it returns null). That one can't be fixed at the template level without a working date-diff builtin, so it is intentionally left for a framework follow-up rather than worked around with an unreadable bucketed ternary. Claude-Session: https://claude.ai/code/session_01BZguyAaQbyUpwMZ2gMLaAP Co-authored-by: Claude <noreply@anthropic.com>
1 parent 93a5477 commit 960f24d

1 file changed

Lines changed: 7 additions & 2 deletions

File tree

packages/hr/src/objects/hr_document.object.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ export const EmployeeDocument = ObjectSchema.create({
4848
}),
4949
is_expiring_soon: Field.formula({
5050
label: 'Expiring Soon?',
51-
expression: F`record.expires_at != null && record.expires_at >= today() && record.expires_at <= (today() + 30)`,
51+
// `daysFromNow(30)`, not `today() + 30`: the CEL formula engine has no
52+
// `Timestamp + int` operator, so `today() + 30` evaluates to null and the
53+
// whole comparison silently fails. Day offsets go through the builtin.
54+
expression: F`record.expires_at != null && record.expires_at >= today() && record.expires_at <= daysFromNow(30)`,
5255
}),
5356
is_expired: Field.formula({
5457
label: 'Expired?',
@@ -57,7 +60,9 @@ export const EmployeeDocument = ObjectSchema.create({
5760
expiry_status: Field.formula({
5861
label: 'Expiry Status',
5962
description: 'Single severity band for sorting/colour-coding the document list.',
60-
expression: F`record.expires_at == null ? "none" : (record.expires_at < today() ? "expired" : (record.expires_at <= (today() + 30) ? "expiring_30d" : (record.expires_at <= (today() + 60) ? "expiring_60d" : "valid")))`,
63+
// `daysFromNow(N)`, not `today() + N`: CEL has no `Timestamp + int` operator
64+
// (see is_expiring_soon above), so the offset bands must use the builtin.
65+
expression: F`record.expires_at == null ? "none" : (record.expires_at < today() ? "expired" : (record.expires_at <= daysFromNow(30) ? "expiring_30d" : (record.expires_at <= daysFromNow(60) ? "expiring_60d" : "valid")))`,
6166
}),
6267
notes: Field.markdown({ label: 'Notes' }),
6368
},

0 commit comments

Comments
 (0)