Workflow Patronen
Dit artikel beschrijft veelvoorkomende workflow patronen die Ultimo consultants in de praktijk tegenkomen. Elk patroon bevat een beschrijving van het scenario, wanneer je het gebruikt, en een concreet XML voorbeeld. Zie workflow-instructies voor de volledige documentatie van elke instructie.
Job aanmaken vanuit een workflowbewerken
Scenariobewerken
Na het afmelden (Finished) van een Job moet automatisch een vervolgjob worden aangemaakt, bijvoorbeeld voor een herhalende inspectie of een vervolgactie.
Wanneerbewerken
- PostFinished workflows (na afmelding)
- PostApproved workflows (bij goedkeuring automatisch gerelateerde jobs aanmaken)
- Scheduled workflows (periodiek jobs genereren)
Patroonbewerken
<Workflow Name="Job_PostFinished_UserContent_Post" Version="2025.07.28"
WorkflowType="ChangeStatus" AllowUserInteraction="False"
xmlns="urn:Ultimo.Framework.Workflow-mapping">
<Security EditLevel="30" ViewLevel="30" UserContentLevel="30" />
<Description>Maak vervolgjob aan na afmelding</Description>
<Properties>
<Property Name="Job" Type="Job" Accessor="Root" Direction="In" />
<Property Name="NewJob" Type="Job" Accessor="Internal" />
</Properties>
<Execution>
<Transaction>
<!-- Alleen als het een specifiek type job is -->
<When Name="Is inspectiejob" Condition="${Job.Context} == JobContext.TD">
<Insert Name="Maak vervolgjob" ObjectType="Job" OutputProperty="${NewJob}">
<Parameter Name="Status" Direction="In" Value="JobStatus.Created" />
<Parameter Name="Description" Direction="In" Value="${Job.Description}" />
<Parameter Name="Equipment" Direction="In" Value="${Job.Equipment}" />
<Parameter Name="Department" Direction="In" Value="${Job.Department}" />
<Parameter Name="Employee" Direction="In" Value="${Job.Employee}" />
<Parameter Name="Site" Direction="In" Value="${Job.Site}" />
<Parameter Name="ScheduledStartDate" Direction="In"
Value="=#addmonths(${Job.FinishedDate}, 6)" />
</Insert>
</When>
</Transaction>
</Execution>
</Workflow>
Alternatief: Command gebruikenbewerken
Voor complexere job-creatie met volledige initialisatie:
<Transaction>
<Command Name="Job_CreateAndInitialize" CommandName="Job_CreateAndInitialize">
<Parameter Name="JobCreateMethod" Direction="In" Value="CreateApproved" />
<Parameter Name="Job" Direction="Out" OutputProperty="${NewJob}" />
</Command>
<Assign Name="Set equipment" Property="${NewJob.Equipment}" Value="${Job.Equipment}" />
<Assign Name="Set beschrijving" Property="${NewJob.Description}" Value="Vervolg op ${Job.Id}" />
</Transaction>
Consultant tip: Gebruik
Commandals je de volledige Ultimo initialisatielogica wilt doorlopen (context, standaardwaarden, etc.). GebruikInsertals je volledige controle wilt over welke velden worden gevuld.
Email versturen bij statuswijzigingbewerken
Scenariobewerken
Bij een statuswijziging van een Job (bijv. goedkeuring, gereed melding) moet een email worden verstuurd.
Wanneerbewerken
- PostApproved, PostActive, PostFinished workflows
- Altijd in een Post UserContent (na de statuswijziging)
Twee-stappen patroon (verplicht)bewerken
Email versturen in Ultimo werkt altijd in twee stappen binnen dezelfde Transaction:
- Email aanmaken als Concept (
Concept="True"+OutputProperty) - ChangeStatus naar Draft (
EmailStatus.Draft)
Zonder deze twee stappen wordt de email niet verzonden.
Patroon: email naar medewerkerbewerken
<!-- Property declareren (in standaard workflow) -->
<Property Name="Email" Type="Email" Accessor="Internal" />
<!-- In UserContent Post -->
<When Name="Medewerker heeft email" Condition="${Job.Employee} != Empty && ${Job.Employee.EmailAddress} != Empty">
<Transaction>
<Email Name="Notificeer medewerker" From="#{UltimoSettings.EmailSender}"
To="${Job.Employee}" CC="${Job.Employee.Manager}"
RelatedSubjects="${Job}" EmailTemplateCode="9001"
Concept="True" OutputProperty="${Email}">
<Parameters>
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Parameters>
</Email>
<ChangeStatus Name="ChangeStatusEmail" DomainObject="${Email}"
NewStatus="EmailStatus.Draft" />
</Transaction>
</When>
Patroon: email naar vast emailadres vanuit UCBbewerken
Bij gebruik in een UserContent Block moet de Email property worden gedeclareerd in het UCB Properties tab (type Email, accessor Internal).
<!-- In UserContent Post van Job_ChangeProgressStatus -->
<When Name="Mail bij gereed en WOT 1007"
Condition="${NewProgressStatus.NextStatus} == JobStatus.Finished && ${Job.WorkOrderType.Id} == '1007'">
<Transaction>
<Email Name="Mail naar Loparex"
From="#{UltimoSettings.EmailSender}"
To="ontvanger1@bedrijf.com;ontvanger2@bedrijf.com"
RelatedSubjects="${Job}" EmailTemplateCode="9100"
Concept="True" OutputProperty="${Email}">
<Parameters>
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Parameters>
</Email>
<ChangeStatus Name="Set email to Draft" DomainObject="${Email}"
NewStatus="EmailStatus.Draft" />
</Transaction>
</When>
Met bijlagenbewerken
<Transaction>
<Email Name="Stuur rapport" From="#{UltimoSettings.EmailSender}"
To="${Job.Employee}" RelatedSubjects="${Job}" EmailTemplateCode="9002"
Concept="True" OutputProperty="${Email}">
<Parameters>
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Parameters>
<Attachments>
<Attachment FileName="Werkbon.pdf" Data="${ReportData}" />
</Attachments>
</Email>
<ChangeStatus Name="ChangeStatusEmail" DomainObject="${Email}"
NewStatus="EmailStatus.Draft" />
</Transaction>
TextTemplate voorbeeldbewerken
De EmailTemplateCode verwijst naar een TextTemplate in de database (tabel STEXTTEMPLATE). Een typisch template:
Onderwerp: Job ${Job.Id} is goedgekeurd
Beste #if(${Job.Employee} != Empty)${Job.Employee.Description}#end,
Job ${Job.Id} "${Job.Description}" is goedgekeurd.
Equipment: ${Job.Equipment.Description}
Locatie: ${Job.Site.Description}
Geplande startdatum: ${Job.ScheduledStartDate}
Met vriendelijke groet,
Ultimo
Consultant tip: Maak altijd een check of de ontvanger een emailadres heeft. Een Email instructie naar een onbestaand adres veroorzaakt een fout in de workflow. Gebruik
#{UltimoSettings.EmailSender}als From-adres (niet#{Settings.EmailSender}). Bron: standaard workflow ActionField667.
Scenariobewerken
Bij goedkeuring van een Job moet een email worden verstuurd naar de verantwoordelijke medewerker en diens leidinggevende.
Wanneerbewerken
- PostApproved, PostActive, PostFinished workflows
- Altijd in een Post UserContent (na de statuswijziging)
Patroonbewerken
<!-- In UserContent Post van Job_PostApproved -->
<When Name="Medewerker heeft email" Condition="${Job.Employee} != Empty && ${Job.Employee.EmailAddress} != Empty">
<Email Name="Notificeer medewerker" From="#{Settings.EmailSender}"
To="${Job.Employee}" CC="${Job.Employee.Manager}"
RelatedSubjects="${Job}" EmailTemplateCode="9001">
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Email>
</When>
Met bijlagenbewerken
<Email Name="Stuur rapport" From="#{Settings.EmailSender}"
To="${Job.Employee}" RelatedSubjects="${Job}" EmailTemplateCode="9002">
<Parameter Name="Job" Direction="In" Value="${Job}" />
<Attachments>
<Attachment FileName="Werkbon.pdf" Data="${ReportData}" />
</Attachments>
</Email>
TextTemplate voorbeeldbewerken
De EmailTemplateCode verwijst naar een TextTemplate in de database (tabel STEXTTEMPLATE). Een typisch template:
Onderwerp: Job ${Job.Id} is goedgekeurd
Beste #if(${Job.Employee} != Empty)${Job.Employee.Description}#end,
Job ${Job.Id} "${Job.Description}" is goedgekeurd.
Equipment: ${Job.Equipment.Description}
Locatie: ${Job.Site.Description}
Geplande startdatum: ${Job.ScheduledStartDate}
Met vriendelijke groet,
Ultimo
Consultant tip: Maak altijd een check of de ontvanger een emailadres heeft. Een Email instructie naar een onbestaand adres veroorzaakt een fout in de workflow.
Validatie voor opslaanbewerken
Scenariobewerken
Voor het goedkeuren van een Job moeten verplichte velden zijn ingevuld. Als dat niet zo is, wordt een foutmelding getoond en wordt de statuswijziging geblokkeerd.
Wanneerbewerken
- PreApproved, PreActive en andere Pre-status workflows
- UserContent Pre secties
Patroon: enkele validatiebewerken
<!-- In UserContent Pre van Job_PreApproved -->
<Validation Name="Equipment is verplicht"
Condition="${Job.Equipment} != Empty" MessageCode="9001">
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Validation>
<Validation Name="Afdeling is verplicht"
Condition="${Job.Department} != Empty" MessageCode="9002">
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Validation>
Patroon: complexe validatie met berekeningbewerken
<Transaction>
<GetSum Name="Tel threshold" Type="AbcCode" OutputProperty="${TotalThreshold}"
PropertyName="Threshold">
<Filters>
<PropertyFilter PropertyName="Status" Operator="=" PropertyValue="AbcCodeStatus.Approved" />
</Filters>
</GetSum>
</Transaction>
<Validation Name="Totaal threshold max 100"
Condition="${TotalThreshold} + ${AbcCode.Threshold} <= 100" MessageCode="3844">
<Parameter Name="AbcCode" Direction="In" Value="${AbcCode}" />
</Validation>
Patroon: validatie met gebruikersbevestigingbewerken
Soms wil je de gebruiker laten kiezen of ze door willen gaan ondanks een waarschuwing:
<When Name="Negatieve voorraad" Condition="${Article.Stock} - ${Quantity} < 0">
<ContinuationQuestion Name="Doorgaan?" MessageCode="0846">
<Parameter Name="Article" Direction="In" Value="${Article}" />
</ContinuationQuestion>
</When>
Consultant tip: Custom MessageCodes beginnen bij 9000. Voeg ze toe via UCT > Messages of direct in de tabel SMESSAGECOUNTRY.
Scheduled workflowsbewerken
Scenariobewerken
Elke nacht moeten verlopen contracten worden gedeactiveerd en moeten er herinneringen worden verstuurd voor contracten die binnenkort verlopen.
Wanneerbewerken
- Periodieke taken (dagelijks, wekelijks, maandelijks)
- Batch-verwerking van records
- Automatische statuswijzigingen op basis van datum
Patroonbewerken
<Workflow Name="ScheduledTask_ContractExpiry" Version="2025.07.28"
WorkflowType="Standard" AllowUserInteraction="False"
xmlns="urn:Ultimo.Framework.Workflow-mapping">
<Security EditLevel="10" ViewLevel="20" UserContentLevel="30" />
<Description>Verwerk verlopen contracten</Description>
<Properties>
<Property Name="ExpiredContracts" Type="List[ServiceContract]" Accessor="Internal" />
<Property Name="ExpiringContracts" Type="List[ServiceContract]" Accessor="Internal" />
<Property Name="WarningDays" Type="Int32" Accessor="Internal" Default="30" />
</Properties>
<Execution>
<UserContent Name="Pre" />
<!-- Verlopen contracten deactiveren -->
<Transaction>
<GetList Name="Haal verlopen contracten op" Type="ServiceContract"
OutputProperty="${ExpiredContracts}">
<Filters>
<CombinedFilter FilterOperator="And">
<PropertyFilter PropertyName="Status" Operator="="
PropertyValue="ServiceContractStatus.Active" />
<DateFilter PropertyName="EndDate" Operator="<"
PropertyValue="#{Environment.CurrentDate}" />
</CombinedFilter>
</Filters>
</GetList>
<ForEach Name="Deactiveer verlopen" In="${ExpiredContracts}" As="Contract">
<ChangeStatus Name="Deactiveer" DomainObject="${Contract}"
NewStatus="ServiceContractStatus.Expired" />
</ForEach>
</Transaction>
<!-- Waarschuwing voor bijna verlopen contracten -->
<Transaction>
<GetList Name="Bijna verlopen contracten" Type="ServiceContract"
OutputProperty="${ExpiringContracts}">
<Filters>
<CombinedFilter FilterOperator="And">
<PropertyFilter PropertyName="Status" Operator="="
PropertyValue="ServiceContractStatus.Active" />
<DateFilter PropertyName="EndDate" Operator="<="
PropertyValue="=#adddays(#{Environment.CurrentDate}, ${WarningDays})" />
<DateFilter PropertyName="EndDate" Operator=">="
PropertyValue="#{Environment.CurrentDate}" />
</CombinedFilter>
</Filters>
</GetList>
<ForEach Name="Stuur waarschuwing" In="${ExpiringContracts}" As="Contract">
<Reminder Name="Herinnering" Recipients="${Contract.ContactEmployee}"
ReminderTemplateCode="9010" RelatedSubjects="${Contract}" />
</ForEach>
</Transaction>
<UserContent Name="Post" />
</Execution>
</Workflow>
Configuratie in UCTbewerken
Scheduled workflows worden geconfigureerd via:
- UCT > Scheduler - Definieer het schema (cron-expressie of interval)
- Koppel de workflow aan de scheduler-taak
AllowUserInteractionis altijdFalse(er is geen gebruiker)
Consultant tip: Splits grote batch-taken op in stukken met
TopenWhileom geheugen- en performance-problemen te voorkomen. GebruikFlushof meerdere Transactions bij grote aantallen records.
Import/Export patronenbewerken
PreImport: records overslaanbewerken
Gebruik PreImport om bepaalde records over te slaan op basis van voorwaarden:
<!-- In UserContent Pre van Job_PreImport -->
<When Name="Skip inactieve jobs" Condition="${PropertyValues} != Empty && #containskey(${PropertyValues}, 'Status')">
<When Name="Status is Inactive" Condition="${PropertyValues['Status']} == 'Inactive'">
<Assign Name="Skip record" Property="${SkipRecord}" Value="True" />
</When>
</When>
PostImport: statuswijziging na importbewerken
Het standaard PostImport patroon verwerkt de statuswijziging die via de connector is meegegeven:
<!-- Standaard patroon in PostImport (al aanwezig in standaard workflows) -->
<Transaction>
<When Name="Status change requested" Condition="${ChangeStatusTo} != Empty">
<ChangeStatus Name="Change status" DomainObject="${Job}"
NewStatus="${ChangeStatusTo}" />
</When>
</Transaction>
Export workflow patroonbewerken
<Transaction>
<GetList Name="Haal data op" Type="Job" OutputProperty="${JobList}">
<Filters>
<PropertyFilter PropertyName="Status" Operator="="
PropertyValue="JobStatus.Finished" />
</Filters>
</GetList>
<Export Type="Job" Data="${JobList}" OutputProperty="${ExportOutput}"
ExportFormat="Xml" Culture="NL" Encoding="utf-8"
ExportGuid="=#guid()" />
<SaveFile Name="Sla export op" FileName="Export\Jobs_export.xml"
Data="${ExportOutput}" />
</Transaction>
CSV import met transformatiebewerken
<!-- Open het CSV bestand -->
<OpenFile Name="Open CSV" FileName="Import\employees.csv"
OutputProperty="${FileStream}" Encoding="utf-8" />
<!-- Parseer CSV naar XML -->
<CsvToXmlParser Name="Parse" Input="${FileStream}" Separator=";"
Type="Employee" OutputProperty="${XmlDoc}" Encoding="UTF-8"
Action="InsertOrUpdate" HasHeaderLine="True" />
<!-- Optioneel: transformeer met XSLT -->
<XmlTransform Name="Transform" Input="${XmlDoc}" XsltName="employee_import.xslt"
OutputProperty="${TransformedDoc}" />
<!-- Importeer -->
<Import Name="Import" Data="${TransformedDoc}" OutputProperty="${ImportResult}"
RollbackOnError="True" ProcessObjects="True" />
<!-- Verplaats verwerkt bestand -->
<MoveFile Name="Archiveer" SourceFile="Import\employees.csv"
TargetFile="Import\processed\employees.csv" />
Consultant tip: Gebruik altijd
RollbackOnError="True"bij imports in productie. Test eerst metProcessObjects="False"om de data te valideren zonder business logic uit te voeren.
UserContent hooks (Pre/Post)bewerken
Scenariobewerken
Je wilt extra logica toevoegen aan een standaard Ultimo workflow zonder de originele workflow te wijzigen.
Wanneerbewerken
- Altijd als je bedrijfslogica wilt aanpassen of uitbreiden
- Dit is de standaard manier voor consultants om workflows aan te passen
Hoebewerken
- Open in UCT de betreffende workflow (bijv.
Job_PostApproved) - Klik op de
UserContent PreofUserContent Postsectie - Voeg je instructies toe
- Publiceer de workflow
Voorbeeld: extra velden vullen bij creatie (Pre)bewerken
<!-- UserContent Pre van Job_PreApproved -->
<!-- Zet standaard doorlooptijd op basis van prioriteit -->
<Choose Name="Bepaal doorlooptijd">
<When Name="Hoge prioriteit" Condition="${Job.Priority.SortSequence} <= 2">
<Assign Name="Deadline 2 dagen" Property="${Job.Deadline}"
Value="=#adddays(#{Environment.CurrentDate}, 2)" />
</When>
<When Name="Normale prioriteit" Condition="${Job.Priority.SortSequence} <= 5">
<Assign Name="Deadline 1 week" Property="${Job.Deadline}"
Value="=#adddays(#{Environment.CurrentDate}, 7)" />
</When>
<Otherwise Name="Lage prioriteit">
<Assign Name="Deadline 1 maand" Property="${Job.Deadline}"
Value="=#addmonths(#{Environment.CurrentDate}, 1)" />
</Otherwise>
</Choose>
Voorbeeld: kopie maken van velden (Post)bewerken
<!-- UserContent Post van Job_PostActive -->
<!-- Kopieer gegevens naar een gerelateerd object -->
<Transaction>
<When Name="Equipment aanwezig" Condition="${Job.Equipment} != Empty">
<Assign Name="Laatste activatie" Property="${Job.Equipment.Custom1}"
Value="=#formatdate(#{Environment.CurrentDateTime}, 'd', false)" />
</When>
</Transaction>
Voorbeeld: meerdere validaties combineren (Pre)bewerken
<!-- UserContent Pre van Job_PreApproved -->
<Validation Name="Equipment verplicht"
Condition="${Job.Equipment} != Empty" MessageCode="9001" />
<Validation Name="Afdeling verplicht"
Condition="${Job.Department} != Empty" MessageCode="9002" />
<Validation Name="Startdatum verplicht"
Condition="${Job.ScheduledStartDate} != Empty" MessageCode="9003" />
<Validation Name="Startdatum in de toekomst"
Condition="${Job.ScheduledStartDate} >= #{Environment.CurrentDate}" MessageCode="9004" />
Bulk verwerking met Whilebewerken
Scenariobewerken
Grote aantallen records verwerken zonder geheugen- of timeout-problemen.
Patroonbewerken
<Properties>
<Property Name="AllDone" Type="Boolean" Accessor="Internal" Default="False" />
<Property Name="BatchSize" Type="Int32" Accessor="Internal" Default="500" />
<Property Name="Items" Type="List[Job]" Accessor="Internal" />
</Properties>
<Execution>
<While Name="Verwerk in batches" Condition="${AllDone} == False" CycleLimit="1000">
<Transaction>
<GetList Name="Haal batch op" Type="Job" OutputProperty="${Items}"
OrderBy="Id" Top="${BatchSize}">
<Filters>
<PropertyFilter PropertyName="Status" Operator="="
PropertyValue="JobStatus.Created" />
</Filters>
</GetList>
<When Name="Geen items meer" Condition="${Items.Count} == 0">
<Assign Name="Klaar" Property="${AllDone}" Value="True" />
</When>
<ForEach Name="Verwerk" In="${Items}" As="Item">
<ChangeStatus Name="Activeer" DomainObject="${Item}"
NewStatus="JobStatus.Active" />
</ForEach>
</Transaction>
</While>
</Execution>
Consultant tip: Door de Transaction binnen de While te plaatsen, wordt elke batch als aparte transactie verwerkt. Dit voorkomt dat het geheugen volloopt bij grote datasets.
Dialog met gebruikersinvoerbewerken
Scenariobewerken
Bij het afmelden van een Job moet de gebruiker extra informatie invoeren (reden, datum, etc.).
Patroonbewerken
<ChooseUserInteraction Name="UI check">
<WithUserInteraction Name="Met dialoog">
<Dialog Name="Afmelding" TitleCode="JOBFINISH" AllowCancel="True">
<Container Border="True" TitleCode="DETAILS">
<DateTime Name="FinishDate" Value="=#{Environment.CurrentDateTime}"
OutputProperty="${FinishDate}" Required="True" LabelCode="FINISHDATE" />
<SelectionList Name="Reason" OutputProperty="${FinishReason}"
ColumnName="FinishReasonId" Required="True" LabelCode="REASON" />
<TextArea Name="Remarks" OutputProperty="${Remarks}"
LabelCode="REMARK" TextAreaRows="4" />
</Container>
<Validations>
<ValidationExpression Name="Datum niet in toekomst"
Condition="${FinishDate} <= #{Environment.CurrentDateTime}"
MessageCode="9010" ValidationType="Error" />
</Validations>
</Dialog>
</WithUserInteraction>
<WithoutUserInteraction Name="Automatisch">
<Assign Name="Standaard datum" Property="${FinishDate}"
Value="=#{Environment.CurrentDateTime}" />
</WithoutUserInteraction>
</ChooseUserInteraction>
<Transaction>
<Assign Name="Sla afmelddatum op" Property="${Job.FinishedDate}" Value="${FinishDate}" />
<When Name="Opmerking ingevuld" Condition="${Remarks} != Empty">
<Assign Name="Sla opmerking op" Property="${Job.RemarkText1}" Value="${Remarks}" />
</When>
</Transaction>
ActionField patronen (knopacties op schermen)bewerken
Scenariobewerken
ActionField workflows zijn gekoppeld aan knoppen op Ultimo-schermen. Ze combineren validatie, statuswijziging en gebruikersfeedback in een gestructureerd patroon.
Wanneerbewerken
- Knoppen op data-entry schermen (bijv. "Goedkeuren", "Activeren", "Afmelden", "Sluiten")
- Elke actie die door een gebruikersklik wordt gestart
Patroon: Statuswijziging via knopbewerken
<Workflow Name="ActionField025" Version="2025.07.28"
WorkflowType="Standard"
xmlns="urn:Ultimo.Framework.Workflow-mapping">
<Security EditLevel="10" ViewLevel="20" UserContentLevel="30" />
<Description>Approve Job (from status created).</Description>
<Properties>
<Property Name="Job" Type="Job" Accessor="Root" Direction="In" />
</Properties>
<Execution>
<UserContent Name="Pre" />
<Transaction>
<!-- Optioneel: vooraf een command-check -->
<Command Name="Check job has site" CommandName="SiteCheck">
<Parameter Name="DomainObject" Direction="In" Value="${Job}" />
</Command>
<!-- Valideer dat de huidige status correct is -->
<Validation Name="JobStatus OK?"
Condition="${Job.Status} == JobStatus.Created" MessageCode="0055">
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Validation>
<!-- Voer de statuswijziging uit -->
<ChangeStatus Name="Approve job" DomainObject="${Job}"
NewStatus="JobStatus.Approved" />
</Transaction>
<UserContent Name="Post" />
<!-- Toon bevestiging aan gebruiker -->
<Message Name="Bevestiging" MessageCode="0258">
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Message>
</Execution>
</Workflow>
Patroon: Statuswijziging met bevestigingsvraagbewerken
<!-- Activeer Job met bevestigingsvraag -->
<Execution>
<UserContent Name="Pre" />
<ContinuationQuestion Name="Weet je het zeker?" MessageCode="0335">
<Parameter Name="Job" Direction="In" Value="${Job}" />
</ContinuationQuestion>
<Validation Name="Status check"
Condition="${Job.Status} == JobStatus.Approved || ${Job.Status} == JobStatus.Postponed"
MessageCode="0143">
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Validation>
<Transaction>
<ChangeStatus Name="Activeer" DomainObject="${Job}" NewStatus="JobStatus.Active" />
</Transaction>
<Message Name="Geactiveerd" MessageCode="0148">
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Message>
<UserContent Name="Post" />
</Execution>
Consultant tip: Let op de volgorde: UserContent Pre -> Validatie -> Transaction -> UserContent Post -> Message. De Message komt buiten de transaction zodat deze ook getoond wordt als de transaction succesvol was.
WorkOrder goedkeuren en Job aanmakenbewerken
Scenariobewerken
Bij het goedkeuren van een WorkOrder moet automatisch een Job worden aangemaakt en geopend op het scherm.
Patroonbewerken
<Execution>
<UserContent Name="Pre" />
<Validation Name="Status check"
Condition="${WorkOrder.Status} == WorkOrderStatus.Created || ${WorkOrder.Status} == WorkOrderStatus.Approved"
MessageCode="0168">
<Parameter Name="WorkOrder" Direction="In" Value="${WorkOrder}" />
</Validation>
<!-- Roep de goedkeuringsworkflow aan -->
<WorkflowCall Name="WorkOrder_Approve" WorkflowName="WorkOrder_Approve">
<Parameter Name="JobContext" Direction="In" Value="${JobContext}" />
<Parameter Name="WorkOrder" Direction="In" Value="${WorkOrder}" />
<Parameter Name="CreatedJob" Direction="Out" OutputProperty="${CreatedJob}" />
</WorkflowCall>
<UserContent Name="Post" />
<!-- Toon bevestiging -->
<Message Name="Job aangemaakt" MessageCode="0176">
<Parameter Name="Job" Direction="In" Value="${CreatedJob}" />
<Parameter Name="WorkOrder" Direction="In" Value="${WorkOrder}" />
</Message>
<!-- Open het nieuw aangemaakte Job-scherm -->
<View Name="Open job" ViewName="DataEntryScreen">
<Parameter Name="DomainObject" Direction="In" Value="${CreatedJob}" />
<Parameter Name="FocusEditableField" Direction="In"
Value="=${CreatedJob.Description} == Empty" />
<Parameter Name="ScreenName" Direction="In" Value="${FormName}" />
</View>
</Execution>
Consultant tip: De
FocusEditableFieldparameter met een expressie zorgt ervoor dat het eerste bewerkbare veld automatisch focus krijgt als de beschrijving nog leeg is.
PreDelete patroon: gerelateerde records opschonenbewerken
Scenariobewerken
Voor het definitief verwijderen van een record moeten gerelateerde records eerst worden losgekoppeld of verwijderd.
Patroonbewerken
<!-- Account_PreDelete: kosten loskoppelen voor verwijdering -->
<Execution>
<UserContent Name="Pre" />
<Transaction IncludeTrashedObjects="True">
<!-- Haal alle gerelateerde kosten op -->
<GetList Name="Get costs" Type="Cost" OutputProperty="${Costs}"
OrderBy="Id" OrderDirection="Ascending">
<Filters>
<PropertyFilter PropertyName="Account" Operator="="
PropertyValue="${Account}" />
</Filters>
</GetList>
<!-- Koppel kosten los van het account -->
<ForEach Name="Ontkoppel kosten" In="${Costs}" As="Cost">
<Assign Name="Verwijder koppeling" Property="${Cost.Account}" Value="Empty" />
</ForEach>
</Transaction>
<UserContent Name="Post" />
</Execution>
Consultant tip: Gebruik
IncludeTrashedObjects="True"in de Transaction om ook verwijderde (trashed) records mee te nemen. Anders worden referenties naar trashed objecten gemist.
Settings-gebaseerde logicabewerken
Scenariobewerken
Workflow-gedrag laten afhangen van applicatie-instellingen (UltimoSettings of workflow Settings).
Patroon: UltimoSettings controlerenbewerken
<When Name="StockLevel per site actief"
Condition="#{UltimoSettings.StockLevelPerSite} == True">
<!-- Site-specifieke logica -->
<WorkflowCall Name="Per site" WorkflowName="ArticleSite_AddWareHouse">
<Parameter Name="ArticleSite" Direction="In" Value="${ArticleSite}" />
</WorkflowCall>
</When>
Patroon: Workflow Settings gebruikenbewerken
<Settings>
<SettingsGroup Name="WorkOrder">
<Setting Name="CreateActiveJobOnApproval" Type="Boolean" Value="False" />
<Setting Name="CreateJobOnApproval" Type="Boolean" Value="True" />
</SettingsGroup>
</Settings>
<Execution>
<Choose Name="Welke job-creatie methode?">
<When Name="Active job"
Condition="#{Settings.WorkOrder.CreateActiveJobOnApproval} == True">
<!-- Maak job direct actief -->
</When>
<When Name="Created job"
Condition="#{Settings.WorkOrder.CreateJobOnApproval} == True">
<!-- Maak job met status Created -->
</When>
</Choose>
</Execution>
InOut parameters met WorkflowCallbewerken
Scenariobewerken
Data doorsturen naar een sub-workflow en het gewijzigde resultaat terugkrijgen.
Patroonbewerken
<ForEach Name="Loop items" In="${AbcDeterminationList}" As="AbcDetermination"
Condition="${AbcDetermination.Processed} == False && ${Continue} == True">
<WorkflowCall Name="Update item" WorkflowName="AbcDetermination_UpdateArticleSite">
<!-- In: read-only parameters -->
<Parameter Name="AbcCode" Direction="In" Value="${AbcCode}" />
<!-- InOut: worden aangepast door de sub-workflow en teruggegeven -->
<Parameter Name="AbcDetermination" Direction="InOut"
Value="${AbcDetermination}" OutputProperty="${AbcDetermination}" />
<Parameter Name="Continue" Direction="InOut"
Value="${Continue}" OutputProperty="${Continue}" />
<Parameter Name="TotalPercentage" Direction="InOut"
Value="${TotalPercentage}" OutputProperty="${TotalPercentage}" />
<Parameter Name="TotalPercentageABC" Direction="InOut"
Value="${TotalPercentageABC}" OutputProperty="${TotalPercentageABC}" />
</WorkflowCall>
</ForEach>
Consultant tip: Bij InOut parameters moet je zowel
Value(invoer) alsOutputProperty(uitvoer) specificeren. De sub-workflow kan de waarde wijzigen en de gewijzigde waarde wordt teruggeschreven naar de OutputProperty.
NotFilter met geneste filtersbewerken
Scenariobewerken
Records ophalen die NIET voldoen aan een bepaalde conditie, gecombineerd met andere filters.
Patroonbewerken
<GetList Name="Filter andere ProgressStatusFlows" Type="ProgressStatusFlow"
OutputProperty="${ProgressStatusFlows}" OrderBy="Id" OrderDirection="Ascending">
<Filters>
<CombinedFilter FilterOperator="And">
<PropertyFilter PropertyName="Id.ProgressStatus" Operator="="
PropertyValue="${DefaultAvailable.Id.ProgressStatus}" />
<!-- Sluit het huidige record uit -->
<NotFilter>
<PropertyFilter PropertyName="Id.ProgressStatusNextAvailable"
Operator="="
PropertyValue="${DefaultAvailable.Id.ProgressStatusNextAvailable}" />
</NotFilter>
</CombinedFilter>
</Filters>
</GetList>
Complexe dialoog met SelectionList en dynamische filterbewerken
Scenariobewerken
Een dialoog tonen waarbij de tweede keuzelijst afhankelijk is van de eerste selectie.
Patroonbewerken
<Dialog Name="SelecteerBron" TitleCode="SELECTBUILDINGPART">
<Container>
<SelectionList Name="Building" OutputProperty="${CollectBuilding}"
ColumnName="BldId" ViewfieldConfiguration="Default"
Required="True" LabelCode="BUILDING"
SqlWhereClause="BldContext=1" />
<!-- Tweede lijst filtert op de eerste selectie -->
<SelectionList Name="BuildingPart" OutputProperty="${CollectBuildingPart}"
ColumnName="BldpId" ViewfieldConfiguration="Default"
Required="True" LabelCode="BUILDINGPART"
SqlWhereClause="BldpBldId = '${CollectBuilding.Id?}' AND BldpId != '${BuildingPart.Id.Id}'" />
</Container>
</Dialog>
Consultant tip: De syntax
${CollectBuilding.Id?}met het vraagteken voorkomt een fout als de waarde nog leeg is. De SqlWhereClause werkt direct op databasekolommen (niet op property-namen).
Trash en herstel van gerelateerde objectenbewerken
Scenariobewerken
Bij het naar de prullenbak verplaatsen van een parent-object moeten gerelateerde objecten ook naar de prullenbak, en bij het kopieen van onderdelen moeten bestaande objecten eerst worden opgeruimd.
Patroonbewerken
<!-- Vraag of bestaande records verwijderd mogen worden -->
<When Name="Records bestaan" Condition="${SpaceMaintenances} != Empty">
<Question Name="Verwijderen?" Type="YesNoCancel" MessageCode="1106"
Default="No" OutputProperty="${DeleteAnswer}" />
<Choose Name="Keuze">
<When Name="Ja" Condition="${DeleteAnswer} == Yes">
<ForEach Name="Verwijder" In="${SpaceMaintenances}" As="SM">
<Transaction>
<!-- Valideer eerst of trash toegestaan is -->
<GetCount Name="Check afhankelijkheden" Type="YearPlanScenarioLine"
OutputProperty="${LineCount}">
<Filters>
<PropertyFilter PropertyName="SpaceMaintenanceActivity.Id.SpaceMaintenance"
Operator="=" PropertyValue="${SM}" />
</Filters>
</GetCount>
</Transaction>
<Validation Name="Geen afhankelijkheden"
Condition="${LineCount} == 0" MessageCode="1006" />
</ForEach>
</When>
<When Name="Annuleer" Condition="${DeleteAnswer} == Cancel">
<Stop Name="Stop" Mode="Abort" />
</When>
</Choose>
</When>
<!-- Verwijder en kopieer -->
<Transaction>
<When Name="Verwijder na bevestiging" Condition="${DeleteAnswer} == Yes">
<ForEach Name="Trash" In="${SpaceMaintenances}" As="SM">
<TrashObject Name="Trash" DomainObject="${SM}" />
</ForEach>
</When>
<!-- Kopieer van bron -->
<WorkflowCall Name="Kopieer" WorkflowName="SpaceMaintenance_CopyList">
<Parameter Name="DestinationBuildingPart" Direction="In" Value="${BuildingPart}" />
<Parameter Name="SpaceMaintenanceList" Direction="In" Value="${SourceList}" />
</WorkflowCall>
</Transaction>
Echte standaard Ultimo workflow-patronenbewerken
De onderstaande patronen zijn afgeleid uit de daadwerkelijke standaard Ultimo workflows (workflows.xml). Ze tonen hoe Ultimo R&D de belangrijkste bedrijfslogica implementeert.
Job_PreApprove: autorisatie en materiaalreserveringbewerken
Trigger: Pre-statuswijziging naar Approved Wat doet het: Controleert of de medewerker bevoegd is om de Job goed te keuren (budget check), reserveert materialen, en voert de standaard PreApprove command uit. Als de Job nog in status Requested staat, wordt deze eerst impliciet naar Created gezet.
<Workflow Name="Job_PreApprove" Version="2025.07.28" WorkflowType="ChangeStatus"
AllowUserInteraction="False" xmlns="urn:Ultimo.Framework.Workflow-mapping">
<Security EditLevel="10" ViewLevel="20" UserContentLevel="30" />
<Properties>
<Property Name="Job" Type="Job" Accessor="Root" Direction="In" />
<Property Name="CurrentEmployee" Type="Employee" Accessor="Internal" Default="#{User.EmployeeId}" />
<Property Name="JobMaterials" Type="List[JobMaterial]" Accessor="Internal" />
<Property Name="TotalCalculated" Type="CompanyMoney" Accessor="Internal" />
</Properties>
<Execution>
<UserContent Name="Pre" />
<Transaction>
<!-- Impliciete statuswijziging: Requested -> Created -->
<When Name="Jobstatus = Requested" Condition="${Job.Status} == JobStatus.Requested">
<Assign Name="Set implicit" Property="#{Settings.ProgressStatusHistory.IsImplicitProgressStatusChange}" Value="True" />
<ChangeStatus Name="Open job" DomainObject="${Job}" NewStatus="JobStatus.Created" />
<Assign Name="Clear implicit" Property="#{Settings.ProgressStatusHistory.IsImplicitProgressStatusChange}" Value="False" />
</When>
<!-- Autorisatiecheck: mag deze medewerker dit bedrag goedkeuren? -->
<When Name="When AuthorizeApproveJob" Condition="#{UltimoSettings.AuthorizeApproveJob} == True">
<Validation Name="Check Employee" Condition="${CurrentEmployee} != Empty" MessageCode="0731" />
<GetMax Name="Get TotalCalculated" Type="Job" OutputProperty="${TotalCalculated}" PropertyName="TotalCalculated">
<Filters>
<PropertyFilter PropertyName="Id" Operator="=" PropertyValue="${Job}" />
</Filters>
</GetMax>
<Validation Name="Validate TotalCalculated"
Condition="#createcompanymoney(${TotalCalculated.Amount}, 'Job.TotalCalculated') <= ${CurrentEmployee.Tariff4}"
MessageCode="0056" />
</When>
<!-- Materiaalreservering -->
<GetList Name="Get JobMaterials" Type="JobMaterial" OutputProperty="${JobMaterials}">
<Filters>
<CombinedFilter FilterOperator="And">
<PropertyFilter PropertyName="Id.Job" Operator="=" PropertyValue="${Job}" />
<PropertyFilter PropertyName="Status" Operator="=" PropertyValue="JobMaterialStatus.Created" />
</CombinedFilter>
</Filters>
</GetList>
<ForEach Name="ForEach JobMaterial" In="${JobMaterials}" As="JobMaterial">
<Validation Name="Check for negative PlannedQuantity"
Condition="${JobMaterial.PlannedQuantity} >= 0" MessageCode="2988" />
<WorkflowCall Name="Reserve Article" WorkflowName="JobMaterial_ReserveArticle">
<Parameter Name="JobMaterial" Direction="In" Value="${JobMaterial}" />
</WorkflowCall>
</ForEach>
<Command Name="Job_PreApprove" CommandName="Job_PreApprove">
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Command>
</Transaction>
<UserContent Name="Post" />
</Execution>
</Workflow>
Consultant tip: De autorisatiecheck vergelijkt het berekende bedrag van de Job met Tariff4 van de medewerker. Dit is configureerbaar via
UltimoSettings.AuthorizeApproveJob. In de UserContent Pre kun je extra validaties toevoegen (bijv. verplichte velden).
Job_PostFinish: meest complexe post-workflowbewerken
Trigger: Post-statuswijziging naar Finished Wat doet het: De meest uitgebreide standaard workflow. Werkt meetpuntwaarden bij (IndicatorValue), update het gekoppelde PmWorkOrder, registreert de laatste onderhoudsdatum op Equipment, sluit JobTasks, zet de voortgangsstatus, verwerkt conditiegebreken, sluit de multijob indien nodig, en verstuurt een email naar de melder.
Key XML-structuur (vereenvoudigd):
<Workflow Name="Job_PostFinish" WorkflowType="ChangeStatus" AllowUserInteraction="False">
<Settings>
<SettingsGroup Name="Job">
<Setting Name="JobCreateMethod" Type="JobCreateMethod" Value="MaterialMerge" />
<Setting Name="SkipSendingEmailWhenMovingJobToAnotherBackOffice" Type="Boolean" Value="False" />
</SettingsGroup>
</Settings>
<Execution>
<UserContent Name="Pre" />
<Transaction>
<!-- 1. Planning bijwerken -->
<WorkflowCall Name="Job_PostStatusChange_ShowRealityInPlanning" ... />
<!-- 2. Meetpuntwaarden bijwerken (als IndicatorValue > 0) -->
<When Condition="${Job.IndicatorValue} > 0 && ${Job.Equipment} != Empty">
<Command Name="Get Default MeasurementPoint" CommandName="Equipment_GetDefaultMeasurementPoint" />
<Command Name="Add Value" CommandName="EquipmentMeasurementPoint_AddValue" />
</When>
<!-- 3. Server-side command voor standaard PostFinish logica -->
<Command Name="Job_PostFinish" CommandName="Job_PostFinish" />
<!-- 4. PmWorkOrder bijwerken -->
<WorkflowCall Name="Job_UpdateAssociatedPmWorkOrder" ... />
<!-- 5. Laatste onderhoudsdatum op Equipment bijwerken -->
<When Condition="${Job.Equipment} != Empty">
<Command Name="Equipment_UpdateLastMaintenance" CommandName="Equipment_UpdateLastMaintenance" />
</When>
<!-- 6. JobTasks sluiten -->
<WorkflowCall Name="Job_CheckAndCloseJobTasksIfNeeded" ... />
<!-- 7. Voortgangsstatus zetten -->
<WorkflowCall Name="Job_SetDefaultOrRequestedProgressStatus" ... />
<!-- 8. Conditiegebreken bijwerken -->
<WorkflowCall Name="Job_UpdateConditionFlawPresents" ... />
<!-- 9. Multijob sluiten indien nodig -->
<WorkflowCall Name="Job_CheckAndCloseMultijobIfNeeded" ... />
<!-- 10. Extra UserContent hook specifiek voor Finished -->
<UserContent Name="JobFinished" />
<!-- 11. Email naar melder -->
<When Condition="#{UltimoSettings.JobSendEmailOnFinish} == True">
<Email Name="SendEmailToReportEmployee" From="#{UltimoSettings.EmailSender}"
To="${Job.ReportForeignKeyEmployee}" RelatedSubjects="${Job}"
EmailTemplateCode="00000010001" />
</When>
<!-- 12. Equipment/ProcessFunction next-PM velden bijwerken -->
<When Condition="${Job.Equipment} != Empty">
<WorkflowCall Name="UpdateEquipmentNextPMFields" WorkflowName="Equipment_UpdateEquipmentNextPMFields" />
</When>
</Transaction>
<UserContent Name="Post" />
</Execution>
</Workflow>
Consultant tip: Let op de extra
<UserContent Name="JobFinished" />-- dit is een derde hook naast Pre en Post, specifiek voor de Job_PostFinish workflow. Gebruik deze voor logica die na de standaard afmeldverwerking maar voor de email moet draaien.
Job_PreActivate: LOTO en vergunningscontrolebewerken
Trigger: Pre-statuswijziging naar Active Wat doet het: Controleert LOTO-verzoeken (Lock Out Tag Out), doorloopt impliciete statuswijzigingen (Requested->Created->Approved als nodig), verwijdert release-condities, valideert werkvergunningen, en controleert de multijob-parentstatus.
Key XML-structuur (vereenvoudigd):
<Transaction>
<!-- LOTO checks -->
<WorkflowCall Name="Test for attached LOTO-requests"
WorkflowName="Job_CheckAttachedLockoutTagoutRequests" />
<!-- Impliciete statusovergangen -->
<When Condition="${Job.Status} == JobStatus.Requested">
<ChangeStatus Name="Open job" DomainObject="${Job}" NewStatus="JobStatus.Created" />
</When>
<When Condition="${Job.Status} == JobStatus.Created">
<ChangeStatus Name="Approve job" DomainObject="${Job}" NewStatus="JobStatus.Approved" />
</When>
<!-- Release conditie wissen -->
<When Condition="${Job.ReleaseCondition} != Empty">
<Assign Property="${Job.ReleaseCondition}" Value="Empty" />
<Assign Property="${Job.ReleaseDate}" Value="Empty" />
<Assign Property="${Job.ReleaseTillDate}" Value="Empty" />
</When>
<!-- Werkvergunning validatie -->
<When Condition="...EnablePermit...">
<GetCount Name="GetPermit" Type="Permit" OutputProperty="${PermitsCount}">
<Filters>
<PropertyFilter PropertyName="Job" Operator="=" PropertyValue="${Job}" />
<InFilter PropertyName="Status"
Values="PermitStatus.Created,PermitStatus.Prepared,PermitStatus.Active,PermitStatus.ToProlong" />
</Filters>
</GetCount>
<Validation Name="Check permits count" Condition="${PermitsCount} > 0" MessageCode="1694" />
</When>
<!-- Multijob parent-status check -->
<When Condition="${Job.MultijobTemplateLine} != Empty">
<Validation Name="CheckParentStatus"
Condition="${Job.Multijob.Status} != JobStatus.Finished && ${Job.Multijob.Status} != JobStatus.Closed"
MessageCode="1290" />
</When>
</Transaction>
Consultant tip: Het patroon van impliciete statuswijzigingen (Requested -> Created -> Approved -> Active) is een belangrijk Ultimo-concept. Met
IsImplicitProgressStatusChange = Trueworden de tussenliggende voortgangsstatussen niet gelogd.
PmWorkOrder_PostApprove: PM-model goedkeuringbewerken
Trigger: Post-statuswijziging naar Approved Wat doet het: Keurt alle PmJobs binnen het PM-model goed, werkt de next-PM velden bij op Equipment/ProcessFunction, zet de AutoPM NextRunDate als deze leeg is, en verwerkt periodieke activiteiten door het model eventueel te heropenen.
Key XML-structuur (vereenvoudigd):
<Execution>
<UserContent Name="Pre" />
<!-- Reset ReopenedAutomatically flag -->
<Assign Property="${PmWorkOrder.ReopenedAutomatically}" Value="False" />
<!-- Keur alle open PmJobs goed -->
<When Condition="#{Settings.PmWorkOrder.ApprovePmJobs} == True || ${PmWorkOrder.GroupPmWorkOrder} != Empty">
<GetList Name="Get open PmJobs" Type="PmJob" OutputProperty="${OpenPmJobs}">
<Filters>
<PropertyFilter PropertyName="Id.PmWorkOrder" Operator="=" PropertyValue="${PmWorkOrder}" />
</Filters>
</GetList>
<ForEach Name="Foreach open PmJob" In="${OpenPmJobs}" As="PmJob">
<Choose>
<When Condition="${PmJob.Status} == PmJobStatus.Approved">
<!-- Alleen beschrijving zetten voor courseroute -->
</When>
<Otherwise>
<ChangeStatus Name="Approve PmJob" DomainObject="${PmJob}" NewStatus="PmJobStatus.Approved" />
</Otherwise>
</Choose>
</ForEach>
</When>
<!-- Equipment next-PM velden bijwerken -->
<When Condition="${PmWorkOrder.ParentPeriodicActivity} == False">
<Command Name="Update next PM" CommandName="PmWorkOrder_UpdateEquipmentNextPreventiveMaintenance" />
</When>
<!-- Periodieke activiteit: heropenen -->
<When Condition="${PmWorkOrder.ParentPeriodicActivity} == True">
<When Condition="#{Settings.PmWorkOrder.Reopen} == True">
<ChangeStatus Name="Reopen PmWorkOrder" DomainObject="${PmWorkOrder}" NewStatus="PmWorkOrderStatus.Created" />
</When>
</When>
<UserContent Name="Post" />
</Execution>
Consultant tip: De setting
ApprovePmJobsbepaalt of PmJobs automatisch worden goedgekeurd bij goedkeuring van het PM-model. Bij een Group PM (ParentPeriodicActivity=True) wordt het model na goedkeuring automatisch heropend.
Purchase_PostApprove: inkoopregels doorvoerenbewerken
Trigger: Post-statuswijziging naar Approved Wat doet het: Zet de status van alle PurchaseLines naar Approved. Dit is een typisch cascade-patroon waarbij de parent-status wordt doorgevoerd naar child-records.
<Workflow Name="Purchase_PostApprove" WorkflowType="ChangeStatus" AllowUserInteraction="False">
<Properties>
<Property Name="Purchase" Type="Purchase" Accessor="Root" Direction="In" />
<Property Name="PurchaseLines" Type="List[PurchaseLine]" Accessor="Internal" />
</Properties>
<Execution>
<UserContent Name="Pre" />
<Transaction>
<GetList Name="Get PurchaseLines" Type="PurchaseLine" OutputProperty="${PurchaseLines}">
<Filters>
<PropertyFilter PropertyName="Id.Purchase" Operator="=" PropertyValue="${Purchase}" />
</Filters>
</GetList>
<ForEach Name="For each PurchaseLine" In="${PurchaseLines}" As="PurchaseLine">
<ChangeStatus Name="Set status to Approved" DomainObject="${PurchaseLine}"
NewStatus="PurchaseLineStatus.Approved" />
</ForEach>
</Transaction>
<UserContent Name="Post" />
</Execution>
</Workflow>
Consultant tip: Dit cascade-patroon (parent-status -> child-status) is een veelvoorkomend Ultimo-patroon dat je terugziet bij Purchase, PurchaseRequest, ServiceContract en andere entiteiten met child-regels.
PurchaseRequest_PostApprove: email naar aanvragerbewerken
Trigger: Post-statuswijziging naar Approved
Wat doet het: Verstuurt een email naar de aanvrager via de herbruikbare PurchaseRequest_Mail workflow.
<Workflow Name="PurchaseRequest_PostApprove" WorkflowType="ChangeStatus" AllowUserInteraction="False">
<Properties>
<Property Name="PurchaseRequest" Type="PurchaseRequest" Accessor="Root" Direction="In" />
</Properties>
<Execution>
<UserContent Name="Pre" />
<WorkflowCall Name="PurchaseRequest_Mail" WorkflowName="PurchaseRequest_Mail">
<Parameter Name="PurchaseRequest" Direction="In" Value="${PurchaseRequest}" />
</WorkflowCall>
<UserContent Name="Post" />
</Execution>
</Workflow>
Consultant tip: Let op dat de email-logica is afgesplitst in een herbruikbare Standard workflow (
PurchaseRequest_Mail). Dit is een best practice: hergebruik logica via WorkflowCall in plaats van duplicatie.
Equipment_PreActive: PM heropenen bij reactiveringbewerken
Trigger: Pre-statuswijziging naar Active Wat doet het: Wanneer een Equipment vanuit de ToDelete status weer actief wordt gemaakt, worden bijbehorende PmJobs/PmWorkOrders heropend en InspectionLines ge-un-trasht. Houdt rekening met de ProgressStatus feature toggle.
<Workflow Name="Equipment_PreActive" WorkflowType="ChangeStatus" AllowUserInteraction="False">
<Properties>
<Property Name="Equipment" Type="Equipment" Accessor="Root" Direction="In" />
<Property Name="EquipmentIsFeatureToggleProgressStatusEnabled" Type="Boolean" Accessor="Internal" />
</Properties>
<Execution>
<UserContent Name="Pre" />
<When Condition="${Equipment.Status} == EquipmentStatus.ToDelete">
<Transaction>
<Command Name="Equipment_IsFeatureToggleProgressStatusEnabled"
CommandName="Equipment_IsFeatureToggleProgressStatusEnabled">
<Parameter Name="Equipment" Direction="In" Value="${Equipment}" />
<Parameter Name="FeatureToggleIsEnabled" Direction="Out"
OutputProperty="${EquipmentIsFeatureToggleProgressStatusEnabled}" />
</Command>
<When Condition="${EquipmentIsFeatureToggleProgressStatusEnabled} == False">
<WorkflowCall Name="Approve PM and Untrash inspection lines"
WorkflowName="Equipment_ApprovePMAndUnTrashInspectionLines">
<Parameter Name="Equipment" Direction="In" Value="${Equipment}" />
</WorkflowCall>
</When>
</Transaction>
</When>
<UserContent Name="Post" />
</Execution>
</Workflow>
Consultant tip: Dit patroon van conditioneel heropenen van PM is belangrijk bij equipment-lifecycle management. De feature toggle controle toont hoe Ultimo geleidelijk nieuwe functionaliteit uitrolt.
Dialog met vraag-herhaling patroonbewerken
Scenariobewerken
Een gebruiker moet meerdere vragen beantwoorden. Als het antwoord "Nee" is, wordt een invoerdialoog getoond en daarna de vraag herhaald. Dit patroon is typisch voor het begeleiden van gebruikers bij het invullen of corrigeren van gegevens stap voor stap.
Wanneerbewerken
- ActionField workflows waarbij gebruikers gegevens moeten bevestigen of aanpassen
- Wizardachtige invoerflows waarbij onjuiste waarden direct gecorrigeerd worden
Patroonbewerken
1. Question: "Is de beschrijving correct?" → ${descriptionQuestionResult}
2. When: ${descriptionQuestionResult} == No
3. Dialog: Beschrijving opnieuw invoeren
4. Question: "Is de medewerker correct?" → ${employeeQuestionResult}
5. When: ${employeeQuestionResult} == No
6. Dialog: Medewerker opnieuw selecteren
<Question Name="Is beschrijving correct?" Type="YesNo" MessageCode="9020"
OutputProperty="${descriptionQuestionResult}">
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Question>
<When Name="Beschrijving niet correct" Condition="${descriptionQuestionResult} == No">
<Dialog Name="Beschrijving aanpassen" TitleCode="9021">
<Container Border="True">
<Text Name="NewDescription" OutputProperty="${Job.Description}"
LabelCode="9022" Width="800" Required="True" />
</Container>
</Dialog>
</When>
<Question Name="Is medewerker correct?" Type="YesNo" MessageCode="9023"
OutputProperty="${employeeQuestionResult}">
<Parameter Name="Job" Direction="In" Value="${Job}" />
</Question>
<When Name="Medewerker niet correct" Condition="${employeeQuestionResult} == No">
<Dialog Name="Medewerker selecteren" TitleCode="9024">
<Container Border="True">
<SelectionList Name="Employee" OutputProperty="${Job.Employee}"
ColumnName="EmpId" Required="True" LabelCode="9025"
SqlWhereClause="EmpActive=1" />
</Container>
</Dialog>
</When>
Dialog configuratie detailsbewerken
| Element | Eigenschap | Toelichting |
|---|---|---|
Container |
verplichte wrapper | Alle invoervelden moeten in een Container zitten |
Text |
Width in pixels |
Bijv. Width="800" voor een breed tekstveld |
Text |
LabelCode → message |
Verwijst naar een Message-code voor het label |
Text |
OutputProperty → variabele |
Schrijft de invoer direct naar een property of variabele |
SelectionList |
ColumnName |
Primaire sleutel van de tabel (bijv. EmpId) |
SelectionList |
SqlWhereClause |
Filter op kolomnamen (niet property-namen!), bijv. EmpActive=1 |
SelectionList |
Required="True" |
Verplicht selectieveld |
Consultant tip: Gebruik
Ctrl+dragom blokken te kopiëren in de UCT workflow designer. Dit spaart veel tijd bij herhalende patronen zoals vraag-herhaling flows.
Choose patroon voor prioriteit toewijzingbewerken
Scenariobewerken
Op basis van het type job wordt automatisch een prioriteit toegewezen. Dit is een typisch configuratie-automatiseringspatroon waarbij beleidsregels worden vertaald naar workflow-logica.
Wanneerbewerken
- Pre-save of Post-insert workflows voor Job
- ActionField bij aanmaken of wijzigen van een Job
- Overal waar een categorisering automatisch een waarde moet sturen
Patroonbewerken
<Choose Name="Bepaal prioriteit op basis van jobtype">
<When Name="Correctief" Condition="${Job.JobType.JobTypeCategory} == JobTypeCategory.C">
<Assign Name="Prioriteit Medium" Property="${Job.Priority}" Value="PriorityId.M" />
</When>
<When Name="Preventief" Condition="${Job.JobType.JobTypeCategory} == JobTypeCategory.P">
<Assign Name="Prioriteit Hoog" Property="${Job.Priority}" Value="PriorityId.H" />
</When>
<When Name="Overig" Condition="${Job.JobType.JobTypeCategory} == JobTypeCategory.O">
<Assign Name="Prioriteit Laag" Property="${Job.Priority}" Value="PriorityId.L" />
</When>
</Choose>
Referentiewaardenbewerken
Job Type Category:
| Waarde | Betekenis |
|---|---|
C |
Corrective (Correctief) |
P |
Preventive (Preventief) |
O |
Other (Overig) |
Priority ID:
| Waarde | Betekenis |
|---|---|
H |
High (Hoog) |
M |
Medium |
L |
Low (Laag) |
Consultant tip: Gebruik altijd
Choosemet explicieteWhen-condities per categorie in plaats van genesteWhen/Otherwiseblokken. Dit maakt de logica leesbaar en makkelijker uitbreidbaar als er nieuwe categorieën bijkomen.
WorkflowCall — juiste sub-workflow vindenbewerken
Scenariobewerken
Je wilt een sub-workflow aanroepen die specifieke logica uitvoert (bijv. een berekening of een statuswijziging), maar je weet niet welke workflow dat is. Dit is een veelgestelde vraag bij consultants die de standaard Ultimo-workflows onderzoeken.
Aanpak: Technical Screen Informationbewerken
- Ga naar het veld of de knop op het scherm waar de actie vandaan komt
- Gebruik Technical Screen Information (via het vraagteken of developer tools) op het veld — dit toont de gekoppelde workflow, bijv.
ActionField1602 - Open die workflow in UCT
- Zoek daarin de
WorkflowCallinstructie die doet wat je nodig hebt - Roep die sub-workflow aan in jouw eigen workflow, niet de ActionField-workflow zelf
<!-- Gevonden via Technical Screen Information: ActionField1602 roept dit aan -->
<WorkflowCall Name="Voer specifieke actie uit" WorkflowName="Job_SomeSpecificSubWorkflow">
<Parameter Name="Job" Direction="In" Value="${Job}" />
</WorkflowCall>
Consultant tip: ActionField workflows zijn entrypoints — ze valideren, roepen sub-workflows aan, en tonen berichten. Roep nooit een ActionField workflow zelf aan vanuit jouw workflow; gebruik altijd de sub-workflow die de daadwerkelijke logica bevat.
View action — scherm openen na Insertbewerken
Scenariobewerken
Na het aanmaken van een nieuw record (bijv. een werkorder) wordt de gebruiker automatisch naar het nieuwe record geleid op een specifiek scherm.
Wanneerbewerken
- ActionField workflows die een nieuw record aanmaken
- Wizards waarbij de gebruiker na aanmaken direct door wil werken
Patroonbewerken
<View Name="Bekijk nieuw werkorder" 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>
| Parameter | Waarde | Toelichting |
|---|---|---|
DomainObject |
${newJob} |
Het nieuw aangemaakte object |
ScreenName |
bijv. job03 |
Schermcode (Prepare Jobs) — raadpleeg screens voor codes |
CreateNewRecord |
False |
Bestaand record tonen, geen nieuw scherm |
OpenNewWindow |
True |
Open in een nieuw venster/tab |
FocusEditableField |
True |
Cursor in het eerste bewerkbare veld |
Consultant tip: Gebruik
ViewName="DataEntryScreen"voor data-entry formulieren. DeScreenNameis de technische schermcode die je vindt via Technical Screen Information of in de UCT schermendefinities.
Scheduled email met conditionele tekstbewerken
Scenariobewerken
Elke nacht wordt een overzichtsmail verstuurd met jobs die de afgelopen 2 dagen zijn afgemeld. Als er geen jobs zijn, bevat de mail een andere tekst (of wordt er geen mail verstuurd).
Wanneerbewerken
- Scheduled workflows voor rapportage- of notificatiedoeleinden
- Emails waarbij de inhoud afhankelijk is van het aantal gevonden records
Patroonbewerken
<Workflow Name="ScheduledTask_DailyJobReport" WorkflowType="Standard"
AllowUserInteraction="False" xmlns="urn:Ultimo.Framework.Workflow-mapping">
<Properties>
<Property Name="to" Type="String" Accessor="Root" Direction="In" />
<Property Name="jobList" Type="List[Job]" Accessor="Internal" />
<Property Name="mailBody" Type="String" Accessor="Internal" />
</Properties>
<Execution>
<!-- Haal afgemelde jobs op van de afgelopen 2 dagen -->
<Transaction>
<GetList Name="Haal afgemelde jobs op" Type="Job" OutputProperty="${jobList}">
<Filters>
<CombinedFilter FilterOperator="And">
<PropertyFilter PropertyName="Status" Operator="="
PropertyValue="JobStatus.Finished" />
<DateFilter PropertyName="FinishedDate" Operator=">="
PropertyValue="=#startofday(#adddays(#{Environment.CurrentDateTime}, -2))" />
<DateFilter PropertyName="FinishedDate" Operator="<"
PropertyValue="=#startofday(#{Environment.CurrentDateTime})" />
</CombinedFilter>
</Filters>
</GetList>
</Transaction>
<!-- Genereer mailtekst via TextTemplate met conditionele inhoud -->
<TextTemplate Name="Genereer mailtekst" TextTemplateCode="9030"
OutputProperty="${mailBody}">
<Parameter Name="jobList" Direction="In" Value="${jobList}" />
</TextTemplate>
<!-- Verstuur de mail -->
<Email Name="Verstuur dagrapport"
From="#{Settings.EmailSender}"
To="${to}"
RelatedSubjects="${jobList}"
EmailTemplateCode="9031">
<Parameter Name="jobList" Direction="In" Value="${jobList}" />
<Parameter Name="mailBody" Direction="In" Value="${mailBody}" />
</Email>
</Execution>
</Workflow>
TextTemplate met if/else op lijstgroottebewerken
#if(${jobList.Count} > 0)
Er zijn ${jobList.Count} jobs afgemeld in de afgelopen 2 dagen:
#foreach($job in ${jobList})
- ${job.Id}: ${job.Description} (${job.Equipment.Description})
#end
#elseif(${jobList.Count} == 0)
Er zijn geen jobs afgemeld in de afgelopen 2 dagen.
#end
Configuratie-aandachtspuntenbewerken
tois een verplichte Root-parameter die wordt meegegeven vanuit de Scheduler-instellingFromverwijst altijd naar#{Settings.EmailSender}(nooit hardcoden)AllowUserInteraction = Falseis verplicht voor scheduled workflows — er is immers geen gebruiker- Datumbereik: gebruik
startofday(addDays(now(), -2))t/mstartofday(now())voor volledige dagen
Consultant tip: Gebruik
${jobList.Count}in de TextTemplate om enkelvoud/meervoud te onderscheiden:if(${jobList.Count} == 1, "1 job", "${jobList.Count} jobs"). Sla deto-parameter in als RootDirection="In"zodat de Scheduler die kan invullen.
Messages met parameters en expressiesbewerken
Scenariobewerken
Workflow-berichten tonen dynamische teksten op basis van variabelen: aantallen, beschrijvingen van entiteiten, of enkelvoud/meervoud varianten.
Wanneerbewerken
- Bevestigingsberichten na een actie (
Messageinstructie) - Dialog-titels en labels (
LabelCode) - TextTemplate-inhoud voor emails
Enkelvoud/meervoud met if-expressiebewerken
<Message Name="Resultaat" MessageCode="9040">
<Parameter Name="count" Direction="In" Value="${items.Count}" />
<Parameter Name="summary" Direction="In"
Value="=if(${items.Count} == 1, '1 equipment gevonden', '${items.Count} equipments gevonden')" />
</Message>
In de Message-definitie (UCT > Messages):
${summary}
Entiteitbeschrijving tonenbewerken
Gebruik altijd .Description om de leesbare naam van een entiteit te tonen. Zonder .Description zie je alleen het ID.
<!-- Fout: toont alleen het ID (bijv. "EMP001") -->
<Message Name="Medewerker" MessageCode="9041">
<Parameter Name="employee" Direction="In" Value="${employee}" />
</Message>
<!-- Goed: toont de naam (bijv. "Jan de Vries") -->
<Message Name="Medewerker" MessageCode="9041">
<Parameter Name="employeeName" Direction="In" Value="${employee.Description}" />
</Message>
Message typesbewerken
| Type | Gebruik |
|---|---|
Label |
Titels van dialogen, labels in formulieren |
Ultimo |
Berichten in workflow-notificaties en Message instructies |
Email |
Onderwerp- en inhoudstemplates voor emails |
Consultant tip: Geef parameters in messages altijd een beschrijvende naam die overeenkomt met hoe ze in de message-template worden gebruikt (
${employeeName}in de template,employeeNameals parameternaam). Dit voorkomt verwarring bij meerdere gelijksoortige parameters.
Gerelateerde artikelenbewerken
- workflow-engine - Hoe de workflow engine werkt
- standaard-workflows - Overzicht van alle standaard workflows per entiteit
- workflow-instructies - Alle beschikbare instructies
- expressions - Expression functies
- workflow-debugger - Debugging tips
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.