intent: a duplicable document says which fields a copy resets or defaults (#7358) - #7360
Merged
Merged
Conversation
…ults (#7358) `duplicable: true` cloned the source header verbatim, so every ordinary user field rode along - including the ones a business rule says must be fresh. On a sales invoice the copy carried the source's `date`, `due` and `taxEventDate`, so "same invoice as last month" opened as a draft dated last month, due last month, with last month's tax event; an unnoticed one is a legally wrong document. The module could not fix it on its own side: the two create-time actions fill an EMPTY value and respect a present one by contract - the right contract for a hand-made invoice, and exactly what makes the copied value stick - and `date` had no rule at all. `duplicable` now also takes the object form duplicable: defaults: { date: now } # constants written into the clone reset: [due, taxEventDate] # dropped, so the create-time rule refills them `reset` hands a field back to the create path, which fills it exactly as it would on a hand-made document; `defaults` writes a constant, with the same `now` token and the same field-shape rendering `generates.defaults` has (`date` -> YYYY-MM-DD, a `month` field -> YYYY-MM, a `week` field -> YYYY-Www). The shorthand is normalized to the empty object on the raw tree (the `expandUniqueShorthand` precedent), so one typed class carries both forms, the unknown-key walk sees the two keys, and an entity that says nothing generates byte-identically. Both halves travel as STRUCTURED `.edm` attributes (`duplicateReset`, `duplicateDefaults`), because entity metadata a flat attribute cannot carry is lost on the next modeler save (#6826) - which here would silently put the copied dates back. `todayAs` builds from the local calendar fields, never `toISOString()`: east of Greenwich that is yesterday after the evening cut-over. Refused at parse, each because accepting it would be silent: a name that is neither a field nor a to-one of the entity; one of the built-in drops (identity, the `number:` field, the `function: EntityStatus` relation, a `readOnly` or an `aggregate` field), which the Duplicate decided long before reading this block; the same name in both lists; `now` on a property that is not a date / month / week; and a `reset` of a required field with no `defaultValue` and no create-time rule, which would make every duplicate fail on the server's own "field is required". Verified: DuplicableIntentTest (16 cases), EdmIntentGeneratorTest's two new emission cases, the engine-intent unit suite (1280), and both ITs - IntentEmissionCoverageIT, which asserts the generated document page carries the delete and the two `todayAs` assignments, and EdmModelRoundTripIT, which proves both keys survive .intent -> .edm -> .model. Fixes #7358 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 12, 2026
delchev
added a commit
to dirigible-io/dirigible-io.github.io
that referenced
this pull request
Sep 12, 2026
…(#7358) (#245) `duplicable: true` clones the source header verbatim, so a copied invoice keeps the source's date, due date and tax-event date - and a `calculatedActionOnCreate` cannot repair it, because those fill an empty value and respect a present one. Document the object form (`defaults:` + `reset:`), the `now` token's per-field shape, what stays copied, and the seven parse refusals. Platform PR: eclipse-dirigible/dirigible#7360 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
duplicable: trueis a bare boolean. The generated Duplicate clones the source header verbatim - minus identity, audit, status, number, read-only and aggregate fields - and posts the rest through the normal create path, so every ordinary user field rides along, including the ones a business rule says must be fresh. On a sales invoice the copy therefore carries the source'sdate,dueandtaxEventDate: "same invoice as last month" opens as a draft dated last month, due last month, with last month's tax event. The user has to notice and fix three dates by hand, and an unnoticed one is a legally wrong document (the tax-event date is mandatory on BG invoices).The module could not fix this on its own side. The two create-time actions on the invoice (
dueandtaxEventDate, bothcalculatedActionOnCreate) fill an empty value and respect a present one by contract - the right contract for a hand-made invoice, and exactly what makes the copied value stick - anddateis a plainrequiredfield with no default at all.The change
duplicablekeeps the shorthand and also accepts an object form:reset:hands a field back to the create path, which fills it exactly as it would on a hand-made document;defaults:writes a constant, with the samenowtoken and the same field-shape renderinggenerates.defaultsalready has (date->YYYY-MM-DD, amonthfield ->YYYY-MM, aweekfield ->YYYY-Www). They compose and never overlap -resetfor a field that HAS a create-time rule,defaultsfor one that does not. Everything not named is copied, as today. The built-in drops stay exactly as they were; they were never authorable.Three things worth reviewing:
IntentParser.normalizeDuplicable, theexpandUniqueShorthand/moveGeneratesItemLinesprecedent):truebecomes the empty mapping,falseis removed. One typed class (DuplicateIntent) then carries both forms, the unknown-key walk seesdefaults/resetas declared fields, and Gson never has to map a boolean onto an object.EntityIntent.duplicableis therefore aDuplicateIntent, not aBoolean;isDuplicable()is unchanged for every caller..edmattributes (duplicateReset,duplicateDefaults- registered inEdmIntentGenerator.STRUCTURED_ATTRIBUTESand intransform-edm.js'sENTITY_STRUCTURED). Entity metadata a flat attribute cannot carry is dropped on the.edm -> .modelrebuild (The .edm must stay a lossless source for the derived .model: saving a diagram drops rollupGuard/checks/labelParts/uniqueConstraints, and the editor serializer's allow-list drops the intent-era attributes wholesale #6826), and here that loss would silently put the copied dates back on the first unrelated modeler save.todayAsbuilds from the LOCAL calendar fields, nevertoISOString(): that is UTC, so east of Greenwich every copy made after the evening cut-over would be dated yesterday.Refused at parse
Each because accepting it would be silent:
number:field, thefunction: EntityStatusrelation, areadOnlyor anaggregatefield - which the Duplicate decided long before reading this block, so naming it would let an author believe they control something they do not;resetanddefaults(a contradiction);nowon a property that is not a date / month / week;reseton a required field with neither adefaultValuenor a create-time rule, which would make every duplicate fail with the server's own "field is required";duplicablescalar that is neithertrue/falsenor a mapping.Verified
DuplicableIntentTest- 16 cases: the shorthand still parses and carries no rules,falseis not duplicable, the object form parses, and each refusal above has a case asserting its message.EdmIntentGeneratorTest- two new cases: the object form emits the resets (as generated property names) and the defaults ({name, shape, js},nowresolved per field shape, a string literal quoted) and the.edmcarries both as JSON attributes; the shorthand emits neither key.IntentEmissionCoverageIT(green, 149s) - a newReorderdocument with the object form; the assertions read the generated document page and requiredelete header['Note'];,header['OrderedOn'] = this.todayAs('date');,header['Period'] = this.todayAs('month');,header['Comment'] = "Copy";and a local-calendartodayAs.EdmModelRoundTripIT(green, both tests) - a duplicable document added to the fixture; both keys are asserted present in the.edm, structurally equal after the.edm -> .modelrebuild, and covered by the whole-documentassertEveryKeySurvivesdiff.mvn formatter:validatewith the cache wiped, and the-P releasejavadoc build on engine-intent: no errors.Out of scope (deliberately not done here)
<Entity>Duplicateglue controller in the pattern of<Entity>Generatewould fix that - separate issue.defaultValue: nowon date fields (it would letreset: [date]replacedefaults: { date: now }) - worth its own issue.Fixes #7358
🤖 Generated with Claude Code