Skip to content

Field.formula({ scale: 2 }) is not applied: a percentage formula stores and returns 41.666666666666664, and the record page prints all 15 digits #10280

Description

@os-zhuang

Field.formula({ scale: 2 }) is not applied: a percentage formula stores and returns 41.666666666666664, and the record page prints all 15 digits

Measured on 17.1.0 (open edition, objectstack dev), HotCRM's crm_campaign.

The declaration

src/objects/campaign.object.ts:

response_rate: Field.formula({label: 'Response Rate %',expression: F`coalesce(record.num_sent, 0) > 0 ? (coalesce(record.num_responses, 0) * 100.0) / record.num_sent : 0.0`,scale: 2,}),

What the runtime returns

GET /api/v1/data/crm_campaign/<id> with num_sent: 12, num_responses: 5:

{"response_rate": 41.666666666666664, "roi": 400, "num_sent": 12, "num_responses": 5}

scale: 2 is declared and has no effect — the raw double is what is stored/returned, so every consumer inherits it. The record page renders it verbatim:

效果数据
响应率(%)
41.666666666666664

That is what an end user sees on the campaign page, next to 预算成本 28,000 and 投资回报率(%) 400. It reads like a bug in the product to them, and there is nothing an app author can do about it declaratively: they did declare the scale.

Why it matters beyond cosmetics

A formula field's scale is the only declarative rounding an author has. If it is inert:

  • every division-shaped formula (rates, ratios, per-unit prices, averages) is a coin flip on how it prints;
  • values that flow onward into a stored money/decimal field hit that field's own decimal validation and get rejected — the same tail that makes x * (d/100) unwritable to a 2-decimal money column. So an inert scale is not only display noise, it can turn a downstream write into a hard failure.

Expected

Either the engine applies scale when evaluating the formula (round-half-up to N decimals before returning/persisting), or scale is rejected at author time on Field.formula so authors know they must round inside the expression. Silently accepting a rounding declaration and not rounding is the shape that costs an app author a production incident.

crm_campaign.roi carries the same scale: 2 and happens to divide evenly in the seed data, so it looks fine — that is the accident, not the rule.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:enginepriority:p1High: required for production / M2

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions