Summary
#7164 (#7091) added items: where: + refuse: to a create-from. Both refusals - "an unqualified row exists" and "no row qualifies, the target would have no lines" - are evaluated AFTER the target header has been saved. The header save is inside the UnitOfWork, so the row itself is rolled back - but two things the generated repository does inside save() are, by the #7069 design, deliberately OUTSIDE the unit: the document-number allocation and the History create entry. Every hit of the rule the author declared therefore burns a number of a "continuous, never auto-reset" series and leaves a history row for a document that never existed. Pressing "Invoice this month" on a month with one rejected timesheet, three times, consumes three invoice numbers.
Evidence (origin/master)
components/template/template-application-events-java/src/main/resources/META-INF/dirigible/template-application-events-java/events/Generate.java.template:
- line 150:
UnitOfWork.call(() -> {
- line 223:
... ${toEntity}Repository().save(target); (the header)
- lines 225-235: the
where:-narrowed item query (.eq("${srcFkProperty}", sourceId)${itemWhere})
- lines 236-257: the
refuse: scan and throw new ValidationException("${itemRefuse} (${fromItemEntity} " + unqualified + ")")
- lines 260-265:
throw new ValidationException("no ${fromItemEntity} row of the ${fromEntity} qualifies ... would have no lines")
components/template/template-application-dao-java/.../data/Repository.java.template, inside save(...) (line 331): line 390/392 DocumentNumbers.next(...), line 405 History.recordCreate(...). Per .claude/docs/client-java.md ("The History trail and document numbering deliberately stay outside" the unit) neither is undone by the rollback.
Fix
Both refusal queries depend only on sourceId and the source rows. Run them BEFORE the header is built and saved (before line 223, or before entering the unit at 150), so a refused click writes nothing at all - which is also what the template's own comment at line 98 promises for the prompt: refusal ("refused BEFORE anything is written"). The IntentGeneratesItemsWhereIT (or whatever the behavioural test is) should assert the series counter and the history table are unchanged after a refused run.
Found reviewing #7164.
Summary
#7164 (#7091) added
items: where:+refuse:to a create-from. Both refusals - "an unqualified row exists" and "no row qualifies, the target would have no lines" - are evaluated AFTER the target header has been saved. The header save is inside theUnitOfWork, so the row itself is rolled back - but two things the generated repository does insidesave()are, by the #7069 design, deliberately OUTSIDE the unit: the document-number allocation and theHistorycreate entry. Every hit of the rule the author declared therefore burns a number of a "continuous, never auto-reset" series and leaves a history row for a document that never existed. Pressing "Invoice this month" on a month with one rejected timesheet, three times, consumes three invoice numbers.Evidence (origin/master)
components/template/template-application-events-java/src/main/resources/META-INF/dirigible/template-application-events-java/events/Generate.java.template:UnitOfWork.call(() -> {... ${toEntity}Repository().save(target);(the header)where:-narrowed item query (.eq("${srcFkProperty}", sourceId)${itemWhere})refuse:scan andthrow new ValidationException("${itemRefuse} (${fromItemEntity} " + unqualified + ")")throw new ValidationException("no ${fromItemEntity} row of the ${fromEntity} qualifies ... would have no lines")components/template/template-application-dao-java/.../data/Repository.java.template, insidesave(...)(line 331): line 390/392DocumentNumbers.next(...), line 405History.recordCreate(...). Per.claude/docs/client-java.md("TheHistorytrail and document numbering deliberately stay outside" the unit) neither is undone by the rollback.Fix
Both refusal queries depend only on
sourceIdand the source rows. Run them BEFORE the header is built and saved (before line 223, or before entering the unit at 150), so a refused click writes nothing at all - which is also what the template's own comment at line 98 promises for theprompt:refusal ("refused BEFORE anything is written"). TheIntentGeneratesItemsWhereIT(or whatever the behavioural test is) should assert the series counter and the history table are unchanged after a refused run.Found reviewing #7164.