Categorie: workflows Bijgewerkt: 2026-04-15 workflow patronen best practices xml voorbeelden

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

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 Command als je de volledige Ultimo initialisatielogica wilt doorlopen (context, standaardwaarden, etc.). Gebruik Insert als 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

Twee-stappen patroon (verplicht)bewerken

Email versturen in Ultimo werkt altijd in twee stappen binnen dezelfde Transaction:

  1. Email aanmaken als Concept (Concept="True" + OutputProperty)
  2. 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 &amp;&amp; ${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 &amp;&amp; ${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

Patroonbewerken

<!-- In UserContent Post van Job_PostApproved -->
<When Name="Medewerker heeft email" Condition="${Job.Employee} != Empty &amp;&amp; ${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

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} &lt;= 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} &lt; 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

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 Top en While om geheugen- en performance-problemen te voorkomen. Gebruik Flush of 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 &amp;&amp; #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 met ProcessObjects="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

Hoebewerken

  1. Open in UCT de betreffende workflow (bijv. Job_PostApproved)
  2. Klik op de UserContent Pre of UserContent Post sectie
  3. Voeg je instructies toe
  4. 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} &lt;= 2">
        <Assign Name="Deadline 2 dagen" Property="${Job.Deadline}"
            Value="=#adddays(#{Environment.CurrentDate}, 2)" />
    </When>
    <When Name="Normale prioriteit" Condition="${Job.Priority.SortSequence} &lt;= 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} &gt;= #{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} &lt;= #{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

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 FocusEditableField parameter 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 &amp;&amp; ${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) als OutputProperty (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') &lt;= ${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} &gt;= 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} &gt; 0 &amp;&amp; ${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} &gt; 0" MessageCode="1694" />
    </When>

    <!-- Multijob parent-status check -->
    <When Condition="${Job.MultijobTemplateLine} != Empty">
        <Validation Name="CheckParentStatus"
            Condition="${Job.Multijob.Status} != JobStatus.Finished &amp;&amp; ${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 = True worden 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 ApprovePmJobs bepaalt 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

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+drag om 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

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 Choose met expliciete When-condities per categorie in plaats van geneste When/Otherwise blokken. 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

  1. Ga naar het veld of de knop op het scherm waar de actie vandaan komt
  2. Gebruik Technical Screen Information (via het vraagteken of developer tools) op het veld — dit toont de gekoppelde workflow, bijv. ActionField1602
  3. Open die workflow in UCT
  4. Zoek daarin de WorkflowCall instructie die doet wat je nodig hebt
  5. 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

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. De ScreenName is 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

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

Consultant tip: Gebruik ${jobList.Count} in de TextTemplate om enkelvoud/meervoud te onderscheiden: if(${jobList.Count} == 1, "1 job", "${jobList.Count} jobs"). Sla de to-parameter in als Root Direction="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

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, employeeName als parameternaam). Dit voorkomt verwarring bij meerdere gelijksoortige parameters.


Gerelateerde artikelenbewerken

Brondatabewerken

Dit artikel beschrijft workflow-concepten en -patronen. Voor de feitelijke workflow-XML van een specifieke trigger of ActionField: