Workflow Instructies
Dit artikel documenteert alle beschikbare workflow instructies in Ultimo. Instructies zijn de bouwstenen van workflows: elke XML-tag binnen een <Execution> blok is een instructie. Ze zijn gegroepeerd per categorie. Zie workflow-engine voor de algemene opzet van workflows en expressions voor expression functies.
Commonbewerken
Basis instructies die in vrijwel elke workflow voorkomen.
Assignbewerken
Wijs een waarde toe aan een property van een domain object. Kan binnen en buiten een #Transaction gebruikt worden. Binnen een Transaction worden wijzigingen naar de database geschreven; buiten een Transaction alleen in het geheugen.
Let op: Gebruik Assign NIET om een status te wijzigen. Gebruik daarvoor #ChangeStatus zodat de Status Matrix en Pre/Post workflows correct worden doorlopen.
<!-- Binnen Transaction: wijzigt het domain object in de database -->
<Transaction>
<Assign Name="Set Job.Description" Property="${Job.Description}" Value="${Job.FeedbackDescription}" />
<Assign Name="Set DateTime" Property="${Job.PmWorkOrder.LastMaintenanceDate}" Value="${Job.FinishedDate}" />
<Assign Name="Reset Preference" Property="${PreferredVendor.Preference}" Value="True" />
</Transaction>
<!-- Buiten Transaction: wijzigt alleen een interne property -->
<Assign Name="Set Cost property Empty" Property="${Cost}" Value="Empty" />
Attributen:
- Name - Beschrijving van de actie
- Property - Property reference, bijv.
${Job.Description} - Value - Waarde, property reference, of expressie (met
=prefix)
AssignTextbewerken
Wijs een String-waarde toe aan een property, specifiek voor meertalige omgevingen. Gebruik dit wanneer je een "multilingual", "neutral" of "string" waarde naar een ander type wilt converteren.
<Transaction>
<AssignText Name="Set Job.Description" Property="${Job.Description}"
Value="${KnowledgeTopic.Description}" SourceLanguage="EN-US" TargetLanguage="EN" />
</Transaction>
Extra attributen:
- SourceLanguage - Brontaal (bijv.
EN-US) - TargetLanguage - Doeltaal (bijv.
EN) - PreserveOtherMultilingualValues - Behoud andere taalwaarden bij toewijzing
AssignWhenEmptybewerken
Wijs een waarde toe ALLEEN als de property nog leeg is. Handig voor standaardwaarden die niet overschreven mogen worden.
<AssignWhenEmpty Name="Set HoursEmployee if still empty"
Property="${HoursEmployee}" Value="${Employee}" />
Commandbewerken
Voer een voorgedefinieerd commando uit. Commands zijn ingebouwde functionaliteitsblokken in Ultimo die niet als losse instructies beschikbaar zijn.
<Command Name="Job_CreateAndInitialize" CommandName="Job_CreateAndInitialize">
<Parameter Name="JobCreateMethod" Direction="In" Value="CreateApproved" />
<Parameter Name="Job" Direction="Out" OutputProperty="${Job}" />
</Command>
Attributen:
- CommandName - Naam van het commando
- Parameters - In/Out parameters
Commentbewerken
Voeg commentaar toe aan de workflow XML. Wordt niet uitgevoerd maar verbetert de leesbaarheid.
<Comment Comment="Korte beschrijving"><![CDATA[Uitgebreide uitleg van het proces]]></Comment>
CreateObjectbewerken
Maak een nieuw object-instantie aan, zoals een Dictionary of Status. Niet gebruiken voor domain objects - gebruik daarvoor #Insert.
<CreateObject Name="Create status" ObjectType="Status" OutputProperty="${Status}" />
<CreateObject Name="Create dictionary" ObjectType="Dictionary[String]" OutputProperty="${StopFilter}" />
SingleItemValidationbewerken
Valideer dat een lijst precies een element bevat. Als de lijst meer elementen bevat, wordt de workflow afgebroken met een foutmelding.
<SingleItemValidation Name="Get Single Item"
List="${ArticleSiteVendors}" OutputProperty="${ArticleSiteVendor}"
MessageCode="0000" AllowEmpty="True" />
Attributen:
- AllowEmpty -
Trueals de lijst ook leeg mag zijn
Togglebewerken
Inverteer een Boolean waarde: True wordt False en vice versa.
<Toggle Name="Toggle RegisterStock" Property="${Article.RegisterStock}" />
UserContentbewerken
Uitbreidingspunt in een workflow waar consultants en klantbeheerders eigen logica kunnen toevoegen. Zie UserContent in de workflow engine.
<UserContent Name="Pre" />
<!-- ... standaard logica ... -->
<UserContent Name="Post" />
Consultant tip: Voor Bulk workflows moet UserContent een child van ForEach zijn.
Validationbewerken
Controleer een conditie. Als de conditie false retourneert, wordt de workflow afgebroken en een foutmelding getoond.
<Validation Name="Equipment verplicht"
Condition="${Job.Equipment} != Empty" MessageCode="9001">
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Validation>
<Validation Name="Check threshold"
Condition="${TotalThreshold} + ${AbcCode.Threshold} <= 100" MessageCode="3844">
<Parameter Name="AbcCode" Direction="In" Value="${AbcCode}" />
</Validation>
Attributen:
- Condition - Expressie die
trueoffalseretourneert - MessageCode - Code uit de Message tabel in de database. Custom messages starten bij 9000+
Consultant tip: Validation is ideaal in UserContent Pre om extra controles toe te voegen voor een statuswijziging.
Flow controlbewerken
Instructies voor conditionele logica, lussen en flow control.
Whenbewerken
Conditionele uitvoering: de instructies binnen When worden alleen uitgevoerd als de conditie true is. Buiten een Choose gedraagt When zich als een if. Binnen een Choose als een case.
<!-- Standalone if -->
<When Name="Status change requested" Condition="${ChangeStatusTo} != Empty">
<ChangeStatus Name="Change status" DomainObject="${Job}" NewStatus="${ChangeStatusTo}" />
</When>
<!-- Met property vergelijking -->
<When Name="Is preferred vendor" Condition="${PreferredVendor.Preference} == True">
<WorkflowCall Name="GetPreferredVendor" WorkflowName="ArticleSiteVendor_GetPreferredVendor">
<Parameter Name="PreferredVendor" Direction="In" Value="${PreferredVendor}" />
</WorkflowCall>
</When>
Choose / Otherwisebewerken
If-elseif-else constructie. De eerste When waarvan de conditie true is wordt uitgevoerd. Als geen enkele When matcht, wordt Otherwise uitgevoerd.
<Choose Name="Bepaal actie">
<When Name="X is 1" Condition="${x} == 1">
<Assign Name="Set result" Property="${Result}" Value="1" />
</When>
<When Name="X is 2" Condition="${x} == 2">
<Assign Name="Set result" Property="${Result}" Value="2" />
</When>
<Otherwise Name="Overig">
<Assign Name="Set result" Property="${Result}" Value="0" />
</Otherwise>
</Choose>
ChooseUserInteractionbewerken
Splits logica op basis van of er gebruikersinteractie beschikbaar is. Belangrijk voor workflows die zowel vanuit de UI als vanuit achtergrondprocessen (import, scheduler) kunnen draaien.
<ChooseUserInteraction Name="UI beschikbaar?">
<WithUserInteraction Name="Met UI">
<Dialog Name="ShowDialog" TitleCode="CONFIRM" />
</WithUserInteraction>
<WithoutUserInteraction Name="Zonder UI">
<Assign Name="Standaard" Property="${Job.Description}" Value="${Job.FeedbackDescription}" />
</WithoutUserInteraction>
</ChooseUserInteraction>
ForEachbewerken
Itereer over een lijst van items. De As property wordt automatisch aangemaakt binnen de loop en bestaat niet meer na afloop.
<ForEach Name="Keur werkorders goed" In="${CreatedWorkOrders}" As="NewWorkOrder">
<ChangeStatus Name="Approve" DomainObject="${NewWorkOrder}" NewStatus="WorkOrderStatus.Approved" />
</ForEach>
<!-- Met conditie (filter binnen de loop) -->
<ForEach Name="Loop items" In="${AbcDeterminationList}" As="AbcDetermination"
Condition="${AbcDetermination.Processed} == False && ${Continue} == True">
<!-- acties -->
</ForEach>
Attributen:
- In - Lijst om over te itereren (
List[DomainObject]) - As - Naam van de loop-variabele
- Condition - Optionele filter: alleen items die voldoen worden verwerkt
Patroon: bulk statuswijziging
Een veelvoorkomend patroon is het ophalen van een lijst via GetList en vervolgens de status van elk item wijzigen via ForEach + ChangeStatus:
<!-- Stap 1: haal de lijst op -->
<Transaction>
<GetList Name="Haal te verwijderen equipment op" Type="Equipment"
OutputProperty="${equipmentList}" OrderBy="Id">
<Filters>
<PropertyFilter PropertyName="Status" Operator="=" PropertyValue="EquipmentStatus.OutOfService" />
</Filters>
</GetList>
</Transaction>
<!-- Stap 2: verwerk elk item -->
<ForEach Name="Verwerk equipment" In="${equipmentList}" As="${equipmentItem}">
<Transaction>
<ChangeStatus Name="Zet naar ToDelete"
DomainObject="${equipmentItem}"
NewStatus="EquipmentStatus.ToDelete" />
</Transaction>
</ForEach>
Consultant tip: Elke ChangeStatus binnen een ForEach heeft zijn eigen Transaction nodig zodat statusfouten op itemniveau worden afgehandeld zonder de hele loop af te breken.
Whilebewerken
Herhaal een blok totdat een conditie niet meer waar is. Heeft een cycle limit als beveiliging tegen oneindige lussen.
<While Name="Verwijder regels" Condition="${AllDeleted} == False" CycleLimit="2500">
<Transaction>
<GetList Name="Get batch" Type="InternalChargeLine" OutputProperty="${Lines}"
OrderBy="Id" Top="${BatchSize}">
<Filters>
<PropertyFilter PropertyName="Id.InternalCharge" Operator="=" PropertyValue="${InternalCharge}" />
</Filters>
</GetList>
<When Name="Geen regels meer" Condition="${Lines.Count} == 0">
<Assign Name="Klaar" Property="${AllDeleted}" Value="True" />
</When>
<ForEach Name="Delete" In="${Lines}" As="Line">
<DeleteObject Name="Delete" DomainObject="${Line}" />
</ForEach>
</Transaction>
</While>
Attributen:
- CycleLimit - Maximum aantal iteraties (default beveiliging)
- ContinueAtLimit -
Trueom door te gaan bij het bereiken van de limiet (met voorzichtigheid gebruiken)
Counterbewerken
Verhoog een numerieke property met een stapgrootte. Equivalent aan Assign met een expressie.
<Counter Name="index" Property="${index}" Step="2" />
<Counter Name="index terug" Property="${index}" Step="-2" />
Stopbewerken
Beeindig de workflow. Verschillende modi beschikbaar:
| Mode | Effect |
|---|---|
Abort |
Stopt alle parent workflows, rollback transaction |
EndAll |
Beeindig huidige en parent workflows, resultaat is succesvol |
EndCurrent |
Beeindig alleen de actieve workflow |
EndUserContent |
Beeindig alleen de UserContent sectie |
<Stop Name="Stop" Mode="Abort" />
<Stop Name="Klaar" Mode="EndAll" />
WhenFeatureTogglebewerken
Controleer of een feature toggle actief is.
<WhenFeatureToggle Name="Check feature" Feature="ExampleFeature" />
WorkflowCallbewerken
Roep een andere workflow aan. Parameters worden doorgegeven via In/Out/InOut.
<WorkflowCall Name="Initialiseer job" WorkflowName="Job_CreateAndInitialize">
<Parameter Name="JobCreateMethod" Direction="In" Value="CreateApproved" />
<Parameter Name="Job" Direction="Out" OutputProperty="${Job}" />
</WorkflowCall>
Attributen:
- WorkflowName - Naam van de aan te roepen workflow
- EnforceNoUserInteraction -
Trueom de WithoutUserInteraction-pad af te dwingen
Consultant tip — ActionField workflows: Workflows waarvan de naam begint met
ActionField(bijv.ActionField286) zijn container-workflows die Ultimo intern gebruikt om schermknoppen te koppelen. Roep deze nooit direct aan via WorkflowCall. Open in plaats daarvan de ActionField workflow in de Workflow Designer, zoek de sub-workflow erin die de gewenste logica bevat, en roep díe sub-workflow aan. De ActionField-wrapper bevat vaak dialogen en context-logica die niet relevant zijn als je de functionaliteit programmatisch aanroept.
Databasebewerken
Instructies voor database-operaties. Moeten altijd binnen een Transaction staan.
Transactionbewerken
Container voor alle database-operaties. Zonder Transaction kunnen GetList, Insert, ChangeStatus etc. niet worden gebruikt.
<Transaction>
<Insert Name="Insert" ObjectType="Job" OutputProperty="${NewJob}">
<Parameter Name="Status" Direction="In" Value="JobStatus.Created" />
</Insert>
</Transaction>
Attributen:
- Flush - Tussentijds naar database schrijven
- IncludeTrashedObjects - Verwijderde objecten meenemen in queries
- IncludeTranslationsForQuery - Vertalingen meenemen bij Query instructie
- IsolationLevel -
Default(kan uncommitted data lezen) ofReadCommitted(alleen committed data)
Consultant tip: Gebruik
ReadCommittedalleen bij bekende locking/performance problemen.
Insertbewerken
Maak een nieuw domain object aan in de database. De Id wordt automatisch gegenereerd als autokey actief is.
<Insert Name="Insert Incident" ObjectType="Incident" OutputProperty="${Incident}">
<Parameter Name="Context" Direction="In" Value="${IncidentContext}" />
<Parameter Name="Status" Direction="In" Value="IncidentStatus.None" />
<Parameter Name="Description" Direction="In" Value="${Job.Description}" />
<Parameter Name="ReportDate" Direction="In" Value="${Job.StatusCreatedReportDate}" />
<Parameter Name="ReportEmployee" Direction="In" Value="${Job.ReportForeignKeyEmployee}" />
<Parameter Name="Site" Direction="In" Value="${Job.Site}" />
</Insert>
Attributen:
- ObjectType - Type domain object (Job, Equipment, Incident, etc.)
- OutputProperty - Property om het nieuwe object in op te slaan
- Parameters - Veldwaarden voor het nieuwe record (Direction altijd
In)
Praktische tips — parameter voorbeelden:
- Status als property name, niet numeriek: gebruik
JobStatus.Created(niet1). Ultimo lost de enum op via de property name. Hetzelfde geldt voor andere enum-velden. - Context als property name: gebruik
jobContext.cdvoor de Technische Dienst. De naam volgt de entiteitsnaam + context-code, in camelCase. - Output altijd opslaan: geef
OutputProperty="${newJob}"mee zodat je het nieuwe record later in de workflow kunt gebruiken (bijv. voor ChangeStatus of View). - Minimale betrouwbare Insert voor een Job:
<Transaction>
<Insert Name="Nieuwe job aanmaken" ObjectType="Job" OutputProperty="${newJob}">
<Parameter Name="Status" Direction="In" Value="JobStatus.Created" />
<Parameter Name="Context" Direction="In" Value="jobContext.cd" />
<Parameter Name="Description" Direction="In" Value="${omschrijving}" />
<Parameter Name="Site" Direction="In" Value="${Job.Site}" />
</Insert>
</Transaction>
Consultant tip: Geef altijd minimaal Status én Context mee bij een Insert. Zonder Context kan Ultimo de juiste schermen, rechten en procesflows niet bepalen, wat tot onverwacht gedrag leidt.
Updatebewerken
Werk meerdere properties van een domain object bij in een instructie. Vervangt meerdere Assign instructies.
<Update Name="SetJobProperties" DomainObject="${Job}">
<Parameter Name="Context" Direction="In" Value="JobContext.TD" />
<Parameter Name="Status" Direction="In" Value="JobStatus.Created" />
</Update>
Copybewerken
Kopieer properties van het ene domain object naar het andere. De objecten hoeven niet van hetzelfde type te zijn, maar de property-namen en datatypes moeten overeenkomen.
<Copy Name="Copy CustomColumns" Source="${Job.ServiceDeskReportType}" Target="${Job}"
CopyType="OnlySetIfEmpty" CustomOnly="True" />
Attributen:
- CopyType -
Overwrite(overschrijf bestaande waarden) ofOnlySetIfEmpty(alleen lege velden vullen) - CustomOnly -
Trueom alleen custom properties te kopieren
ChangeStatusbewerken
Wijzig de status van een domain object via de Status Matrix. De matrix bepaalt of de overgang is toegestaan en welke Pre/Post workflows worden uitgevoerd.
<ChangeStatus Name="Activate" DomainObject="${Job}" NewStatus="JobStatus.Active" />
<ChangeStatus Name="Copy status" DomainObject="${Job2}" NewStatus="${Job1.Status}" />
Attributen:
- DomainObject - Het object waarvan de status wordt gewijzigd
- NewStatus - Nieuwe status (enum waarde, property reference, of numeriek)
- UsedDate - Optionele datum/tijd voor de statushistorie
Belangrijk: Gebruik ALTIJD ChangeStatus voor statuswijzigingen — gebruik nooit Assign om een statusproperty direct te overschrijven. ChangeStatus doorloopt de Status Matrix (controleert of de overgang is toegestaan), voert Pre/Post workflows uit en registreert de statushistorie. Een Assign omzeilt al deze validaties en achtergrondbewerkingen, wat leidt tot inconsistente data en ontbrekende history-regels.
DeleteObjectbewerken
Verwijder een domain object. Dit omvat trash + delete, inclusief Pre/Post workflows.
Volgorde: StatusMatrix validatie -> PreTrash -> Status naar Trash -> PostTrash -> PreDelete -> Daadwerkelijke delete.
<DeleteObject Name="Delete" DomainObject="${Job}" />
TrashObjectbewerken
Markeer een domain object als verwijderd (soft delete). Het object kan nog hersteld worden.
<TrashObject Name="TrashJob" DomainObject="${Job}" />
UntrashObjectbewerken
Herstel een naar de prullenbak verplaatst domain object.
<UntrashObject Name="UntrashJob" DomainObject="${Job}" />
GetItembewerken
Haal exact een domain object op uit de database. Het resultaat MOET precies 1 record bevatten.
<GetItem Name="Get default status" Type="DefaultProgressStatusContextRecordStatus"
OutputProperty="${DefaultStatus}">
<Filters>
<PropertyFilter PropertyName="Id.DefaultProgressStatusContext.Id"
Operator="=" PropertyValue="${Job.ServiceDeskReportType.SetContext}" />
<PropertyFilter PropertyName="Id.Status" Operator="=" PropertyValue="1" />
</Filters>
</GetItem>
Praktische tips:
- Property chaining in filters: je kunt via een tussenliggend record navigeren. Gebruik
${root.job.Equipment.Id}om het equipment ID op te halen via het job-record dat als startpunt dient. Voorbeeld:
<GetItem Name="Haal equipment op via job" Type="Equipment"
OutputProperty="${gekoppeldEquipment}">
<Filters>
<PropertyFilter PropertyName="Id" Operator="=" PropertyValue="${root.job.Equipment.Id}" />
</Filters>
</GetItem>
- Exact 1 resultaat vereist: als de filters nul of meer dan één record opleveren, breekt de workflow af. Zorg dat de combinatie van filters altijd uniek is. Gebruik GetList + SingleItemValidation als je onzeker bent over het resultaat.
- In User Content Blocks (UCB): declareer variabelen in het UCB Properties tab (rechterkant in de Workflow Designer). Definieer ze niet in het
Start-blok van de UCB — dat blok is beschermd en jouw wijzigingen worden overschreven bij een update van Ultimo.
GetListbewerken
Haal meerdere domain objects op als lijst. De OutputProperty moet van type List[...] zijn.
<GetList Name="Get subjobs" Type="Job" OutputProperty="${SubjobList}"
OrderBy="ScheduledStartDate" OrderDirection="Ascending">
<Filters>
<PropertyFilter PropertyName="Multijob" Operator="=" PropertyValue="${Job}" />
<PropertyFilter PropertyName="ScheduledStartDate" Operator="<"
PropertyValue="${Job.ScheduledStartDate}" />
</Filters>
</GetList>
Attributen:
- OrderBy / OrderDirection - Sortering
- Top - Maximum aantal resultaten
Praktische tips:
- Output type is altijd
WithList(EntityName): declareer de output-variabele alsWithList(Equipment),WithList(Job), etc. In de Workflow Designer kies je dit type in het UCB Properties tab. - Order By is VERPLICHT: GetList vereist minimaal één sorteerkolom. Zonder
OrderBygeeft de workflow een fout. Gebruik bij voorkeurIdof een relevante datum. - Meerdere PropertyFilters werken als AND: je kunt meerdere
PropertyFilter-elementen direct onder<Filters>plaatsen; ze worden gecombineerd met AND-logica. - Between Filter met datumfuncties: gebruik expression-functies voor dynamische datumranges:
<GetList Name="Gewijzigde equipment laatste 2 dagen" Type="Equipment"
OutputProperty="${equipmentList}" OrderBy="Id">
<Filters>
<BetweenFilter PropertyName="ModificationDate"
LowValue="=startOfDay(addDays(now(), -2))"
HighValue="=startOfDay(now())" />
<PropertyFilter PropertyName="Status" Operator="=" PropertyValue="EquipmentStatus.Active" />
</Filters>
</GetList>
- Resultaat tellen: na een GetList kun je het aantal resultaten opvragen via
${equipmentList.Count}. Gebruik dit in When-condities om te controleren of de lijst resultaten bevat:
<When Name="Lijst heeft resultaten" Condition="${equipmentList.Count} > 0">
<!-- verwerk de lijst -->
</When>
GetCountbewerken
Tel het aantal records dat aan de filter voldoet.
<GetCount Name="Tel subjobs" Type="Job" OutputProperty="${SubJobCount}">
<Filters>
<PropertyFilter PropertyName="Multijob" Operator="=" PropertyValue="${Job}" />
</Filters>
</GetCount>
GetSum / GetMax / GetMinbewerken
Aggregatiefuncties op een specifieke property.
<GetSum Name="Sum Quantity" Type="ObjectBatch" OutputProperty="${SumQuantity}" PropertyName="QuantityOut">
<Filters>
<PropertyFilter PropertyName="JobMaterial" Operator="=" PropertyValue="${JobMaterial}" />
</Filters>
</GetSum>
<GetMax Name="Max price" Type="Article" OutputProperty="${MaxPrice}" PropertyName="Price" />
<GetMin Name="Min score" Type="RiskClass" OutputProperty="${MinScore}" PropertyName="FromScore" />
GetGroupedListbewerken
Haal een gegroepeerde lijst op. Output type is GroupedList[GroupKey, ItemType].
<GetGroupedList Name="Materials per warehouse" Type="JobMaterial"
OutputProperty="${GroupedMaterials}" GroupBy="Warehouse" OrderBy="Id">
<Joins>
<Join Name="Warehouse" Alias="warehouse" Type="LeftOuterJoin" />
</Joins>
<Filters>
<PropertyFilter PropertyName="Id.Job" Operator="=" PropertyValue="${Job}" />
</Filters>
</GetGroupedList>
Query / Subquerybewerken
Voer een geavanceerde query uit met custom projectie (specifieke kolommen).
<Query Name="GetDepartments" Type="Department" OrderBy="Id" OrderDirection="Ascending"
OutputProperty="${DepartmentResult}">
<Properties>
<Property Name="Id" Alias="DepartmentId" Type="GroupProperty" />
<Property Name="emp2.Description" Alias="Description" Type="GroupProperty" />
</Properties>
</Query>
IsReferredTobewerken
Controleer of een domain object door andere objecten wordt gerefereerd.
<IsReferredTo Name="Check references" DomainObject="${Job}" OutputProperty="${JobIsUsed}"
ExcludeTrashedRecords="True" />
Flushbewerken
Schrijf in-memory wijzigingen naar de database, zonder de transaction te beeindigen. Kan geheugen vrijmaken bij grote operaties.
<Flush Comment="Write changes to DB" />
Logbewerken
Schrijf een bericht naar een logbestand. Configureer de log via log.config.
<Log Name="Log" Description="New job created" LogName="Workflow" Type="Information">
<Parameter Name="NumberOfRecords" Direction="In" Value="${NumberOfRecords}" />
</Log>
Log types: Information, Warning, Error
Filtersbewerken
Filters worden gebruikt binnen GetItem, GetList, GetCount, GetSum, GetMax, GetMin en GetGroupedList.
PropertyFilterbewerken
De meest gebruikte filter. Vergelijkt een property met een waarde.
<PropertyFilter PropertyName="Status" Operator="=" PropertyValue="JobStatus.Active" />
<PropertyFilter PropertyName="Equipment" Operator="!=" PropertyValue="Empty" />
Operators: =, !=, >, <, >=, <=, Like
CombinedFilterbewerken
Combineer meerdere filters met AND of OR.
<CombinedFilter FilterOperator="And">
<PropertyFilter PropertyName="Id.Job" Operator="=" PropertyValue="${Job}" />
<PropertyFilter PropertyName="Status" Operator="=" PropertyValue="JobMaterialStatus.Created" />
</CombinedFilter>
<CombinedFilter FilterOperator="Or">
<PropertyFilter PropertyName="Status" Operator="=" PropertyValue="JobStatus.Active" />
<PropertyFilter PropertyName="Status" Operator="=" PropertyValue="JobStatus.Finished" />
</CombinedFilter>
InFilterbewerken
Filter op meerdere waarden tegelijk (SQL IN clausule).
<InFilter PropertyName="Status" Values="JobStatus.Active, JobStatus.Closed" />
<InFilter PropertyName="Id" Values="${WorkPeriodIds}" />
NotFilterbewerken
Inverteer een filter (SQL NOT).
<NotFilter>
<PropertyFilter PropertyName="Id.ProgressStatusNextAvailable" Operator="="
PropertyValue="${DefaultAvailable}" />
</NotFilter>
DateFilter / DatePartFilterbewerken
Filter op datum of een deel van een datum.
<DateFilter PropertyName="ActivateDate" Operator="=" PropertyValue="#{Environment.CurrentDate}" />
<DatePartFilter PropertyName="DepreciationStartDate" Operator="!=" PropertyValue="1" DatePart="Day" />
DatePart waarden: Hour, Day, Week, Month, Quarter, Year
BetweenFilterbewerken
Filter op een bereik (inclusief grenzen).
<BetweenFilter PropertyName="${Job.Cost}" LowValue="1000" HighValue="2000" />
PropertyEmptyFilterbewerken
Filter op lege waarden.
<PropertyEmptyFilter PropertyName="Description" />
WhenFilterbewerken
Conditionele filter: wordt alleen toegepast als de conditie waar is.
<WhenFilter Condition="${UseSiteFilter} == True">
<PropertyFilter PropertyName="Site" Operator="=" PropertyValue="${Job.Site}" />
</WhenFilter>
DistanceFilterbewerken
Filter op geografische afstand in meters.
<DistanceFilter FirstLatitude="52.123" FirstLongitude="5.456"
SecondLatitude="52.789" SecondLongitude="5.012" Operator="<" Distance="1000" />
Joinsbewerken
Joins worden gebruikt binnen Get-instructies om gerelateerde entiteiten mee te laden.
<GetList Name="Get items" Type="JobMaterial" OutputProperty="${Materials}">
<Joins>
<Join Name="Warehouse" Alias="warehouse" Type="LeftOuterJoin" />
<Join Name="Article" Alias="article" Type="InnerJoin" />
</Joins>
<Filters>
<PropertyFilter PropertyName="article.Description" Operator="Like" PropertyValue="%" />
</Filters>
</GetList>
Join types: InnerJoin, LeftOuterJoin
Communicationbewerken
Instructies voor communicatie: emails, reminders, afspraken.
Emailbewerken
Verstuur een email op basis van een TextTemplate. De template wordt uit de database gehaald via de code.
Belangrijk: emails versturen werkt altijd in twee stappen. Eerst maak je de email aan als Concept (Concept="True" + OutputProperty), dan zet je de status naar Draft via ChangeStatus. Zonder deze twee stappen wordt de email niet verzonden. Beide stappen moeten binnen dezelfde Transaction staan.
<!-- Twee-stappen patroon: Email aanmaken als Concept, dan naar Draft zetten -->
<Transaction>
<Email Name="EmailToVendor" From="#{UltimoSettings.EmailSender}"
To="${Job.Vendor.EmailAddress}" RelatedSubjects="${Job}"
EmailTemplateCode="00000000031" Concept="True" OutputProperty="${Email}">
<Parameters>
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Parameters>
<Attachments>
<Attachment Data="${ReportData}" />
</Attachments>
</Email>
<ChangeStatus Name="ChangeStatusEmail" DomainObject="${Email}"
NewStatus="EmailStatus.Draft" />
</Transaction>
Vereiste property-declaratie:
De OutputProperty ${Email} moet als property zijn gedeclareerd. In een standaard workflow:
<Property Name="Email" Type="Email" Accessor="Internal" />
In een UserContent Block (UCB): declareer de property in het UCB Properties tab (rechterkant in de Workflow Designer), type Email, accessor Internal.
Attributen:
- From - Afzender (gebruik altijd
#{UltimoSettings.EmailSender}) - To - Ontvanger: employee, lijst van employees, emailadres, of combinatie (gescheiden door
;) - CC / BCC - Carbon copy / blind carbon copy
- RelatedSubjects - Domain objects gekoppeld aan de email (voor traceerbaarheid in Ultimo)
- EmailTemplateCode - Code van de TextTemplate in de database
- Concept - Altijd
True(eerste stap van het twee-stappen patroon) - OutputProperty - Verplicht bij
Concept="True", slaat het Email-object op voor de ChangeStatus
Email statussen:
EmailStatus.Concept(32) — aangemaakt, nog niet verzendklaarEmailStatus.Draft(1) — "Te verzenden", wordt opgepakt door de mailserviceEmailStatus.Sent(2) — succesvol verzonden
Consultant tip: Gebruik
#{UltimoSettings.EmailSender}als From-adres, niet#{Settings.EmailSender}. Dit is wat de standaard Ultimo workflows gebruiken (zie bijv. ActionField667). Vergeet niet de Email property te declareren — zonder OutputProperty kan de ChangeStatus naar Draft niet werken. Beide instructies (Email + ChangeStatus) moeten in dezelfde Transaction staan.
Reminderbewerken
Stuur een interne reminder (notificatie) naar een of meer medewerkers.
<Reminder Name="Stuur herinnering" Recipients="${Recipients}"
Priority="${Priority}" ReminderTemplateCode="0000"
RelatedSubjects="${Job}" EnablePushNotifications="True" />
CreateAppointment / UpdateAppointmentbewerken
Maak of wijzig een afspraak in een externe agenda via email.
<CreateAppointment Name="Plan afspraak" From="#{Settings.EmailSender}"
To="${Job.Employee}" StartDate="${Job.ScheduledStartDate}"
EndDate="=#addminutes(${Job.ScheduledStartDate}, 30)"
Location="Werkplaats A" RelatedSubjects="${Job}" TemplateCode="0000"
OutputProperty="${Appointment}" />
Dialogbewerken
Instructies voor gebruikersinteractie via dialogen. Alleen beschikbaar als AllowUserInteraction="True".
Dialogbewerken
Toon een dialoogvenster aan de gebruiker.
<Dialog Name="VraagNaam" TitleCode="ASKFORNAME" AllowCancel="True">
<Container Border="True">
<Text Name="NewName" Value="${OriginalName}" OutputProperty="${NewName}"
LabelCode="NAME" Required="True" />
</Container>
</Dialog>
Dialog controlsbewerken
Beschikbare controls binnen een Dialog:
| Control | Beschrijving |
|---|---|
| Text | Tekstveld |
| TextArea | Groot tekstveld (meerdere regels) |
| Number | Numeriek veld |
| Date | Datumkiezer |
| DateTime | Datum+tijd kiezer |
| Time | Tijdkiezer |
| Duration | Duur (uren:minuten) |
| CheckBox | Vinkje |
| CheckBoxGroup | Groep vinkjes |
| RadioGroup | Keuzerondjes |
| ComboBox | Keuzelijst |
| SelectionList | Selectielijst uit database |
| EmailAddress | Email-adres selectie |
| FileSelector | Bestand uploaden |
| Signature | Digitale handtekening |
| Location | Locatiekiezer |
| Html | HTML-teksteditor |
| Password | Wachtwoordveld |
| Literal | Alleen-lezen tekst |
| ListSelector | Lijst met bron- en doellijst |
| SelectionPeriod | Periodeselectie |
Voorbeeld met meerdere controls:
<Dialog Name="UrenRegistratie" TitleCode="HOURREGISTRATION">
<Container TitleCode="DETAILS" Border="True">
<SelectionList Name="Employee" OutputProperty="${SelectedEmployee}"
ColumnName="EmpId" Required="True" LabelCode="EMPLOYEE" />
<Number Name="Hours" OutputProperty="${Hours}" LabelCode="HOURS"
DecimalPrecision="2" MinValue="0" />
<DateTime Name="StartDate" OutputProperty="${StartDate}"
LabelCode="STARTDATE" Required="True" IntervalMinutes="15" />
<TextArea Name="Remark" OutputProperty="${Remark}" LabelCode="REMARK"
TextAreaRows="4" />
</Container>
</Dialog>
Configuratiedetails per control:
- Dialog Container is VERPLICHT: alle controls moeten binnen een
<Container>staan. Een Container groepeert controls visueel en structureel. Zonder Container worden controls niet correct gerenderd. - Text control:
Widthin pixels (bijv.Width="400")LabelCodeverwijst naar een Message in de database van type Label (niet type Info of Error)OutputPropertyslaat de ingevoerde waarde op als variabele voor later gebruik in de workflow
- SelectionList control:
ColumnName= de primary key van de entiteit (bijv.EqmIdvoor Equipment,JobIdvoor Job)SqlWhereClausevoor filtering — gebruik hier kolomnamen (databasenamen), niet property names uit de workflow (bijv.EqmContext = 1, nietjobContext.cd)Required="True"maakt het veld verplicht;Required="False"staat lege selectie toe- Voorbeeld:
<Dialog Name="KiesEquipment" TitleCode="SEL_EQUIPMENT" AllowCancel="True">
<Container Border="True">
<SelectionList Name="Equipment" OutputProperty="${geselecteerdEquipment}"
ColumnName="EqmId" LabelCode="EQUIPMENT" Required="True"
SqlWhereClause="EqmContext = 1 AND EqmStatus = 10" />
</Container>
</Dialog>
Consultant tip: Gebruik Ctrl+drag in de Workflow Designer om bestaande blokken (inclusief hun kinderen) te kopiëren naar een andere positie in de workflow. Dit bespaart tijd bij terugkerende patronen zoals Container + SelectionList.
Dialog validatiebewerken
<Validations>
<ValidationExpression Name="Check waarden ingevuld"
Condition="${Hours} != Empty || ${FixedPrice} != Empty"
MessageCode="1030" ValidationType="Error" TriggerType="OnClose" />
<ValidationWorkflow Name="Validate status"
WorkflowName="Validation_ChangeProgressStatus">
<Parameter Name="NewProgressStatus" Direction="In" Value="${NewStatus}" />
</ValidationWorkflow>
</Validations>
Dialog updatesbewerken
Gebruik UpdateWorkflow om dialoog-waarden dynamisch bij te werken:
<Updates>
<UpdateWorkflow Name="Get defaults" WorkflowName="Dialog_GetDefaults">
<Parameter Name="Employee" Direction="In" Value="${Employee}" />
<Parameter Name="DefaultCategory" Direction="Out" OutputProperty="${DefaultCategory}" />
</UpdateWorkflow>
</Updates>
User Inputbewerken
Messagebewerken
Toon een meldingsbericht aan de gebruiker.
<Message Name="Bevestiging" MessageCode="0169">
<Parameter Name="WorkOrder" Direction="In" Value="${WorkOrder}" />
</Message>
Questionbewerken
Stel een Ja/Nee (of Ja/Nee/Annuleer) vraag.
<Question Name="Bevestig" Type="YesNo" MessageCode="0000"
Default="Yes" OutputProperty="${QuestionResult}" />
ContinuationQuestionbewerken
Ja/Nee vraag waarbij "Nee" de workflow afbreekt.
<ContinuationQuestion Name="Doorgaan?" MessageCode="0846">
<Parameter Name="Article" Direction="In" Value="${JobMaterial.Article}" />
</ContinuationQuestion>
SystemDialogbewerken
Toon een systeemdialoog (bijv. een selectiescherm).
<SystemDialog Name="Kies Equipment" DialogName="SelectDomainObject">
<Parameter Name="EntityName" Direction="In" Value="Equipment" />
<Parameter Name="SqlWhereClause" Direction="In" Value="EqmContext = 1" />
<Parameter Name="DomainObject" Direction="Out" OutputProperty="${SelectedEquipment}" />
</SystemDialog>
Viewbewerken
Open een scherm na een workflow-actie.
<View Name="Open job" ViewName="DataEntryScreen">
<Parameter Name="DomainObject" Direction="In" Value="${CreatedJob}" />
<Parameter Name="ScreenName" Direction="In" Value="${FormName}" />
</View>
Volledige parameterlijst:
| Parameter | Richting | Beschrijving | Voorbeeld |
|---|---|---|---|
DomainObject |
In | Het te openen record | ${newJob} |
ScreenName |
In | Schermcode (zie Screens in UCT) | job03 |
CreateNewRecord |
In | False als het record al via Insert is aangemaakt; True voor een nieuw leeg scherm |
False |
OpenNewWindow |
In | True om het scherm in een nieuwe browsertab te openen |
True |
FocusEditableField |
In | True om de cursor direct in het eerste bewerkbare veld te plaatsen |
True |
Voorbeeld na Insert:
<View Name="Open nieuw job-scherm" ViewName="DataEntryScreen">
<Parameter Name="DomainObject" Direction="In" Value="${newJob}" />
<Parameter Name="ScreenName" Direction="In" Value="job03" />
<Parameter Name="CreateNewRecord" Direction="In" Value="False" />
<Parameter Name="OpenNewWindow" Direction="In" Value="True" />
<Parameter Name="FocusEditableField" Direction="In" Value="True" />
</View>
Consultant tip: Gebruik
CreateNewRecord="False"altijd na een Insert — anders probeert Ultimo opnieuw een record aan te maken op het openingsscherm, wat een dubbel record of een fout veroorzaakt.
VerifyElectronicSignaturebewerken
Vraag de gebruiker om een elektronische handtekening (wachtwoord + reden).
<VerifyElectronicSignature Name="Verifieer" />
Import / Exportbewerken
Importbewerken
Importeer data vanuit XML of CSV.
<Import Name="Import data" Data="${Input}" OutputProperty="${Output}"
RollbackOnError="True" ProcessObjects="True" Culture="NL" />
Attributen:
- RollbackOnError - Rollback bij fouten
- ProcessObjects - Voer workflow business logica uit tijdens import
- ImportFormat -
Xml(default) ofJson - ErrorOutputProperty - Property voor foutmeldingen
- ErrorCount / InsertCount / UpdateCount etc. - Tellerproperties
Exportbewerken
Exporteer data naar XML of CSV.
<Export Type="${ExportConnector.EntityToExport}" Data="${Data}"
OutputProperty="${Output}" ExportFormat="${ExportConnector.FileType}"
Culture="${ExportConnector.Culture}" Encoding="${ExportConnector.OutputEncoding}"
Separator="${ExportConnector.CsvSeparator}" />
Documentbewerken
CreateDocument / DocumentGridPagebewerken
Maak een Excel of XML document met data.
<CreateDocument Name="Export" DocumentType="Excel" FileName="Export.xlsx"
ConvertHtmlToPlainText="True">
<DocumentGridPage Name="Cost overzicht" PageTitle="Kosten" Type="Cost"
ViewfieldConfiguration="default">
<Properties>
<Property Name="Id" Alias="CostId" Type="Property" />
<Property Name="Context" Alias="Context" Type="Property" />
</Properties>
</DocumentGridPage>
</CreateDocument>
Downloadbewerken
Download een bestand uit de FileService. De workflow stopt na het downloaden.
<Download Name="Download rapport" FileName="Documents\Rapport.pdf" />
Filebewerken
Instructies voor bestandsbeheer in de Ultimo FileService.
| Instructie | Beschrijving |
|---|---|
| OpenFile | Open een bestand als stream |
| SaveFile | Sla een bestand op |
| CopyFile | Kopieer een bestand |
| MoveFile | Verplaats/hernoem een bestand |
| DeleteFile | Verwijder een bestand |
| FileExists | Controleer of bestand bestaat |
| CreateFolder | Maak een map aan |
| CopyDirectory | Kopieer een map |
| MoveFolder | Verplaats/hernoem een map |
| DeleteFolder | Verwijder een map |
| FolderExists | Controleer of map bestaat |
| GetFileList | Lijst van bestanden in een map |
OpenFilebewerken
Open een bestand uit de FileService als stream. Alle bestanden moeten binnen de FileServiceData-locatie staan.
<OpenFile Name="Open" FileName="Documents\input.txt" OutputProperty="${Stream}" Encoding="utf-8" />
Attributen:
- FileName - Pad binnen de FileService
- OutputProperty - Property van type
FileServiceStreamContainer - Encoding - Encoding (bijv.
utf-8,utf-16,iso-8859-1)
SaveFilebewerken
Sla een bestand op in de FileService. Data moet van het type DocumentContainer zijn.
<SaveFile Name="Save" FileName="Documents\Report.pdf" Data="${ReportStream}" />
CopyFilebewerken
Kopieer een bestand naar een nieuwe locatie in de FileService.
<CopyFile Name="Copy" SourceFile="${Source.ImageFile}" TargetFile="${Target.ImageFile}" />
MoveFilebewerken
Verplaats of hernoem een bestand in de FileService.
<MoveFile Name="Move" SourceFile="Import\input.xml" TargetFile="Import\processed\input.xml" />
DeleteFilebewerken
Verwijder een bestand uit de FileService. Kan niet ongedaan worden gemaakt.
<DeleteFile Name="Delete" FileName="Import\${ConnectorId}\Input\upload" />
FileExistsbewerken
Controleer of een bestand bestaat in de FileService.
<FileExists Name="Check" FileName="${Dir}\${FileName}" OutputProperty="${Exists}" />
GetFileListbewerken
Haal een lijst van bestandsnamen op uit een map in de FileService.
<GetFileList Name="Get files" OutputProperty="${Files}" FolderName="Workflows"
SearchPattern="*.wfl" OrderBy="Name" />
Attributen:
- OutputProperty - Van type
List[String] - FolderName - Map in de FileService
- SearchPattern - Regex of wildcardpatroon voor bestandsnamen
- OrderBy - Sortering (bijv.
Name)
CreateFolderbewerken
Maak een nieuwe map aan in de FileService.
<CreateFolder Name="MaakMap" FolderName="Documents" />
CopyDirectorybewerken
Kopieer een map inclusief inhoud naar een nieuwe locatie.
<CopyDirectory Name="Copy" SourceDirectory="${SourceDirectory}" TargetDirectory="${TargetDirectory}" />
MoveFolderbewerken
Verplaats of hernoem een map in de FileService.
<MoveFolder Name="Move" OriginalFolderName="${OldName}" NewFolderName="${NewName}" />
DeleteFolderbewerken
Verwijder een map uit de FileService.
<DeleteFolder Name="Delete" FolderName="${FullPath}" />
FolderExistsbewerken
Controleer of een map bestaat in de FileService.
<FolderExists Name="Check" FolderName="Documents" OutputProperty="${FolderExists}" />
Printbewerken
Druk data af naar een printer geconfigureerd in UCT.
<Print Name="Print" PrinterId="0001" FileName="Documents\Order.pdf"
Landscape="False" Data="${Data}" />
Attributen:
- PrinterId - Id van de printer in UCT
- FileName - Tijdelijke bestandsnaam voor printdata
- Landscape -
Truevoor liggend formaat (niet van toepassing op email-printers) - Data - De data om af te drukken
Reportbewerken
CreateReportbewerken
Genereer een rapport (Crystal Reports) in verschillende formaten.
<CreateReport Name="Genereer rapport" ReportName="job01" ReportType="Pdf"
OutputProperty="${ReportData}" ShowTrashedRecords="False">
<CollectInput Name="Input" DialogTitleCode="CRITERIA">
<CollectInputFormula Name="F1" ColumnName="EqmId" Operator="Like"
ResultType="string" SqlWhereClause="EqmContext = 1" />
</CollectInput>
<ReportParameter Name="P1" Value="${Job.Id}" />
<ReportFormulaValue Name="F1" ColumnName="JobId" Operator="=" Value="${Job.Id}" ResultType="string" />
</CreateReport>
ReportType opties: Pdf, Word, Excel, ExcelDataOnly, Rtf, EditableRtf, Csv, Html, Xml
Printbewerken
Druk data af naar een printer (geconfigureerd in UCT).
<Print Name="Print" PrinterId="0001" FileName="Documents\Order.pdf"
Landscape="False" Data="${Data}" />
Textbewerken
FormatTextbewerken
Formatteer tekst via de template engine. Gebruik een TextCode uit de database of een inline template.
<FormatText Name="Format label" TextCode="ARCGISCONDITIONPEROBJECT"
OutputProperty="${Title}" />
<FormatText Name="Format warning" TextCode="2938" OutputProperty="${Warning}">
<Parameters>
<Parameter Name="Reservation" Direction="In" Value="${Reservation}" />
</Parameters>
</FormatText>
CheckFormatText / UnformatTextbewerken
Reguliere expressies: valideer of parseer tekst.
<!-- Valideer met regex -->
<CheckFormatText Name="Check URL" Text="${Url}"
Mask="^https?://.*$" CaseSensitive="False" OutputProperty="${IsValid}" />
<!-- Parseer tekst met regex groepen -->
<UnformatText Name="Extract filename" Text="${UploadedFile}" Mask="[^\\]*$"
CaseSensitive="False" OutputProperty="${FileName}">
<Parameter Name="0" Direction="Out" OutputProperty="${FileName}" />
</UnformatText>
XMLbewerken
XmlParser / CsvToXmlParserbewerken
Parseer XML of CSV naar een XmlDocumentContainer voor verdere verwerking.
<XmlParser Name="Parse XML" Input="${Input}" OutputProperty="${XmlDoc}" />
<CsvToXmlParser Name="Parse CSV" Input="${Input}" Separator=";" Type="Employee"
OutputProperty="${XmlDoc}" Encoding="UTF-8" Action="InsertOrUpdate" HasHeaderLine="True" />
XmlTransformbewerken
Transformeer XML met een XSLT stylesheet.
<XmlTransform Name="Transform" Input="${XmlDoc}" XsltName="import.xslt"
OutputProperty="${TransformedDoc}" Encoding="UTF-8" />
XmlValidationbewerken
Valideer XML tegen een XSD schema.
<XmlValidation Name="Validate" Input="${XmlDoc}" XsdName="schema.xsd" />
Listbewerken
AddToList / RemoveFromListbewerken
Voeg items toe aan of verwijder items uit een lijst.
<AddToList Name="Voeg toe" List="${Articles}" Item="${Article}" />
<RemoveFromList Name="Verwijder" List="${Jobs}" Item="${Job}" />
Filterbewerken
Filter een lijst op basis van een conditie.
<Filter Name="Niet-lege items" InList="${InList}" OutList="${ResultList}"
As="${value}" Condition="${value} != Empty" />
Mathbewerken
Add / Subtractbewerken
Reken met twee numerieke waarden.
<Add Name="Verhoog teller" ValueLeft="${Counter}" ValueRight="1" OutputProperty="${Counter}" />
<Subtract Name="Bereken verschil" ValueLeft="100" ValueRight="${Used}" OutputProperty="${Remaining}" />
Data (Test)bewerken
Instructies voor het beheren van testdata in scenario-tests.
InsertTestDatabewerken
Maak testdata aan voor een scenario. De testdata wordt automatisch verwijderd wanneer het scenario klaar is. Benader testdata via ${TestData.[Name]}. Naamconventie: recordtype-prefix gevolgd door twee cijfers in lowercase, bijv. emp01, job01, eqm01.
<InsertTestData Name="OBJ1" ObjectType="Job" />
<!-- Benader via ${TestData.OBJ1} -->
Attributen:
- Name - Naam van de testdata (wordt gebruikt als referentie)
- ObjectType - Type entiteit om aan te maken (Job, Employee, Equipment, etc.)
- Parameters - Veldwaarden voor het testrecord
Let op: Bij een compound key hoeft alleen de LineId leeg gelaten te worden; de Id wordt automatisch gegenereerd.
SetRelationbewerken
Stel een relatie in op een testdata-object. Gebruik dit voor properties die niet gezet kunnen worden tijdens creatie, of voor circulaire referenties.
<SetRelation Name="Set Multijob" Relation="${TestData.Job.Multijob}" Value="${TestData.Job}" />
Attributen:
- Relation - Referentie naar de relatie, bijv.
${TestData.JOB1.Employee} - Value - Waarde van de relatie
Datebewerken
CalculateWeekOfYearbewerken
Bereken het weeknummer van een datum.
<CalculateWeekOfYear Name="Weeknummer" Date="#{Environment.CurrentDate}" WeekOfYear="${WeekDate}" />
Debugbewerken
Debugbewerken
Toon een debug-bericht. Alleen in Development/Test omgevingen. De workflow stopt bij het debug-bericht.
<Debug Name="Debug" Message="${Job.HoursCalculated}" />
<Debug Name="Debug expression" Message="#{Environment.CurrentDateTime}" />
<Debug Name="Debug berekening" Message="#round(3.33335, 2)" />
Consultant tip: Verwijder altijd Debug instructies voor deployment naar productie. In productie zorgt Debug voor een foutmelding.
Gerelateerde artikelenbewerken
- workflow-engine - Hoe de workflow engine werkt
- standaard-workflows - Overzicht van alle standaard workflows per entiteit
- expressions - Expression functies
- workflow-debugger - Debugging tips
Custom Workflow XML: Hash en Security attributenbewerken
Bij het aanmaken van custom workflows in Ultimo worden door de workflow engine automatisch extra attributen toegevoegd aan de XML. Consultants en AI-assistenten moeten hier rekening mee houden bij het genereren van workflow XML.
Hash attribuutbewerken
Ultimo gebruikt een Hash attribuut op het <Workflow> element om de integriteit van de workflow te valideren bij import.
Hash-algoritme (reverse engineered en geverifieerd):
SHA1( UTF-8 bytes van de volledige XML, zonder BOM, zonder het Hash="..." attribuut )
Concreet:
- Neem de volledige workflow XML
- Verwijder het
Hash="..."attribuut (inclusief de spatie ervoor) - Encodeer als UTF-8 (zonder BOM)
- Bereken SHA1 → uppercase hex string
Python implementatie:
import hashlib
import re
def compute_ultimo_hash(xml_text: str) -> str:
"""Bereken Ultimo workflow hash. Input: XML string zonder BOM."""
no_hash = re.sub(r' Hash="[^"]*"', '', xml_text)
return hashlib.sha1(no_hash.encode('utf-8')).hexdigest().upper()
def add_hash_to_workflow(xml_text: str) -> str:
"""Voeg Hash attribuut toe aan workflow XML. Normaliseert naar CRLF."""
xml_text = xml_text.replace('\r\n', '\n').replace('\r', '\n').replace('\n', '\r\n')
h = compute_ultimo_hash(xml_text)
if 'Hash="' in xml_text:
return re.sub(r'Hash="[^"]*"', f'Hash="{h}"', xml_text)
else:
return xml_text.replace('<Workflow ', f'<Workflow Hash="{h}" ', 1)
Belangrijk:
- Het
.wflbestand bevat een UTF-8 BOM (EF BB BF) — maar die BOM wordt niet meegenomen in de hash-berekening - Het bestand gebruikt CRLF (
\r\n) regeleindes en tabs voor indentatie — deze worden wél meegenomen in de hash - Bij het genereren van workflows moet je CRLF regeleindes en tabs gebruiken om een correcte hash te krijgen
Security EditLevel en ViewLevelbewerken
Ultimo past de <Security> attributen aan op basis van het UCT-autorisatieniveau van de ingelogde gebruiker:
<!-- Standaard workflows (door Ultimo R&D) -->
<Security EditLevel="10" ViewLevel="20" UserContentLevel="30" />
<!-- Custom workflows (opgeslagen door consultant, typisch level 12+) -->
<Security EditLevel="12" ViewLevel="26" UserContentLevel="26" />
MessageCode 2060 als generiek berichtbewerken
Bij custom workflows gebruik je MessageCode 2060 als generiek bericht met een vrije Message parameter:
<Message Name="Done" MessageCode="2060">
<Parameter Name="Message" Direction="In" Value="Mijn custom bericht" />
</Message>
<Validation Name="Check" Condition="${X} != Empty" MessageCode="2060">
<Parameter Name="Message" Direction="In" Value="X mag niet leeg zijn." />
</Validation>
<ContinuationQuestion Name="Bevestig" MessageCode="2060">
<Parameter Name="Message" Direction="In" Value="Weet je het zeker?" />
</ContinuationQuestion>
Gebruik NIET MessageCode 0480 — die wordt door Ultimo geïnterpreteerd als "Dit actieveld is vervallen".
Hash attribuutbewerken
Ultimo voegt automatisch een Hash attribuut toe aan het <Workflow> element:
<Workflow Hash="00D95683F9D076F0B399A30AF8A077A3097A0CAA" Name="_MijnWorkflow" ...>
- De Hash is een SHA1-hash van de workflow XML-inhoud
- Ultimo genereert deze automatisch bij het opslaan
- Je hoeft de hash niet zelf te berekenen of mee te geven — Ultimo vult deze aan
- Bij elke wijziging wordt de hash automatisch herberekend
Security EditLevel en ViewLevelbewerken
Ultimo past ook de <Security> attributen aan op basis van het niveau van de gebruiker die de workflow opslaat:
<!-- Standaard workflows (door Ultimo R&D) -->
<Security EditLevel="10" ViewLevel="20" UserContentLevel="30" />
<!-- Custom workflows (opgeslagen door consultant, typisch level 12+) -->
<Security EditLevel="12" ViewLevel="26" UserContentLevel="26" />
De levels worden automatisch ingesteld op basis van het UCT-autorisatieniveau van de ingelogde gebruiker.
Praktische gevolgen voor het genereren van workflow XMLbewerken
- Hash NIET meegeven — Ultimo voegt deze automatisch toe bij opslaan
- Security levels worden automatisch gezet — gebruik standaard
EditLevel="10" ViewLevel="20" UserContentLevel="30"in templates - Properties worden alfabetisch gesorteerd door Ultimo bij opslaan — de volgorde in broncode maakt niet uit
- Bij het kopiëren van workflows tussen omgevingen wordt de hash opnieuw berekend
Voorbeeld: workflow XML vóór opslaan (door consultant/AI gegenereerd)bewerken
<Workflow Name="_MijnCustomWorkflow" Version="2025.07.28" WorkflowType="Standard" xmlns="urn:Ultimo.Framework.Workflow-mapping">
<Security EditLevel="10" ViewLevel="20" UserContentLevel="30" />
<Description>Mijn beschrijving</Description>
<Properties>
<Property Name="MyEntity" Type="Job" Accessor="Root" Direction="In" />
</Properties>
<Execution>
<UserContent Name="Pre" />
<!-- logica -->
<UserContent Name="Post" />
</Execution>
</Workflow>
Dezelfde workflow XML ná opslaan door Ultimobewerken
<Workflow Hash="A1B2C3D4E5F6..." Name="_MijnCustomWorkflow" Version="2025.07.28" WorkflowType="Standard" xmlns="urn:Ultimo.Framework.Workflow-mapping">
<Security EditLevel="12" ViewLevel="26" UserContentLevel="26" />
<Description>Mijn beschrijving</Description>
<Properties>
<Property Name="MyEntity" Type="Job" Accessor="Root" Direction="In" />
</Properties>
<Execution>
<UserContent Name="Pre" />
<!-- logica -->
<UserContent Name="Post" />
</Execution>
</Workflow>
MessageCode 2060 als generiek berichtbewerken
Bij custom workflows gebruik je MessageCode 2060 als generiek bericht met een vrije Message parameter:
<Message Name="Done" MessageCode="2060">
<Parameter Name="Message" Direction="In" Value="Mijn custom bericht" />
</Message>
<Validation Name="Check" Condition="${X} != Empty" MessageCode="2060">
<Parameter Name="Message" Direction="In" Value="X mag niet leeg zijn." />
</Validation>
<ContinuationQuestion Name="Bevestig" MessageCode="2060">
<Parameter Name="Message" Direction="In" Value="Weet je het zeker?" />
</ContinuationQuestion>
Gebruik NIET MessageCode 0480 — die wordt door Ultimo geïnterpreteerd als "Dit actieveld is vervallen".
Brondatabewerken
Dit artikel beschrijft workflow-concepten en -patronen. Voor de feitelijke workflow-XML van een specifieke trigger of ActionField:
- Workflow opvragen —
lookup_workflow("<WorkflowName>")Volledige workflow-XML incl. properties en CurrentValue. Bron:workflows.xml. - Workflows doorzoeken —
find_workflows(query, entity=None)Fuzzy zoeken op naam, beschrijving of per entiteit. Bron:workflows.xml. - Alle ActionFields als index — ActionFields index
1341 gegenereerde pagina's, één per ActionField. Bron:
workflows.xml. - Entiteit-definities voor referenties in workflows —
lookup_entity("<Name>")Properties en kolomnamen van entiteiten waarnaar workflows verwijzen. Bron:Entities.xml. - Kennisbank-breed zoeken —
search(query)Doorzoekt alle wiki + reference + workflows tegelijk.