Skip to content

Allow a calculated action on a to-one relation - #6687

Open
delchev wants to merge 1 commit into
masterfrom
feat/intent-calculated-action-on-relation
Open

Allow a calculated action on a to-one relation#6687
delchev wants to merge 1 commit into
masterfrom
feat/intent-calculated-action-on-relation

Conversation

@delchev

@delchev delchev commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

A to-one relation could not express a DERIVED default. init: covers the fixed case (a literal target seed id), but nothing covered "read it off another record" — a sales document's currency defaulting from its company's base currency. The adjacent hooks all fall short by design: dependsOn is a UI-only cascade that never fires on a server-side create, and the setRelationField process step takes a literal id too.

Authoring calculatedActionOnCreate on a relation looked like it should work and silently did nothing: the typed mapping is Gson, which ignores unknown properties, so the key was dropped at parse time with no error — the #6541 family, one level up from seed rows.

Changes

  • RelationIntent gains calculatedActionOnCreate/OnUpdate + isCalculated().
  • New shared EdmIntentGenerator.putCalculatedAction emits isCalculatedProperty plus the action names from BOTH relation builders (same-model relationProperty and crossModelRelationProperty).
  • The DAO template needed NO change: a relation is already an ordinary property in the .model — the FK column, typed to the target's key — so the template's shared property loop and its actionAssign macro emit entity.<Relation> = Beans.get(<class>.class).calculate(entity); against the Integer FK with no new branch. The gap was engine-only, so consuming it needs a jar swap and no registry re-seed.
  • The parser rejects the combinations where there is no single FK to assign or the value is not the author's to set: a collection relation, a composition parent (preset by the layout), and an EntityStatus badge (owned by the workflow transitions — init: is its starting-value hook).

Tests

  • EdmIntentGeneratorTest asserts the three emitted keys on both builders and that an action-less relation stays unmarked.
  • IntentParserTest asserts the key binds onto the model (the typed-mapping test is the regression guard for the silent drop) and that a collection relation is rejected.
  • Full engine-intent suite: 406 green.
  • Verified at the runtime layer against a live instance: five document repositories emit the call; a REST create omitting Currency defaults it from the company's base currency, an explicitly posted Currency is respected, and a defaulted invoice + a currency-less payment auto-allocate end to end (ISSUED → PAID).

Owed: an IntentEmissionCoverageIT case — the fixture has no calculated action of either kind today, and adding one needs a compilable custom/ class in the emission-test project; left for a change that can run that gate.

🤖 Generated with Claude Code

A to-one relation could not express a DERIVED default. `init:` covers the fixed
case (a literal target seed id), but nothing covered "read it off another record" -
a sales document's currency defaulting from its company's base currency. The
adjacent hooks all fall short by design: `dependsOn` is a UI-only cascade that
never fires on a server-side create, and the `setRelationField` process step takes
a literal id too.

Authoring `calculatedActionOnCreate` on a relation looked like it should work and
silently did nothing: the typed mapping is Gson, which ignores unknown properties,
so the key was dropped at parse time with no error - the #6541 family, one level
up from seed rows.

RelationIntent gains calculatedActionOnCreate/OnUpdate + isCalculated(), and the
new shared EdmIntentGenerator.putCalculatedAction emits isCalculatedProperty plus
the action names from BOTH relation builders (same-model relationProperty and
crossModelRelationProperty).

The DAO template needed NO change, which is the point worth recording: a relation
is already an ordinary property in the .model - the FK column, typed to the
target's key - so the template's shared property loop and its actionAssign macro
emit `entity.<Relation> = Beans.get(<class>.class).calculate(entity);` against the
Integer FK with no new branch. The gap was engine-only, so consuming it needs a jar
swap and no registry re-seed.

The parser rejects the combination where there is no single FK to assign, or where
the value is not the author's to set: a collection relation, a composition parent
(preset by the layout) and an EntityStatus badge (owned by the workflow
transitions - `init:` is its starting-value hook).

Tests: EdmIntentGeneratorTest asserts the three emitted keys on both builders and
that an action-less relation stays unmarked; IntentParserTest asserts the key binds
onto the model (the typed-mapping test is the regression guard for the silent drop)
and that a collection relation is rejected. Full engine-intent suite 406 green.

NOT done, owed: an IntentEmissionCoverageIT case. That fixture has no calculated
action of either kind today, and adding one needs a compilable custom/ class in the
emission-test project - a generated repository referencing a missing class fails the
whole client-Java batch and with it every REST assertion in that gate. Left for a
change that can run the gate rather than guessed at.
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.

1 participant