From 102345e51b68131850e26c1be7b710f86fb517fe Mon Sep 17 00:00:00 2001 From: delchev Date: Wed, 22 Jul 2026 18:18:52 +0300 Subject: [PATCH 1/3] 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 --- .../intent/generator/edm/EdmIntentGenerator.java | 6 ++++++ .../intent/generator/edm/EdmIntentGeneratorTest.java | 4 +++- .../data/Repository.java.template | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java index ada15a6e64..aacbdc8002 100644 --- a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java +++ b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java @@ -918,6 +918,12 @@ private static Map propertyMap(String entityName, FieldIntent fi if (field.isReadOnly() || "uuid".equalsIgnoreCase(field.getType())) { p.put("isReadOnlyProperty", "true"); } + // A uuid field is platform-generated: the generated repository assigns a random UUID on create + // when the value is empty (no hand-written calculatedActionOnCreate needed). The author can + // still seed/import an explicit value - it is only filled when blank. + if ("uuid".equalsIgnoreCase(field.getType())) { + p.put("generatedUuid", "true"); + } if (field.isSensitive()) { // Hidden from the personal (my) surface: absent from its pages and stripped from the // personal REST controller's responses. The power surface ignores this attribute. diff --git a/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGeneratorTest.java b/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGeneratorTest.java index ea5ee86164..d9562c2c82 100644 --- a/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGeneratorTest.java +++ b/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGeneratorTest.java @@ -66,8 +66,10 @@ void customerEmitsCrossModelProjectionsAndForeignKeys() { .noneMatch(p -> "Country".equals(p.get("name"))), "projection targets must not create perspectives"); - // uuid carries the unique constraint; the four audit columns are present. + // uuid carries the unique constraint and is platform-generated on create (no custom action); + // the four audit columns are present. assertEquals("true", propertyByName(customer, "Uuid").get("dataUnique")); + assertEquals("true", propertyByName(customer, "Uuid").get("generatedUuid")); assertEquals("CREATED_AT", propertyByName(customer, "CreatedAt").get("auditType")); assertEquals("UPDATED_BY", propertyByName(customer, "UpdatedBy").get("auditType")); diff --git a/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Repository.java.template b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Repository.java.template index b78ed136d8..abffae0ab6 100644 --- a/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Repository.java.template +++ b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Repository.java.template @@ -143,6 +143,14 @@ public class ${name}Repository extends JavaRepository<${name}Entity> { #end #end #end +## A uuid field (intent type: uuid) is platform-generated: assign a random UUID on create when empty. +#foreach ($property in $properties) +#if($property.generatedUuid) + if (entity.${property.name} == null || entity.${property.name}.isBlank()) { + entity.${property.name} = java.util.UUID.randomUUID().toString(); + } +#end +#end #if($documentMaster) recalculate(entity); #end From 6cbbaa1e3c8f40eedb2b1039159f791aae95f422 Mon Sep 17 00:00:00 2001 From: delchev Date: Wed, 22 Jul 2026 18:33:22 +0300 Subject: [PATCH 2/3] feat(intent): generate create-time numbering from number: {} (N3b-i) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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.NumberStamp, idempotent) the process wires via delegate:, so stampOn:issue documents (invoices) drop SalesInvoiceNumberAction + the generateNumber delegate. Co-Authored-By: Claude Opus 4.8 --- .../generator/edm/EdmIntentGenerator.java | 27 +++++++++++++++ .../generator/edm/EdmIntentGeneratorTest.java | 33 +++++++++++++++++++ .../data/Repository.java.template | 20 +++++++++++ 3 files changed, 80 insertions(+) diff --git a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java index aacbdc8002..5abd425193 100644 --- a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java +++ b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java @@ -34,6 +34,7 @@ import org.eclipse.dirigible.components.intent.model.EntityIntent; import org.eclipse.dirigible.components.intent.model.LabelExpression; import org.eclipse.dirigible.components.intent.model.FieldIntent; +import org.eclipse.dirigible.components.intent.model.NumberIntent; import org.eclipse.dirigible.components.intent.model.IntentModel; import org.eclipse.dirigible.components.intent.model.RelationIntent; import org.eclipse.dirigible.components.intent.model.RollupIntent; @@ -924,6 +925,32 @@ private static Map propertyMap(String entityName, FieldIntent fi if ("uuid".equalsIgnoreCase(field.getType())) { p.put("generatedUuid", "true"); } + // First-class document numbering (intent `number: {}`): the platform maintains a per-series + // counter and stamps the formatted number. stampOn:create stamps the real number on insert + // (the generated repository calls sdk.numbering.DocumentNumbers); stampOn:issue holds a UUID + // placeholder on create (reusing the uuid auto-fill above) until the generated stamp step runs. + if (field.getNumber() != null) { + NumberIntent number = field.getNumber(); + List numberScope = new ArrayList<>(); + if (number.getScope() != null) { + for (String scopeName : number.getScope()) { + // Scope names index the counter AND read the entity's field on create; PascalCase them + // to match the generated entity property (year stays the literal token). + numberScope.add("year".equalsIgnoreCase(scopeName) ? "year" : IntentNaming.pascalCase(scopeName)); + } + } + p.put("numberSeries", number.getSeries() == null ? entityName : number.getSeries()); + p.put("numberFormat", number.getFormat() == null ? "" : number.getFormat()); + p.put("numberScope", numberScope); + p.put("isReadOnlyProperty", "true"); + if ("issue".equalsIgnoreCase(number.getStampOn())) { + p.put("numberStampOn", "issue"); + p.put("generatedUuid", "true"); // UUID placeholder on create; stamped at the issue step + } else { + p.put("numberStampOn", "create"); + p.put("numberStampOnCreate", "true"); // real number allocated + formatted on insert + } + } if (field.isSensitive()) { // Hidden from the personal (my) surface: absent from its pages and stripped from the // personal REST controller's responses. The power surface ignores this attribute. diff --git a/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGeneratorTest.java b/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGeneratorTest.java index d9562c2c82..62b3a2c7a4 100644 --- a/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGeneratorTest.java +++ b/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGeneratorTest.java @@ -77,6 +77,39 @@ void customerEmitsCrossModelProjectionsAndForeignKeys() { assertEquals("master-data", customer.get("perspectiveNavId")); } + @Test + void numberFieldEmitsStampMarkers() { + String yaml = + """ + name: billing + entities: + - name: SalesInvoice + fields: + - { name: id, type: integer, primaryKey: true, generated: true } + - { name: number, type: string, number: { series: SalesInvoice, format: "SI-{seq:07}", scope: [year], stampOn: issue } } + - name: Proforma + fields: + - { name: id, type: integer, primaryKey: true, generated: true } + - { name: number, type: string, number: { series: Proforma, format: "PF-{seq:05}", stampOn: create } } + """; + Map model = EdmIntentGenerator.buildModelJsonForTest(IntentParser.parse(yaml), "billing"); + List> entities = entities(model); + + // stampOn: issue -> a UUID placeholder on create (reusing the uuid auto-fill) + the series markers. + Map siNumber = propertyByName(entityByName(entities, "SalesInvoice"), "Number"); + assertEquals("issue", siNumber.get("numberStampOn")); + assertEquals("SalesInvoice", siNumber.get("numberSeries")); + assertEquals("true", siNumber.get("generatedUuid")); + assertNull(siNumber.get("numberStampOnCreate")); + + // stampOn: create -> the real number is stamped on insert (numberStampOnCreate), no placeholder. + Map pfNumber = propertyByName(entityByName(entities, "Proforma"), "Number"); + assertEquals("create", pfNumber.get("numberStampOn")); + assertEquals("true", pfNumber.get("numberStampOnCreate")); + assertEquals("PF-{seq:05}", pfNumber.get("numberFormat")); + assertNull(pfNumber.get("generatedUuid")); + } + @Test void crossModelProjectionCellIsMarkedProjectionInTheEdmDiagram() { IntentModel parsed = IntentParser.parse(readResource("/billing/customers.intent")); diff --git a/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Repository.java.template b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Repository.java.template index abffae0ab6..c1f660d89a 100644 --- a/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Repository.java.template +++ b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Repository.java.template @@ -144,6 +144,8 @@ public class ${name}Repository extends JavaRepository<${name}Entity> { #end #end ## A uuid field (intent type: uuid) is platform-generated: assign a random UUID on create when empty. +## A number: {} field with stampOn:issue also carries generatedUuid, so its create-time placeholder is +## a UUID (the real number is stamped at the issue step); stampOn:create fields are handled just below. #foreach ($property in $properties) #if($property.generatedUuid) if (entity.${property.name} == null || entity.${property.name}.isBlank()) { @@ -151,6 +153,24 @@ public class ${name}Repository extends JavaRepository<${name}Entity> { } #end #end +## First-class numbering, stampOn:create: allocate + format the real document number on insert (when +## empty), via the shared per-tenant counter. The scope both partitions the counter and feeds the +## format's tokens; `year` is the current year, any other name reads the entity's own field. +#foreach ($property in $properties) +#if($property.numberStampOnCreate) + if (entity.${property.name} == null || entity.${property.name}.isBlank()) { + java.util.Map ${property.name}Scope = new java.util.LinkedHashMap<>(); +#foreach ($scopeName in $property.numberScope) +#if($scopeName == "year") + ${property.name}Scope.put("year", String.valueOf(java.time.Year.now().getValue())); +#else + ${property.name}Scope.put("${scopeName}", entity.${scopeName} == null ? "" : String.valueOf(entity.${scopeName})); +#end +#end + entity.${property.name} = org.eclipse.dirigible.sdk.numbering.DocumentNumbers.next("${property.numberSeries}", "${property.numberFormat}", ${property.name}Scope); + } +#end +#end #if($documentMaster) recalculate(entity); #end From 8c5fa20a9fe4aa9a9e8eac6870695dc83a525769 Mon Sep 17 00:00:00 2001 From: delchev Date: Wed, 22 Jul 2026 18:44:47 +0300 Subject: [PATCH 3/3] feat(intent): generate the issue-step number stamp delegate (N3b-ii) Completes first-class numbering for stampOn: issue: a `number: { stampOn: issue }` field is created with a UUID placeholder (N3b-i) and stamped with the real formatted number at the modeled issue step by a generated delegate - no hand-written SalesInvoiceNumberAction / generateNumber delegate. Mirrors the snapshot-D pattern: - NumberingSupport builds the `numbering` glue collection (one descriptor per stampOn:issue field: entity, perspective, masterPk, field, series, format, scope). - GlueIntentGenerator emits it into the .glue. - generateUtils adds the `numbering` case (sanitizes perspective -> javaPerspective). - Numbering.java.template generates gen/events/NumberStamp.java: a JavaDelegate that reads the entity id from the process variable, loads it, and - only while the value is still the UUID placeholder (idempotent) - allocates + formats via sdk.numbering.DocumentNumbers and writes with the targeted updateProperty (a workflow system write). The process wires it via delegate: gen.events.NumberStamp (like generateSnapshot). Verified live: a Company with `number: { series: CompanyIssue, format: "CI-{seq:05}", stampOn: issue }` emits a CompanyNumberStamp delegate that compiles clean (542 beans, no javac errors); the created record carries a UUID placeholder (the stamp runs at the issue step). Stacked on #6387 (N3b-i). This unblocks the KF migration: stampOn:issue documents (SalesInvoice / CreditNote / DebitNote) can drop SalesInvoiceNumberAction + the per-doc generateNumber delegates + kf-mod-numbers, replacing them with `number: { ..., stampOn: issue }` + a delegate: step. Co-Authored-By: Claude Opus 4.8 --- .../intent/generator/GlueIntentGenerator.java | 5 +- .../intent/generator/NumberingSupport.java | 72 +++++++++++++++++++ .../events/Numbering.java.template | 61 ++++++++++++++++ .../template/template.js | 7 ++ .../template/generateUtils.js | 28 ++++++++ 5 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/NumberingSupport.java create mode 100644 components/template/template-application-events-java/src/main/resources/META-INF/dirigible/template-application-events-java/events/Numbering.java.template diff --git a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/GlueIntentGenerator.java b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/GlueIntentGenerator.java index e52687634c..7fca32c22b 100644 --- a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/GlueIntentGenerator.java +++ b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/GlueIntentGenerator.java @@ -101,11 +101,13 @@ public void generate(IntentGenerationContext context) { List> postings = buildPostings(model, byName, compositionParents, settings, context); List> printFeeders = PrintFeederSupport.buildPrintFeeders(model, byName, compositionParents, context); List> snapshots = SnapshotSupport.buildSnapshots(model, byName, compositionParents); + List> numbering = NumberingSupport.buildNumbering(model, compositionParents); if (triggers.isEmpty() && resolvers.isEmpty() && fieldLoaders.isEmpty() && timerLoaders.isEmpty() && waits.isEmpty() && aborts.isEmpty() && writers.isEmpty() && setters.isEmpty() && notifications.isEmpty() && schedules.isEmpty() && integrations.isEmpty() && inbound.isEmpty() && rollups.isEmpty() && expansions.isEmpty() && settlements.isEmpty() - && generates.isEmpty() && transitions.isEmpty() && printFeeders.isEmpty() && postings.isEmpty() && snapshots.isEmpty()) { + && generates.isEmpty() && transitions.isEmpty() && printFeeders.isEmpty() && postings.isEmpty() && snapshots.isEmpty() + && numbering.isEmpty()) { // No process glue for this intent - any stale .glue is removed by the post-pass scrub. return; } @@ -131,6 +133,7 @@ public void generate(IntentGenerationContext context) { glue.put("postings", postings); glue.put("printFeeders", printFeeders); glue.put("snapshots", snapshots); + glue.put("numbering", numbering); context.writeModelFile(IntentNaming.baseName(context) + ".glue", JsonHelper.toJson(glue)); LOGGER.debug( "Wrote glue with [{}] trigger(s), [{}] resolver(s), [{}] writer(s), [{}] setter(s)," diff --git a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/NumberingSupport.java b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/NumberingSupport.java new file mode 100644 index 0000000000..0cde32f71a --- /dev/null +++ b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/NumberingSupport.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2010-2026 Eclipse Dirigible contributors + * + * All rights reserved. This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * SPDX-FileCopyrightText: Eclipse Dirigible contributors SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.dirigible.components.intent.generator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.LinkedHashMap; + +import org.eclipse.dirigible.components.intent.model.EntityIntent; +import org.eclipse.dirigible.components.intent.model.FieldIntent; +import org.eclipse.dirigible.components.intent.model.IntentModel; +import org.eclipse.dirigible.components.intent.model.NumberIntent; + +/** + * Builds the {@code numbering} glue collection: one descriptor per {@code number: { stampOn: issue + * }} field, driving the generated {@code gen/events/NumberStamp.java} delegate. The + * document is created with a UUID placeholder (the uuid auto-fill); this delegate, wired as a + * {@code delegate:} service task at the issue step, replaces it with the real formatted number - + * idempotently, so a re-issue after an amend keeps the number. + * + *

+ * The number is allocated + formatted via {@code sdk.numbering.DocumentNumbers} (the shared + * per-tenant counter) and written with the targeted {@code updateProperty} (a workflow system + * write). + */ +final class NumberingSupport { + + private NumberingSupport() {} + + /** + * One numbering descriptor per {@code stampOn: issue} number field in the model. + * + * @param model the parsed intent model + * @param compositionParents each entity's transitive composition parent (perspective resolution) + * @return the {@code numbering} collection (possibly empty) + */ + static List> buildNumbering(IntentModel model, Map compositionParents) { + List> numbering = new ArrayList<>(); + for (EntityIntent entity : model.getEntities()) { + for (FieldIntent field : entity.getFields()) { + NumberIntent number = field.getNumber(); + if (number == null || !"issue".equalsIgnoreCase(number.getStampOn())) { + continue; // stampOn:create is handled at insert by the DAO; only issue needs a step + } + List scope = new ArrayList<>(); + if (number.getScope() != null) { + for (String scopeName : number.getScope()) { + scope.add("year".equalsIgnoreCase(scopeName) ? "year" : IntentNaming.pascalCase(scopeName)); + } + } + Map descriptor = new LinkedHashMap<>(); + descriptor.put("entity", entity.getName()); + descriptor.put("perspective", IntentEntities.resolvePerspective(entity.getName(), compositionParents)); + descriptor.put("masterPk", IntentEntities.keyFieldName(entity)); + descriptor.put("field", IntentNaming.pascalCase(field.getName())); + descriptor.put("series", number.getSeries() == null ? entity.getName() : number.getSeries()); + descriptor.put("format", number.getFormat() == null ? "" : number.getFormat()); + descriptor.put("scope", scope); + numbering.add(descriptor); + } + } + return numbering; + } +} diff --git a/components/template/template-application-events-java/src/main/resources/META-INF/dirigible/template-application-events-java/events/Numbering.java.template b/components/template/template-application-events-java/src/main/resources/META-INF/dirigible/template-application-events-java/events/Numbering.java.template new file mode 100644 index 0000000000..4266814ec8 --- /dev/null +++ b/components/template/template-application-events-java/src/main/resources/META-INF/dirigible/template-application-events-java/events/Numbering.java.template @@ -0,0 +1,61 @@ +package gen.events; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; + +import gen.${javaGenFolderName}.data.${javaPerspective}.${entity}Entity; +import gen.${javaGenFolderName}.data.${javaPerspective}.${entity}Repository; + +/** + * Document-number stamp for ${entity} (field ${field}, series ${series}, stampOn: issue). Wired as a + * `delegate:` service task at the issue step: it replaces the create-time UUID placeholder with the + * real formatted number, idempotently - a re-issue after an amend keeps the number. + * + * Generated from the intent numbering glue - do not edit; it is re-generated with the application. The + * number is allocated + formatted via sdk.numbering.DocumentNumbers (the shared per-tenant counter, + * also managed from the application shell's Document Numbering settings) and written with the targeted + * updateProperty - a workflow system write (no "-updated" event re-fire, no full-row merge). + */ +public class ${entity}NumberStamp implements JavaDelegate { + + @Override + public void execute(DelegateExecution execution) { + Object key = execution.getVariable("${masterPk}"); + if (!(key instanceof Number)) { + return; + } + int id = ((Number) key).intValue(); + ${entity}Repository repository = new ${entity}Repository(); + ${entity}Entity entity = repository.findById(id); + if (entity == null) { + return; + } + // Idempotent: only replace the create-time UUID placeholder; skip an already-stamped number. + if (entity.${field} != null && !isPlaceholder(entity.${field})) { + return; + } + Map scope = new LinkedHashMap<>(); +#foreach ($scopeName in $scope) +#if($scopeName == "year") + scope.put("year", String.valueOf(java.time.Year.now().getValue())); +#else + scope.put("${scopeName}", entity.${scopeName} == null ? "" : String.valueOf(entity.${scopeName})); +#end +#end + String number = org.eclipse.dirigible.sdk.numbering.DocumentNumbers.next("${series}", "${format}", scope); + repository.updateProperty(id, "${field}", number); + } + + /** Whether the value is still the create-time UUID placeholder (vs an already-stamped number). */ + private static boolean isPlaceholder(String value) { + try { + java.util.UUID.fromString(value); + return true; + } catch (IllegalArgumentException notAUuid) { + return false; + } + } +} diff --git a/components/template/template-application-events-java/src/main/resources/META-INF/dirigible/template-application-events-java/template/template.js b/components/template/template-application-events-java/src/main/resources/META-INF/dirigible/template-application-events-java/template/template.js index b1293acf6d..bf2af5031c 100644 --- a/components/template/template-application-events-java/src/main/resources/META-INF/dirigible/template-application-events-java/template/template.js +++ b/components/template/template-application-events-java/src/main/resources/META-INF/dirigible/template-application-events-java/template/template.js @@ -135,6 +135,13 @@ export function getTemplate(parameters) { engine: "velocity", collection: "snapshots" }, + { + location: "/template-application-events-java/events/Numbering.java.template", + action: "generate", + rename: "gen/events/{{entity}}NumberStamp.java", + engine: "velocity", + collection: "numbering" + }, { location: "/template-application-events-java/events/SettlementOnPayment.java.template", action: "generate", diff --git a/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/generateUtils.js b/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/generateUtils.js index 34095df68d..6d9996f905 100644 --- a/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/generateUtils.js +++ b/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/generateUtils.js @@ -1179,6 +1179,34 @@ export function generateFiles(model, parameters, templateSources) { } } break; + case "numbering": + // Document-number stamp delegates (intent layer): one JavaDelegate per number: field + // with stampOn:issue, wired as a delegate: service task at the issue step. It stamps + // the real number via sdk.numbering.DocumentNumbers. Own loop; the entity repo lives + // in this project (javaGenFolderName), its Java package segment is the sanitized + // perspective. + if (model.numbering) { + for (let n = 0; n < model.numbering.length; n++) { + const num = model.numbering[n]; + const numberingParameters = { + ...parameters, + entity: num.entity, + javaPerspective: sanitizeJavaIdentifier(num.perspective), + masterPk: num.masterPk, + field: num.field, + series: num.series, + format: num.format, + scope: num.scope + }; + const cleanNumberingParameters = cleanData(numberingParameters); + generatedFiles.push({ + location: location, + content: getGenerationEngine(template).generate(location, content, cleanNumberingParameters), + path: templateEngines.getMustacheEngine().generate(location, template.rename, cleanNumberingParameters) + }); + } + } + break; default: // No collection parameters.models = model.entities;