@prorigo/protrak-forge 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/bin/protrak-forge.js +2386 -6553
- package/data/CLAUDE.md.template +75 -1
- package/data/patterns/index.md +0 -11
- package/package.json +1 -1
- package/data/patterns/attribute-copy.md +0 -89
- package/data/patterns/batch-update.md +0 -72
- package/data/patterns/notification-template-reference.md +0 -191
- package/data/patterns/reference-navigation.md +0 -76
- package/data/patterns/report.md +0 -73
- package/data/patterns/scheduler.md +0 -77
package/data/CLAUDE.md.template
CHANGED
|
@@ -72,7 +72,7 @@ These are **Protrak-specific gotchas you cannot catch by reading the code**. Alw
|
|
|
72
72
|
|
|
73
73
|
### Understanding the workspace:
|
|
74
74
|
- `list_protrak_schema_elements` (optionally with `kind`: `type`, `attribute`, `lifecycle`,
|
|
75
|
-
`relation_type`) catalogs what's defined.
|
|
75
|
+
`relation_type`, `rule`) catalogs what's defined.
|
|
76
76
|
- `read_protrak_schema_element` (with `kind` and `name`) returns the full JSON of any one
|
|
77
77
|
element — for `kind='type'` you get the resolved schema with attributes, lifecycle, and
|
|
78
78
|
relations hydrated.
|
|
@@ -149,6 +149,77 @@ Each call creates `NotificationTemplates/<name>.json` (metadata) and `Notificati
|
|
|
149
149
|
Valid `target` values: `PromoteNotification`, `AccountNotification`, `WorkflowNotification`,
|
|
150
150
|
`WorkflowTaskNotification`.
|
|
151
151
|
|
|
152
|
+
## How to write Rules
|
|
153
|
+
|
|
154
|
+
Rules express attribute-filter logic used by display conditions, conditional formatting,
|
|
155
|
+
dependent picklists, and Access Policy. They are file-based: `Rules/<Name>.json`.
|
|
156
|
+
|
|
157
|
+
### Writing a new rule:
|
|
158
|
+
1. Call `get_protrak_rule_context(type_name?)` — returns the supported-conditions-per-
|
|
159
|
+
attribute-type table, value-less / range conditions, dynamic-value formats
|
|
160
|
+
(`@Instance.X`, `@User.X`, `$StartOfToday(±Nd)`), date mnemonics, basic-attribute map,
|
|
161
|
+
existing rule names, and canonical examples. Pass `type_name` to include that type's
|
|
162
|
+
resolved attribute schema (rules are not type-bound, but most rules reference one type).
|
|
163
|
+
2. Translate the natural-language intent into `attribute_filter_groups[]`. Each group has
|
|
164
|
+
an `operator` joining it to the **next** group; each condition has an `operator` joining
|
|
165
|
+
it to the **next** condition in the same group. The final group / condition uses `None`.
|
|
166
|
+
3. Instance-context `attribute_name` must exist in `Attributes/` or be a basic attribute
|
|
167
|
+
(`Name`, `State`, `Created`, `Creator`, `Modifier`, `Lifecycle`, ...). User-context names
|
|
168
|
+
refer to user-profile attributes (not workspace-validated). For `UserRoles`, omit
|
|
169
|
+
`attribute_name`.
|
|
170
|
+
4. Call `generate_protrak_rule(name, attribute_filter_groups, error_message?)`. Pass
|
|
171
|
+
`error_message` **only** when the rule will be selected as a Type's Access Policy —
|
|
172
|
+
it appears in the access-denied response.
|
|
173
|
+
|
|
174
|
+
### Attaching a rule to a layout / type / picklist:
|
|
175
|
+
Call `attach_protrak_rule(usage, rule_name, ...)`. One polymorphic tool covers five shapes:
|
|
176
|
+
|
|
177
|
+
- `usage='display_condition'`, `target_kind='layout_template'`, `target_name`, `widget_id?`
|
|
178
|
+
— appends to the widget's `displayConditions: string[]`.
|
|
179
|
+
- `usage='display_condition'`, `target_kind='form'`, `target_name`, `attribute_name`,
|
|
180
|
+
`container_id?` — sets the **singular** `displayCondition` on the form field.
|
|
181
|
+
- `usage='format_condition'`, `target_name=<ViewLayout>`, `attribute_name`, `section_name?`,
|
|
182
|
+
`widget_name?`, `style`, `order_index?` — appends `{ rule:{name}, orderIndex, style }`
|
|
183
|
+
to the ViewLayout field's `formatConditions`.
|
|
184
|
+
- `usage='conditional_formatting'`, `target_kind='type_widget'|'form'`, `target_name`,
|
|
185
|
+
`attribute_name`, `style`, `order_index?` — appends `{ ruleName, orderIndex, style }`
|
|
186
|
+
to the column/field's `conditionalFormatting`.
|
|
187
|
+
- `usage='access_policy'`, `type_name` — sets `accessPolicyRule` on `Types/<name>.json`.
|
|
188
|
+
- `usage='dependent_picklist'`, `attribute_name=<PicklistAttr>`, `option_name` — sets the
|
|
189
|
+
`rule` field on that picklist option.
|
|
190
|
+
|
|
191
|
+
Style is structured: `{ background_color?: '#RRGGBB', fore_color?: '#RRGGBB',
|
|
192
|
+
font_style?: ('Bold'|'Italic'|'Underline'|'Strikethrough')[] }`. The tool serializes it
|
|
193
|
+
to the comma-terminated CSS-string the SPA evaluator expects (`backgroundColor`, `color`,
|
|
194
|
+
`fontWeight`, `fontStyle`, `textDecorationLine`). Both `Underline` and `Strikethrough`
|
|
195
|
+
collapse into a single `textDecorationLine` token.
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
get_protrak_rule_context(type_name='ProjectAllocation')
|
|
199
|
+
# ... agent picks attributes, condition, dynamic values ...
|
|
200
|
+
generate_protrak_rule(
|
|
201
|
+
name='OverdueAllocation',
|
|
202
|
+
attribute_filter_groups=[{
|
|
203
|
+
operator: 'None',
|
|
204
|
+
conditions: [{
|
|
205
|
+
attribute_context: 'Instance',
|
|
206
|
+
attribute_name: 'AllocationEndDate',
|
|
207
|
+
condition: 'LessThanOrEqual',
|
|
208
|
+
first_value: { value_type: 'Static', value: '$StartOfToday()' },
|
|
209
|
+
operator: 'None',
|
|
210
|
+
}],
|
|
211
|
+
}],
|
|
212
|
+
)
|
|
213
|
+
attach_protrak_rule(
|
|
214
|
+
usage='conditional_formatting',
|
|
215
|
+
rule_name='OverdueAllocation',
|
|
216
|
+
target_kind='type_widget',
|
|
217
|
+
target_name='ProjectActiveAllocationsRelationWidget',
|
|
218
|
+
attribute_name='AllocationEndDate',
|
|
219
|
+
style={ background_color: '#ee8bb6' },
|
|
220
|
+
)
|
|
221
|
+
```
|
|
222
|
+
|
|
152
223
|
## Project structure
|
|
153
224
|
- `Types/` — entity type JSON definitions
|
|
154
225
|
- `Attributes/` — attribute JSON definitions
|
|
@@ -158,3 +229,6 @@ Valid `target` values: `PromoteNotification`, `AccountNotification`, `WorkflowNo
|
|
|
158
229
|
- `RelationTypes/` — relation definitions between types
|
|
159
230
|
- `NotificationTemplates/` — notification template files (paired `.json` + `.html`; use
|
|
160
231
|
`generate_protrak_notification_template` to keep them in sync)
|
|
232
|
+
- `Rules/` — business-rule JSON definitions (attribute filter groups). Use
|
|
233
|
+
`generate_protrak_rule` to author and `attach_protrak_rule` to wire into layouts /
|
|
234
|
+
type access policies / picklist dependencies.
|
package/data/patterns/index.md
CHANGED
|
@@ -37,7 +37,6 @@ instance.SetPicklistAttributeValue("Status", "Active");
|
|
|
37
37
|
| [Create and Connect Child Instances](./create-and-connect-pattern.md) | Create related instances and link them | PostCreate, PostConnect |
|
|
38
38
|
| [Cascade Promote](./cascade-promote-pattern.md) | Propagate a lifecycle transition to related instances; aggregate-all-approvers check | PromoteActionCommand, PostCreate, Scheduler |
|
|
39
39
|
| [Query and Filter](./query-filter-pattern.md) | Query instances using InstanceQuery and RelatedInstanceQuery | All program types |
|
|
40
|
-
| [Aggregate Related Instances](./aggregate-related-instances.md) | Parent → related fan-out + roll-up; uses `RelatedQueryBuilder.Select(...)` to avoid N+1 GetInstance calls | Scheduler, PostCreate, PromoteActionCommand |
|
|
41
40
|
| [Invoke Common Program](./invoke-common-program.md) | Call reusable logic encapsulated in a Common Program | All program types |
|
|
42
41
|
| [Manage Instance Access](./manage-instance-access-pattern.md) | Grant or revoke per-instance user access; create users; manage roles | PostConnect, PostCreate, PromoteActionCommand |
|
|
43
42
|
| [Delete Related Data](./delete-related-data-pattern.md) | Delete or unlink child instances before parent deletion | PreDelete |
|
|
@@ -94,16 +93,6 @@ Scheduler
|
|
|
94
93
|
→ [Cascade Promote] — promote when all approvers are done
|
|
95
94
|
```
|
|
96
95
|
|
|
97
|
-
### Scheduler: Roll up an aggregate from related instances onto the parent
|
|
98
|
-
|
|
99
|
-
```
|
|
100
|
-
Scheduler
|
|
101
|
-
→ [Query and Filter] — pick the parent set (e.g. all Active Projects)
|
|
102
|
-
→ [Aggregate Related Instances] — for each parent, fetch related rows with
|
|
103
|
-
RelatedQueryBuilder.Select(...) in a SINGLE call, sum/count/aggregate, and
|
|
104
|
-
write back via InstanceBuilder.ForUpdate + UpdateInstanceAsync.
|
|
105
|
-
```
|
|
106
|
-
|
|
107
96
|
---
|
|
108
97
|
|
|
109
98
|
## Service Injection
|
package/package.json
CHANGED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
# Attribute Copy / Auto-populate Pattern
|
|
2
|
-
|
|
3
|
-
## When to Use
|
|
4
|
-
Use this pattern to automatically copy or compute attribute values when an instance is created or updated. Common uses:
|
|
5
|
-
- Copy a field from a parent/related instance
|
|
6
|
-
- Auto-fill a date field (e.g. start date = today)
|
|
7
|
-
- Compute a derived value from other attributes
|
|
8
|
-
- Propagate changes from parent to children
|
|
9
|
-
|
|
10
|
-
## Program Types
|
|
11
|
-
Applies to: `PostCreate`, `PostUpdate`, `PreCreate`, `PreUpdate`
|
|
12
|
-
|
|
13
|
-
## Services Used
|
|
14
|
-
- `IInstanceService` — to fetch and update instances
|
|
15
|
-
- `IQueryBuilderService` — to filter related instances
|
|
16
|
-
|
|
17
|
-
## Code Example: Copy on Create
|
|
18
|
-
|
|
19
|
-
```csharp
|
|
20
|
-
using Prorigo.Protrak.API.Contracts;
|
|
21
|
-
using System;
|
|
22
|
-
using System.Threading.Tasks;
|
|
23
|
-
|
|
24
|
-
namespace MyProject.Customization
|
|
25
|
-
{
|
|
26
|
-
public class AutoPopulateStartDate : IPostCreateTriggerProgramAsync
|
|
27
|
-
{
|
|
28
|
-
public IInstanceService InstanceService { get; set; }
|
|
29
|
-
|
|
30
|
-
public async Task RunAsync(Guid instanceId)
|
|
31
|
-
{
|
|
32
|
-
var instance = await InstanceService.GetInstanceAsync(
|
|
33
|
-
instanceId, new[] { "StartDate" });
|
|
34
|
-
|
|
35
|
-
if (instance == null) return;
|
|
36
|
-
|
|
37
|
-
instance.Attributes = new[]
|
|
38
|
-
{
|
|
39
|
-
new Attribute { Name = "StartDate", Value = DateTime.UtcNow.ToString("o") }
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
await InstanceService.UpdateInstanceAsync(instance, null);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Code Example: Copy from Related Instance
|
|
49
|
-
|
|
50
|
-
```csharp
|
|
51
|
-
public class CopyCustomerRegionToProject : IPostCreateTriggerProgramAsync
|
|
52
|
-
{
|
|
53
|
-
public IInstanceService InstanceService { get; set; }
|
|
54
|
-
|
|
55
|
-
public async Task RunAsync(Guid instanceId)
|
|
56
|
-
{
|
|
57
|
-
var project = await InstanceService.GetInstanceAsync(
|
|
58
|
-
instanceId, new[] { "Customer", "Region" });
|
|
59
|
-
|
|
60
|
-
if (project == null) return;
|
|
61
|
-
|
|
62
|
-
// Get related customer ID from reference attribute
|
|
63
|
-
var customerIds = project.Attributes?
|
|
64
|
-
.FirstOrDefault(a => a.Name == "Customer")?.ReferenceValues;
|
|
65
|
-
|
|
66
|
-
if (customerIds == null || customerIds.Length == 0) return;
|
|
67
|
-
|
|
68
|
-
var customer = await InstanceService.GetInstanceAsync(
|
|
69
|
-
customerIds[0], new[] { "Region" });
|
|
70
|
-
|
|
71
|
-
var region = customer?.Attributes?
|
|
72
|
-
.FirstOrDefault(a => a.Name == "Region")?.Value?.ToString();
|
|
73
|
-
|
|
74
|
-
if (string.IsNullOrEmpty(region)) return;
|
|
75
|
-
|
|
76
|
-
project.Attributes = new[]
|
|
77
|
-
{
|
|
78
|
-
new Attribute { Name = "Region", Value = region }
|
|
79
|
-
};
|
|
80
|
-
await InstanceService.UpdateInstanceAsync(project, null);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## Key Rules
|
|
86
|
-
- Use `PostCreate`/`PostUpdate` (not `PreCreate`) when you need the instance ID to perform updates
|
|
87
|
-
- Null-check `ReferenceValues` before accessing related instances
|
|
88
|
-
- Pass `null` for `lastModified` in `UpdateInstanceAsync` to skip optimistic concurrency check
|
|
89
|
-
- Only set the attributes you want to change in `instance.Attributes`
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Batch Update Pattern
|
|
2
|
-
|
|
3
|
-
## When to Use
|
|
4
|
-
Use this pattern to update multiple related instances efficiently in parallel. Common uses:
|
|
5
|
-
- Update all child instances when a parent changes
|
|
6
|
-
- Recalculate a field across all instances of a type
|
|
7
|
-
- Cascade attribute changes
|
|
8
|
-
|
|
9
|
-
## Program Types
|
|
10
|
-
Applies to: `PostUpdate`, `PromoteActionCommand`, `Scheduler`
|
|
11
|
-
|
|
12
|
-
## Services Used
|
|
13
|
-
- `IInstanceService` — to fetch and update instances in bulk
|
|
14
|
-
- `IQueryBuilderService` — to filter instances for batch processing
|
|
15
|
-
|
|
16
|
-
## Code Example
|
|
17
|
-
|
|
18
|
-
```csharp
|
|
19
|
-
using Prorigo.Protrak.API.Contracts;
|
|
20
|
-
using System;
|
|
21
|
-
using System.Linq;
|
|
22
|
-
using System.Threading.Tasks;
|
|
23
|
-
|
|
24
|
-
namespace MyProject.Customization
|
|
25
|
-
{
|
|
26
|
-
public class UpdateAllTaskPrioritiesOnProjectChange : IPostUpdateTriggerProgramAsync
|
|
27
|
-
{
|
|
28
|
-
public IInstanceService InstanceService { get; set; }
|
|
29
|
-
public IQueryBuilderService QueryBuilderService { get; set; }
|
|
30
|
-
|
|
31
|
-
public async Task<ProgramResult> RunAsync(Guid instanceId)
|
|
32
|
-
{
|
|
33
|
-
var project = await InstanceService.GetInstanceAsync(
|
|
34
|
-
instanceId, new[] { "Priority" });
|
|
35
|
-
|
|
36
|
-
var priority = project?.Attributes?
|
|
37
|
-
.FirstOrDefault(a => a.Name == "Priority")?.Value?.ToString();
|
|
38
|
-
|
|
39
|
-
if (string.IsNullOrEmpty(priority))
|
|
40
|
-
return new ProgramResult { IsSuccess = true };
|
|
41
|
-
|
|
42
|
-
// Find all tasks for this project
|
|
43
|
-
var filter = QueryBuilderService.CreateRelatedInstanceFilterExpression(
|
|
44
|
-
"Project", instanceId);
|
|
45
|
-
var query = new InstanceQuery { TypeName = "Task", Filter = filter, Take = 500 };
|
|
46
|
-
var tasks = await InstanceService.GetInstancesAsync(query);
|
|
47
|
-
|
|
48
|
-
if (tasks?.Data == null || tasks.Data.Length == 0)
|
|
49
|
-
return new ProgramResult { IsSuccess = true };
|
|
50
|
-
|
|
51
|
-
// Update all tasks in parallel
|
|
52
|
-
var updates = tasks.Data.Select(async task =>
|
|
53
|
-
{
|
|
54
|
-
task.Attributes = new[]
|
|
55
|
-
{
|
|
56
|
-
new Attribute { Name = "Priority", Value = priority }
|
|
57
|
-
};
|
|
58
|
-
await InstanceService.UpdateInstanceAsync(task, null);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
await Task.WhenAll(updates);
|
|
62
|
-
return new ProgramResult { IsSuccess = true };
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Key Rules
|
|
69
|
-
- Use `Task.WhenAll` for parallel updates — much faster than sequential `await` in a loop
|
|
70
|
-
- Limit batch size with `Take` (max 500 recommended)
|
|
71
|
-
- Pass `null` for `lastModified` in `UpdateInstanceAsync` to skip concurrency check
|
|
72
|
-
- Only include changed attributes in `instance.Attributes` to avoid overwriting other fields
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
# Notification Template Reference
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
Protrak notification templates define the content of notifications (email + push) triggered by
|
|
6
|
-
platform events. Templates use **Razor markup syntax** (`@Model.*`) and are stored as paired files:
|
|
7
|
-
|
|
8
|
-
- `NotificationTemplates/<title>.json` — metadata (title, target, push message, state)
|
|
9
|
-
- `NotificationTemplates/<title>.html` — HTML email body (Razor template)
|
|
10
|
-
|
|
11
|
-
Two templates are needed for a complete email notification: one for the **subject** (title ending
|
|
12
|
-
in `Subject`) and one for the **body** (title ending in `Body`). Push-only templates need only
|
|
13
|
-
the `.json` with a `messageText` value.
|
|
14
|
-
|
|
15
|
-
## Target Types
|
|
16
|
-
|
|
17
|
-
| Target | Trigger event | Usage |
|
|
18
|
-
|---|---|---|
|
|
19
|
-
| `PromoteNotification` | Lifecycle promote action | Link in a lifecycle's "Send Notification" command |
|
|
20
|
-
| `AccountNotification` | User creation / password reset | Link in Tenant Settings → Notifications |
|
|
21
|
-
| `WorkflowNotification` | Workflow notification activity | Link in Workflow → Notification Activity |
|
|
22
|
-
| `WorkflowTaskNotification` | Workflow input/checklist task creation | Link in Workflow → Input/Checklist Task Activity |
|
|
23
|
-
|
|
24
|
-
## @Model Variables — PromoteNotification
|
|
25
|
-
|
|
26
|
-
Available when `target` is `PromoteNotification`. These reflect the promoted instance.
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
@Model.InstanceName — Display name of the instance
|
|
30
|
-
@Model.InstanceUrl — Deep link URL to the instance
|
|
31
|
-
@Model.InstanceId — GUID of the instance
|
|
32
|
-
@Model.InstanceTrackingId — Tracking / reference number
|
|
33
|
-
@Model.PreviousStateName — Lifecycle state before the promote
|
|
34
|
-
@Model.CurrentStateName — Lifecycle state after the promote
|
|
35
|
-
@Model.ModifiedDate — Date/time of the promote (UTC)
|
|
36
|
-
@Model.ModifiedBy — Name of the user who triggered it
|
|
37
|
-
@Model.CreatedBy — Name of the user who created the instance
|
|
38
|
-
@Model.Remarks — Remarks entered on the promote action
|
|
39
|
-
@Model.AttributeValues["<AttributeName>"] — Attribute accessor (see below)
|
|
40
|
-
@Model.TenantInfo.TenantName
|
|
41
|
-
@Model.TenantInfo.LogoUrl
|
|
42
|
-
@Model.TenantInfo.DateTimeFormat.DateFormat
|
|
43
|
-
@Model.TenantInfo.DateTimeFormat.TimeFormat
|
|
44
|
-
@Model.TenantInfo.DateTimeFormat.TimeZone
|
|
45
|
-
@Model.TenantInfo.Locale.Name
|
|
46
|
-
@Model.TenantInfo.Locale.DisplayName
|
|
47
|
-
@Model.ApplicationUrl
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## @Model Variables — AccountNotification
|
|
51
|
-
|
|
52
|
-
Available when `target` is `AccountNotification`. These reflect the Protrak user account.
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
@Model.UserName
|
|
56
|
-
@Model.UserFullName
|
|
57
|
-
@Model.UserEmail
|
|
58
|
-
@Model.UserPassword
|
|
59
|
-
@Model.CompanyName
|
|
60
|
-
@Model.CompanyEmail
|
|
61
|
-
@Model.CompanyDomain
|
|
62
|
-
@Model.CompanyLogoUrl
|
|
63
|
-
@Model.UserRoles[index]
|
|
64
|
-
@Model.UserAttributes["<AttributeName>"]
|
|
65
|
-
@Model.MFAVerificationCode
|
|
66
|
-
@Model.MFAVerificationCodeValidity
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## @Model Variables — WorkflowNotification
|
|
70
|
-
|
|
71
|
-
Available when `target` is `WorkflowNotification`. These reflect the instance attached to the workflow.
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
@Model.InstanceName
|
|
75
|
-
@Model.InstanceUrl
|
|
76
|
-
@Model.InstanceId
|
|
77
|
-
@Model.InstanceTrackingId
|
|
78
|
-
@Model.ApplicationUrl
|
|
79
|
-
@Model.StateName
|
|
80
|
-
@Model.ModifiedDate
|
|
81
|
-
@Model.ModifiedBy
|
|
82
|
-
@Model.CreatedBy
|
|
83
|
-
@Model.AttributeValues["<AttributeName>"]
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## @Model Variables — WorkflowTaskNotification
|
|
87
|
-
|
|
88
|
-
Available when `target` is `WorkflowTaskNotification`. These reflect the workflow task.
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
@Model.InstanceName
|
|
92
|
-
@Model.InstanceUrl
|
|
93
|
-
@Model.InstanceId
|
|
94
|
-
@Model.InstanceTrackingId
|
|
95
|
-
@Model.ApplicationUrl
|
|
96
|
-
@Model.StateName
|
|
97
|
-
@Model.ModifiedDate
|
|
98
|
-
@Model.ModifiedBy
|
|
99
|
-
@Model.CreatedBy
|
|
100
|
-
@Model.AttributeValues["<AttributeName>"]
|
|
101
|
-
@Model.WorkflowTask.Id
|
|
102
|
-
@Model.WorkflowTask.Name
|
|
103
|
-
@Model.WorkflowTask.Description
|
|
104
|
-
@Model.WorkflowTask.Comments
|
|
105
|
-
@Model.WorkflowTask.Type
|
|
106
|
-
@Model.WorkflowTask.WorkflowInstanceId
|
|
107
|
-
@Model.WorkflowTask.PausedWorkflowInstActivityId
|
|
108
|
-
@Model.WorkflowTask.TypeInstId
|
|
109
|
-
@Model.WorkflowTask.Status
|
|
110
|
-
@Model.WorkflowTask.AssignedUserId
|
|
111
|
-
@Model.WorkflowTask.AssignedRoleId
|
|
112
|
-
@Model.WorkflowTask.Created
|
|
113
|
-
@Model.WorkflowTask.Due
|
|
114
|
-
@Model.WorkflowTask.Completed
|
|
115
|
-
@Model.WorkflowTask.CompletedBy
|
|
116
|
-
@Model.WorkflowTask.ChecklistItems[index].Id
|
|
117
|
-
@Model.WorkflowTask.ChecklistItems[index].Name
|
|
118
|
-
@Model.WorkflowTask.ChecklistItems[index].IsMandatory
|
|
119
|
-
@Model.WorkflowTask.ChecklistItems[index].IsCompleted
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## Attribute Value Accessors
|
|
123
|
-
|
|
124
|
-
The `@Model.AttributeValues["<AttributeName>"]` accessor returns an object with type-specific
|
|
125
|
-
properties. Access the correct property based on the attribute type:
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
.TextValue — Text attributes
|
|
129
|
-
.NumericValue — Numeric attributes
|
|
130
|
-
.DateValue — Date attributes (stored as UTC DateTime)
|
|
131
|
-
.ArrayValue[0] — Picklist (single) — string value
|
|
132
|
-
.UserValues[0].UserName — User attributes (first user)
|
|
133
|
-
.UserValues[0].UserEmail
|
|
134
|
-
.UserValues[0].UserId
|
|
135
|
-
.ReferenceValue[0].Name — Reference attributes (first linked instance)
|
|
136
|
-
.ReferenceValue[0].Id
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## Razor Syntax Examples
|
|
140
|
-
|
|
141
|
-
### Simple substitution
|
|
142
|
-
```html
|
|
143
|
-
<p>Hi @Model.AttributeValues["Employee"].UserValues[0].UserName,</p>
|
|
144
|
-
<p>Your <a href='@Model.InstanceUrl'>@Model.InstanceName</a> has been approved.</p>
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### Conditional content
|
|
148
|
-
```html
|
|
149
|
-
@if (Model.AttributeValues["Country"].ArrayValue[0] == "India")
|
|
150
|
-
{
|
|
151
|
-
<span>Tax rate: @Model.AttributeValues["GSTRate"].NumericValue%</span>
|
|
152
|
-
}
|
|
153
|
-
else
|
|
154
|
-
{
|
|
155
|
-
<span>No tax applicable.</span>
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Null-safe access
|
|
160
|
-
```html
|
|
161
|
-
@(Model.AttributeValues.ContainsKey("Title")
|
|
162
|
-
? Model.AttributeValues["Title"]?.TextValue ?? "N/A"
|
|
163
|
-
: "N/A")
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## File Format
|
|
167
|
-
|
|
168
|
-
### `<title>.json`
|
|
169
|
-
```json
|
|
170
|
-
{
|
|
171
|
-
"title": "Invoice Sent Body",
|
|
172
|
-
"target": "PromoteNotification",
|
|
173
|
-
"emailText": null,
|
|
174
|
-
"hashCode": "",
|
|
175
|
-
"messageText": null,
|
|
176
|
-
"templateState": "Published"
|
|
177
|
-
}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
- `emailText` is always `null` in file-based templates — the email body lives in the `.html` file.
|
|
181
|
-
- `hashCode` is left empty; Protrak recomputes it on import (same pattern as `programHashCode`).
|
|
182
|
-
- `messageText` is the plain-text push notification content (Razor syntax supported).
|
|
183
|
-
- `templateState`: `"Published"` or `"InProgress"`.
|
|
184
|
-
|
|
185
|
-
### `<title>.html`
|
|
186
|
-
```html
|
|
187
|
-
<p>Hi @Model.AttributeValues["Manager"].UserValues[0].UserName,</p>
|
|
188
|
-
<p>Invoice <strong>@Model.InstanceName</strong> has been sent to the client.</p>
|
|
189
|
-
<p><a href='@Model.InstanceUrl'>View invoice</a></p>
|
|
190
|
-
<p><i>This is a system generated mail, please do not reply.</i></p>
|
|
191
|
-
```
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# Reference Navigation Pattern
|
|
2
|
-
|
|
3
|
-
## When to Use
|
|
4
|
-
Use this pattern to navigate from one instance to a related instance via a reference attribute. Common uses:
|
|
5
|
-
- Get the parent project of a task
|
|
6
|
-
- Get the customer linked to an order
|
|
7
|
-
- Follow a chain of relations (task → project → customer)
|
|
8
|
-
|
|
9
|
-
## Program Types
|
|
10
|
-
Applies to: `PreCreate`, `PostCreate`, `PreUpdate`, `PostUpdate`, `PromoteActionCommand`
|
|
11
|
-
|
|
12
|
-
## Services Used
|
|
13
|
-
- `IInstanceService` — to fetch related instances by GUID
|
|
14
|
-
|
|
15
|
-
## Code Example
|
|
16
|
-
|
|
17
|
-
```csharp
|
|
18
|
-
using Prorigo.Protrak.API.Contracts;
|
|
19
|
-
using System;
|
|
20
|
-
using System.Linq;
|
|
21
|
-
using System.Threading.Tasks;
|
|
22
|
-
|
|
23
|
-
namespace MyProject.Customization
|
|
24
|
-
{
|
|
25
|
-
public class ValidateEquipmentStatus : IPreCreateTriggerProgramAsync
|
|
26
|
-
{
|
|
27
|
-
public IInstanceService InstanceService { get; set; }
|
|
28
|
-
|
|
29
|
-
public async Task<ProgramResult> RunAsync(Instance instance)
|
|
30
|
-
{
|
|
31
|
-
// Navigate: Process → Equipment (via reference attribute)
|
|
32
|
-
var equipmentIds = instance.Attributes?
|
|
33
|
-
.FirstOrDefault(a => a.Name == "Equipment")?.ReferenceValues;
|
|
34
|
-
|
|
35
|
-
if (equipmentIds == null || equipmentIds.Length == 0)
|
|
36
|
-
return new ProgramResult { IsSuccess = false, Message = "Equipment is required." };
|
|
37
|
-
|
|
38
|
-
var equipment = await InstanceService.GetInstanceAsync(
|
|
39
|
-
equipmentIds[0], new[] { "Status", "AvailableFrom" });
|
|
40
|
-
|
|
41
|
-
if (equipment == null)
|
|
42
|
-
return new ProgramResult { IsSuccess = false, Message = "Equipment not found." };
|
|
43
|
-
|
|
44
|
-
var status = equipment.Attributes?
|
|
45
|
-
.FirstOrDefault(a => a.Name == "Status")?.Value?.ToString();
|
|
46
|
-
|
|
47
|
-
if (status != "Available")
|
|
48
|
-
{
|
|
49
|
-
return new ProgramResult
|
|
50
|
-
{
|
|
51
|
-
IsSuccess = false,
|
|
52
|
-
Message = $"Equipment is not available (current status: {status})."
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return new ProgramResult { IsSuccess = true };
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Key Rules
|
|
63
|
-
- Reference attributes expose related instance IDs via `ReferenceValues` (array of Guids)
|
|
64
|
-
- Always null-check `ReferenceValues` and verify length before accessing index 0
|
|
65
|
-
- Fetch only the attributes you need in the `GetInstanceAsync` attributes parameter
|
|
66
|
-
- For multi-valued references, iterate or use `ReferenceValues[0]` for the first
|
|
67
|
-
|
|
68
|
-
## When NOT to use this pattern (anti-pattern callout)
|
|
69
|
-
|
|
70
|
-
This pattern is for **navigating to a single related instance via a Reference attribute**. It is **not** the right approach when you have many related instances and want to read attributes from each.
|
|
71
|
-
|
|
72
|
-
> ❌ **DO NOT** call `GetRelatedInstancesAsync` to get a list of related ids and then call `GetInstanceAsync` once per id to fetch their attributes. That's an N+1 round-trip — one extra DB hit per related instance.
|
|
73
|
-
>
|
|
74
|
-
> ✅ **DO** use `RelatedQueryBuilder.Select(attr1, attr2, ...)` so the related instances come back **with attributes populated in a single call**. Read the values off the `RelatedInstance` directly with `GetDateAttributeValue`, `GetTextAttributeValue`, etc.
|
|
75
|
-
|
|
76
|
-
For collection-level fan-out (sums, counts, rollups), see [Aggregate Related Instances](./aggregate-related-instances.md).
|
package/data/patterns/report.md
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# Report Program Pattern
|
|
2
|
-
|
|
3
|
-
## When to Use
|
|
4
|
-
Use this pattern to generate dynamic reports from Protrak data. Report programs provide both the data and the configuration (columns, charts) for the report UI.
|
|
5
|
-
|
|
6
|
-
## Program Types
|
|
7
|
-
Applies to: `Report`
|
|
8
|
-
|
|
9
|
-
## Services Used
|
|
10
|
-
- `IInstanceService` — to query data for the report
|
|
11
|
-
- `IQueryBuilderService` — to filter data
|
|
12
|
-
|
|
13
|
-
## Code Example
|
|
14
|
-
|
|
15
|
-
```csharp
|
|
16
|
-
using Prorigo.Protrak.API.Contracts;
|
|
17
|
-
using System;
|
|
18
|
-
using System.Collections.Generic;
|
|
19
|
-
using System.Linq;
|
|
20
|
-
using System.Threading.Tasks;
|
|
21
|
-
|
|
22
|
-
namespace MyProject.Customization
|
|
23
|
-
{
|
|
24
|
-
public class EquipmentUtilizationReport : IReportProgramAsync
|
|
25
|
-
{
|
|
26
|
-
public IInstanceService InstanceService { get; set; }
|
|
27
|
-
public IQueryBuilderService QueryBuilderService { get; set; }
|
|
28
|
-
|
|
29
|
-
public async Task<ReportData> GetReportDataAsync(ReportQuery query)
|
|
30
|
-
{
|
|
31
|
-
var instanceQuery = new InstanceQuery
|
|
32
|
-
{
|
|
33
|
-
TypeName = "Equipment",
|
|
34
|
-
Take = 500
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
var equipment = await InstanceService.GetInstancesAsync(instanceQuery);
|
|
38
|
-
|
|
39
|
-
var rows = equipment?.Data?.Select(e => new ReportRow
|
|
40
|
-
{
|
|
41
|
-
Values = new Dictionary<string, object>
|
|
42
|
-
{
|
|
43
|
-
["Name"] = e.Attributes?.FirstOrDefault(a => a.Name == "Name")?.Value ?? "",
|
|
44
|
-
["Status"] = e.Attributes?.FirstOrDefault(a => a.Name == "Status")?.Value ?? "",
|
|
45
|
-
["UtilizationHours"] = e.Attributes?.FirstOrDefault(a => a.Name == "UtilizationHours")?.Value ?? 0,
|
|
46
|
-
}
|
|
47
|
-
}).ToList() ?? new List<ReportRow>();
|
|
48
|
-
|
|
49
|
-
return new ReportData { Rows = rows };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
public async Task<ReportConfig> GetReportConfigAsync()
|
|
53
|
-
{
|
|
54
|
-
return new ReportConfig
|
|
55
|
-
{
|
|
56
|
-
Title = "Equipment Utilization",
|
|
57
|
-
Columns = new[]
|
|
58
|
-
{
|
|
59
|
-
new ReportColumn { Name = "Name", DisplayName = "Equipment Name" },
|
|
60
|
-
new ReportColumn { Name = "Status", DisplayName = "Status" },
|
|
61
|
-
new ReportColumn { Name = "UtilizationHours", DisplayName = "Hours Used" },
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Key Rules
|
|
70
|
-
- Implement both `GetReportDataAsync` and `GetReportConfigAsync`
|
|
71
|
-
- `ReportQuery` may contain filter parameters set by the user in the UI
|
|
72
|
-
- Return an empty `ReportData` (not null) if no data matches
|
|
73
|
-
- Keep queries efficient — reports run on demand, avoid slow full-table scans
|