Skip to content

fix(hr): correct document-expiry date formulas — CEL has no Timestamp+int#93

Merged
os-zhuang merged 1 commit into
mainfrom
claude/fix-hr-date-formulas
Jul 19, 2026
Merged

fix(hr): correct document-expiry date formulas — CEL has no Timestamp+int#93
os-zhuang merged 1 commit into
mainfrom
claude/fix-hr-date-formulas

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Bug(与 compliance #92 同一类根因)

hr_document.is_expiring_soon / expiry_statustoday() + 30 / today() + 60 来算提醒窗口。formula 引擎是 CEL,日期是 Timestamp,没有 Timestamp + int 运算符 —— 所以 today() + 30 求值为 null,针对它的比较全部静默失败。结果:is_expiring_soon 永远是 false,expiry_status 永远返回不了 expiring_30d / expiring_60d 两档(只会给 expired / valid / none)—— 到期提醒 flow 因此没有东西可触发。

修复

daysFromNow(n) builtin(返回 n 天后的 Timestamp)替掉 today() + n:

expires_at <= daysFromNow(30)   // was (today() + 30)
expires_at <= daysFromNow(60)   // was (today() + 60)

验证(fresh dev server,@objectstack 15.1.1,0 错误)

seed 里的文档现在分档正确:

expires_at is_expiring_soon expiry_status
~15 天后 true expiring_30d
明年 false valid
10 天前 (is_expired=true) expired
none

关于 hr_employee.tenure_years(本 PR 未改)

它有一个相关但不同的缺陷:floor((today() - hire_date) / 365) —— CEL 里 Timestamp - Timestamp 不是数字、也没有 floor builtin,而本该用的 daysBetween() 原语虽然在 @objectstack/formula 的类型清单里声明了,却没有实际 wire 进运行时(实测调用返回 null)。所以这个字段在模板层面修不了(除非拿一个十几层的 bucket 三元表达式硬凑,不适合作参考代码)。已在 commit message 里留了说明,建议作为 framework 侧 follow-up(把 daysBetween 接上,或补一个可用的 date-diff builtin)。

影响

纯模板改动,不改 framework。修 2 个到期公式;tenure 留给 framework。


Generated by Claude Code

…+int

`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.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BZguyAaQbyUpwMZ2gMLaAP
@os-zhuang
os-zhuang merged commit 960f24d into main Jul 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants