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 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:
1. UCT > Scheduler - Definieer het schema (cron-expressie of interval)
2. Koppel de workflow aan de scheduler-taak
3. AllowUserInteraction is altijd False (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.
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