Actions
In time cockpit's model you can define Actions. Actions can be used for workflows, creating invoices, etc. In general you can use actions whenever you want to build a certain behavior that is not built in time cockpit out of the box.
Structure of Actions
Actions are represented as instances of the ModelAction class. You can add an action to the model using the ModelActions property.
You can define conditions that have to be fulfilled in order to be able to execute an action. Every condition is represented by an instance of the Condition class or one of it's descendant classes. The most important condition is the ModelEntityTypeCondition. It can be used to specify that a certain action can only be used on instances of a certain entity type.
An action can have an optional parameter. The parameter is represented by an instance of the ModelActionParameterDefinition class or one of it's descendant classes. A parameter can be used to ask a user for additional data that is necessary to perform the corresponding action.
Last but not least every action must have a binding. The binding defines where the code for the action comes from. Currently time cockpit only supports actions written in Python. Therefore you have to use IronPythonBinding.
Create and Update Actions
The following example creates an action from a Python source file:
clr.AddReference("System")
from System.IO import File
model = Context.GetWritableModel()
changeTimesheetAction = ModelAction()
changeTimesheetAction.Name = "CreateInvoice"
changeTimesheetAction.Conditions.Add(ModelEntityTypeCondition({ "Name": "TimesheetCondition", "ModelEntityName": "Timesheet", "ModelEntity": model.Timesheet, "MinimumInputSetSize": 1 }))
changeTimesheetAction.Binding = IronPythonBinding()
changeTimesheetAction.Binding.Name = "IronPythonBinding"
changeTimesheetAction.Binding.SourceCode = File.ReadAllText("C:\Data\Documents\Action for creating invoices.py")
model.Actions.Add(changeTimesheetAction)
Context.SaveModel(model)
print "Done"
The following example updates an action from a Python source file. It adds a filter entity to the action which is shown before the action is executed.
clr.AddReference("System")
from System.IO import File
model = Context.GetWritableModel()
filterEntity = ModelEntity()
filterEntity.Name = "FilterEntityForCreateInvoice"
filterEntity.Properties.Add(TextProperty({ "Name": "InvoiceText", "MaxStorageSize": 200, "InvariantFriendlyName": "Rechnungstext" }))
filterEntity.Properties.Add(DateTimeProperty({ "Name": "InvoiceDate", "FractionalSecondsPrecision": 0, "IsNullable": True, "InvariantFriendlyName":"Rechnungsdatum" }))
filterEntity.Relations.Add(Relation({ "Name": "Customer", "InvariantFriendlyName": "Kunde", "Target": model.Customer }))
model.Actions.CreateInvoice.Parameter = PredefinedAnonymousParameter()
model.Actions.CreateInvoice.Parameter.ModelEntity = filterEntity
model.Actions.CreateInvoice.Binding.SourceCode = File.ReadAllText("C:\Data\Documents\Action for creating invoices.py")
Context.SaveModel(model)
print "Done"
Action Code
The following example shows the code for an action. Note that the action code must only consist of Python method definitions. The action receives one parameter of type ExecutionContext.
clr.AddReference("System")
clr.AddReference("PresentationFramework")
clr.AddReference("System.Core")
from System import DateTime
from System import Decimal
from System.Windows import MessageBox
from System.Linq import Enumerable
from TimeCockpit.Data import EntityObject
def createInvoice(actionContext):
# make sure that input set contains at least one object
if (Enumerable.Count[EntityObject](actionContext.InputSet) > 0):
# find next free invoice number
lastInvoiceNumber = 0
lastInvoice = actionContext.DataContext.SelectSingleWithParams( {
"Query": "From I In Invoice Where :Year(I.InvoiceDate) = @YearParam And :Month(I.InvoiceDate) = @MonthParam Order By I.InvoiceNumberMonthly Desc Select New With { I.InvoiceNumberMonthly }",
"@YearParam": Decimal(actionContext.Parameter.InvoiceDate.Year),
"@MonthParam": Decimal(actionContext.Parameter.InvoiceDate.Month) })
if (lastInvoice <> None):
lastInvoiceNumber = lastInvoice.InvoiceNumberMonthly
# create invoice
newInvoice = actionContext.DataContext.CreateInvoice()
newInvoice.InvoiceDate = actionContext.Parameter.InvoiceDate
newInvoice.InvoiceNumberMonthly = lastInvoiceNumber + 1
newInvoice.InvoiceText = actionContext.Parameter.InvoiceText
newInvoice.Customer = actionContext.Parameter.Customer
actionContext.DataContext.SaveObject(newInvoice)
Using the InputSet
Before an action is executed, time cockpit reloads the corresponding data rows before it passes them to the action via the InputSet
property. This is necessary to make sure that the entire rows including all dependent objects are in memory. A list could have been optimized to load only a subset of the columns of a model entity. If time cockpit would not reload the data rows before executing the action, the action would possibly get incomplete data.
Warning
Be aware that the input set does not guarantee a defined object order. Sorting may be required depending on the use-case.
For reloading the entities, time cockpit generates a TCQL statement with the following structure: From C In [ModelEntityName][IncludeClause] Where C.[ModelEntityName]Uuid In {[SelectedUuids]} Select C
.
The ModelEntity is determined by the list or form in which the user has called the action. If the user calls an action in the time sheet calendar, the underlying ModelEntity is always TimeSheet
.
The SelectedUuids are determined by the form, the selected items in the list, or the selected items in the time sheet calendar.
The IncludeClause
(more about include clauses in TCQL is determined by the following factors:
The action can contain a processing directive # IncludeClause that specifies which relations should be included (details see chapter IncludeClause Processing Directive).
If no IncludeClause directive is specified in the action, time cockpit looks for the default form of the affected ModelEntity. If the form has specified an
IncludeClause
, thisIncludeClause
is used. This does not work if an expression is specified for the default form of a ModelEntity. As multiple entities may be affected by an action, time cockpit cannot determine one default form if an expression is used.If there is no IncludeClause directive in the action and no default form can be determined, time cockpit uses the include clause
.Include(*)
. This means that all relations are recursively included. If the affected entities have lots of relations, the TCQL statement can become quite large and therefore slow. To improve the performance of the action, add an IncludeClause directive that specifies all relations that are really necessary to run the action.
IncludeClause Processing Directive
An action can optionally contain a processing directive # IncludeClause
that specifies which relations should be included. The directive code has to be in the first lines of the action's code with no empty lines before or in between. The IncludeClause
directive is implemented as a Python comment. It has the following syntax:
<includeClauseDirective> ::=
[ # IncludeClause
( NoInclude
| .Include(*)
| .Include(<relation_path>)[.Include(<relation_path>)...] ) ]
[ # SelectSettings.AutoIncludeRelations ( true | false ) ]
<relation_path> ::=
"<relation_name>[.<relation_name>...]"
Let's assume the following action is used for time sheet entries. The # IncludeClause
specifies that Tasks, Projects, Customers and Invoices should be included.
# IncludeClause .Include('APP_Task.APP_Project.APP_Customer').Include('Invoice')
clr.AddReference("System")
clr.AddReference("PresentationFramework")
clr.AddReference("System.Core")
...
If you do not want to include relations, use the following include processing directive: # IncludeClause NoInclude
. Note that this directive does not suppress including relations that are necessary for calculated properties, validation rules, etc.
To specify that you want to include only the relations that you explicitly mention in the # IncludeClause .Include(...)
directive without automatically including relations that are necessary for calculated properties, validation rules or permissions, specify the following directive: # SelectSettings.AutoIncludeRelations false
. This is useful if you just need few properties of the selected entities like for example the Uuids of the entity.
If you do not want to include any relations, you have to combine # SelectSettings.AutoIncludeRelations false
and # IncludeClause NoInclude
.
Note
Never use # SelectSettings.AutoIncludeRelations false
if you will save the selected entities in the action. Validation rules and permissions will not work as expected if the necessary relations are not loaded.