Skip to content

Commit d0ed5f1

Browse files
delchevclaude
andauthored
feat(intent): generate create-time numbering from number: {} (N3b-i) (#6387)
* feat(intent): auto-generate type: uuid fields on create (no hand-written action) A `type: uuid` field is now platform-generated: the generated repository assigns a random UUID on create when the value is empty, so a document's system/business-key uuid no longer needs a hand-written calculatedActionOnCreate. The author can still seed/import an explicit value - it is only filled when blank. - EdmIntentGenerator marks a uuid property generatedUuid="true" (alongside the existing read-only flag). - The DAO Repository.java.template save() (create path) assigns java.util.UUID.randomUUID() to each generatedUuid property that is null/blank, next to the calculated-on-create assignments. Verified: the EDM marks Customer.Uuid generatedUuid; a generated CompanyRepository.save() contains the UUID auto-fill and compiles clean (544 beans, no javac errors). This is the reusable UUID primitive the first-class numbering placeholder (stampOn: issue, N3b) will reuse - together they retire the hand-written per-document UUID/number placeholder actions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(intent): generate create-time numbering from number: {} (N3b-i) N3b-i of first-class document numbering: turn a `number: {}` field (N1) into its create-time behavior against the runtime (N2) via the SDK (N3a). No hand-written placeholder action. - EdmIntentGenerator emits per-field number markers (numberSeries / numberFormat / numberScope [PascalCased] / numberStampOn) and, by mode: stampOn:create -> numberStampOnCreate="true"; stampOn:issue -> generatedUuid="true" (a UUID placeholder on create, reusing the uuid auto-fill), pending the issue stamp step (N3b-ii). The number field is read-only. - Repository.java.template save() (create path): for a numberStampOnCreate field, build the scope map (year = current year; any other name reads the entity's field) and stamp the real number via sdk.numbering.DocumentNumbers.next(series, format, scope) when the field is blank. Verified live: a Company with `number: { series: CompanyDoc, format: "CO-{seq:05}", stampOn: create }` stamps CO-00001 / CO-00002 / CO-00003 on successive REST creates (gap-free), the counter shows in the shell's Document Numbering settings, and it compiles clean. EdmIntentGeneratorTest#numberFieldEmitsStampMarkers covers both modes. Stacked on #6386 (uuid auto-fill — the issue placeholder). N3b-ii: the generated issue stamp delegate (gen.events.<Entity>NumberStamp, idempotent) the process wires via delegate:, so stampOn:issue documents (invoices) drop SalesInvoiceNumberAction + the generateNumber delegate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 466a898 commit d0ed5f1

3 files changed

Lines changed: 80 additions & 0 deletions

File tree

components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.eclipse.dirigible.components.intent.model.EntityIntent;
3535
import org.eclipse.dirigible.components.intent.model.LabelExpression;
3636
import org.eclipse.dirigible.components.intent.model.FieldIntent;
37+
import org.eclipse.dirigible.components.intent.model.NumberIntent;
3738
import org.eclipse.dirigible.components.intent.model.IntentModel;
3839
import org.eclipse.dirigible.components.intent.model.RelationIntent;
3940
import org.eclipse.dirigible.components.intent.model.RollupIntent;
@@ -924,6 +925,32 @@ private static Map<String, Object> propertyMap(String entityName, FieldIntent fi
924925
if ("uuid".equalsIgnoreCase(field.getType())) {
925926
p.put("generatedUuid", "true");
926927
}
928+
// First-class document numbering (intent `number: {}`): the platform maintains a per-series
929+
// counter and stamps the formatted number. stampOn:create stamps the real number on insert
930+
// (the generated repository calls sdk.numbering.DocumentNumbers); stampOn:issue holds a UUID
931+
// placeholder on create (reusing the uuid auto-fill above) until the generated stamp step runs.
932+
if (field.getNumber() != null) {
933+
NumberIntent number = field.getNumber();
934+
List<String> numberScope = new ArrayList<>();
935+
if (number.getScope() != null) {
936+
for (String scopeName : number.getScope()) {
937+
// Scope names index the counter AND read the entity's field on create; PascalCase them
938+
// to match the generated entity property (year stays the literal token).
939+
numberScope.add("year".equalsIgnoreCase(scopeName) ? "year" : IntentNaming.pascalCase(scopeName));
940+
}
941+
}
942+
p.put("numberSeries", number.getSeries() == null ? entityName : number.getSeries());
943+
p.put("numberFormat", number.getFormat() == null ? "" : number.getFormat());
944+
p.put("numberScope", numberScope);
945+
p.put("isReadOnlyProperty", "true");
946+
if ("issue".equalsIgnoreCase(number.getStampOn())) {
947+
p.put("numberStampOn", "issue");
948+
p.put("generatedUuid", "true"); // UUID placeholder on create; stamped at the issue step
949+
} else {
950+
p.put("numberStampOn", "create");
951+
p.put("numberStampOnCreate", "true"); // real number allocated + formatted on insert
952+
}
953+
}
927954
if (field.isSensitive()) {
928955
// Hidden from the personal (my) surface: absent from its pages and stripped from the
929956
// personal REST controller's responses. The power surface ignores this attribute.

components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGeneratorTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,39 @@ void customerEmitsCrossModelProjectionsAndForeignKeys() {
7777
assertEquals("master-data", customer.get("perspectiveNavId"));
7878
}
7979

80+
@Test
81+
void numberFieldEmitsStampMarkers() {
82+
String yaml =
83+
"""
84+
name: billing
85+
entities:
86+
- name: SalesInvoice
87+
fields:
88+
- { name: id, type: integer, primaryKey: true, generated: true }
89+
- { name: number, type: string, number: { series: SalesInvoice, format: "SI-{seq:07}", scope: [year], stampOn: issue } }
90+
- name: Proforma
91+
fields:
92+
- { name: id, type: integer, primaryKey: true, generated: true }
93+
- { name: number, type: string, number: { series: Proforma, format: "PF-{seq:05}", stampOn: create } }
94+
""";
95+
Map<String, Object> model = EdmIntentGenerator.buildModelJsonForTest(IntentParser.parse(yaml), "billing");
96+
List<Map<String, Object>> entities = entities(model);
97+
98+
// stampOn: issue -> a UUID placeholder on create (reusing the uuid auto-fill) + the series markers.
99+
Map<String, Object> siNumber = propertyByName(entityByName(entities, "SalesInvoice"), "Number");
100+
assertEquals("issue", siNumber.get("numberStampOn"));
101+
assertEquals("SalesInvoice", siNumber.get("numberSeries"));
102+
assertEquals("true", siNumber.get("generatedUuid"));
103+
assertNull(siNumber.get("numberStampOnCreate"));
104+
105+
// stampOn: create -> the real number is stamped on insert (numberStampOnCreate), no placeholder.
106+
Map<String, Object> pfNumber = propertyByName(entityByName(entities, "Proforma"), "Number");
107+
assertEquals("create", pfNumber.get("numberStampOn"));
108+
assertEquals("true", pfNumber.get("numberStampOnCreate"));
109+
assertEquals("PF-{seq:05}", pfNumber.get("numberFormat"));
110+
assertNull(pfNumber.get("generatedUuid"));
111+
}
112+
80113
@Test
81114
void crossModelProjectionCellIsMarkedProjectionInTheEdmDiagram() {
82115
IntentModel parsed = IntentParser.parse(readResource("/billing/customers.intent"));

components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Repository.java.template

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,33 @@ public class ${name}Repository extends JavaRepository<${name}Entity> {
144144
#end
145145
#end
146146
## A uuid field (intent type: uuid) is platform-generated: assign a random UUID on create when empty.
147+
## A number: {} field with stampOn:issue also carries generatedUuid, so its create-time placeholder is
148+
## a UUID (the real number is stamped at the issue step); stampOn:create fields are handled just below.
147149
#foreach ($property in $properties)
148150
#if($property.generatedUuid)
149151
if (entity.${property.name} == null || entity.${property.name}.isBlank()) {
150152
entity.${property.name} = java.util.UUID.randomUUID().toString();
151153
}
152154
#end
153155
#end
156+
## First-class numbering, stampOn:create: allocate + format the real document number on insert (when
157+
## empty), via the shared per-tenant counter. The scope both partitions the counter and feeds the
158+
## format's tokens; `year` is the current year, any other name reads the entity's own field.
159+
#foreach ($property in $properties)
160+
#if($property.numberStampOnCreate)
161+
if (entity.${property.name} == null || entity.${property.name}.isBlank()) {
162+
java.util.Map<String, String> ${property.name}Scope = new java.util.LinkedHashMap<>();
163+
#foreach ($scopeName in $property.numberScope)
164+
#if($scopeName == "year")
165+
${property.name}Scope.put("year", String.valueOf(java.time.Year.now().getValue()));
166+
#else
167+
${property.name}Scope.put("${scopeName}", entity.${scopeName} == null ? "" : String.valueOf(entity.${scopeName}));
168+
#end
169+
#end
170+
entity.${property.name} = org.eclipse.dirigible.sdk.numbering.DocumentNumbers.next("${property.numberSeries}", "${property.numberFormat}", ${property.name}Scope);
171+
}
172+
#end
173+
#end
154174
#if($documentMaster)
155175
recalculate(entity);
156176
#end

0 commit comments

Comments
 (0)