@smartkomm/plexus-cli 0.0.0-stage → 0.1.1
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 +863 -2
- package/dist/cli.mjs +5432 -0
- package/dist/skill/.managed-by-plexus-cli +1 -0
- package/dist/skill/SKILL.md +224 -0
- package/dist/skill/references/accounts/email-changes.md +46 -0
- package/dist/skill/references/agents/use-shared-tools.md +52 -0
- package/dist/skill/references/applications/inspect-process-store.md +61 -0
- package/dist/skill/references/applications/start-a-test-application.md +66 -0
- package/dist/skill/references/audit/answer-access-request.md +115 -0
- package/dist/skill/references/config/attach-a-template.md +117 -0
- package/dist/skill/references/config/init-tenant.md +121 -0
- package/dist/skill/references/config/onboard-tenant.md +71 -0
- package/dist/skill/references/config/rename-program-stage.md +99 -0
- package/dist/skill/references/config/set-tenant-branding.md +150 -0
- package/dist/skill/references/config/set-tenant-terms.md +50 -0
- package/dist/skill/references/config/set-up-billing-catalog.md +73 -0
- package/dist/skill/references/config/update-tenant-config.md +174 -0
- package/dist/skill/references/config/wipe-tenant-config.md +107 -0
- package/dist/skill/references/export/create-named-export.md +47 -0
- package/dist/skill/references/feedback/create-and-triage-feedback.md +69 -0
- package/dist/skill/references/feedback/manage-feedback-tags.md +57 -0
- package/dist/skill/references/finance/change-contract-grant.md +36 -0
- package/dist/skill/references/finance/record-submission-date.md +33 -0
- package/dist/skill/references/interessenten/list-interessenten.md +54 -0
- package/dist/skill/references/marketing/demo-video-links.md +101 -0
- package/dist/skill/references/notifications/debug-mail-delivery.md +77 -0
- package/dist/skill/references/notifications/mail-level.md +90 -0
- package/dist/skill/references/notifications/reword-a-letter.md +155 -0
- package/dist/skill/references/notifications/trace-a-login.md +50 -0
- package/dist/skill/references/offer/add-workflow-node.md +268 -0
- package/dist/skill/references/offer/addresses.md +129 -0
- package/dist/skill/references/offer/appointment-task.md +79 -0
- package/dist/skill/references/offer/assignment-rule.md +195 -0
- package/dist/skill/references/offer/attach-form-to-workflow.md +79 -0
- package/dist/skill/references/offer/author-form-scenarios.md +97 -0
- package/dist/skill/references/offer/author-manifest-meta.md +94 -0
- package/dist/skill/references/offer/author-smartform.md +102 -0
- package/dist/skill/references/offer/bind-node-input.md +76 -0
- package/dist/skill/references/offer/completeness-check.md +137 -0
- package/dist/skill/references/offer/countersign-node.md +91 -0
- package/dist/skill/references/offer/create-pedagogy-unit.md +55 -0
- package/dist/skill/references/offer/declare-node-reads.md +99 -0
- package/dist/skill/references/offer/edit-live-tenant-offer.md +144 -0
- package/dist/skill/references/offer/edit-smartform.md +121 -0
- package/dist/skill/references/offer/edit-workflow-node.md +126 -0
- package/dist/skill/references/offer/engage-push-node.md +147 -0
- package/dist/skill/references/offer/insert-and-rewire-node.md +67 -0
- package/dist/skill/references/offer/node-tasks.md +162 -0
- package/dist/skill/references/offer/optional-assessment.md +65 -0
- package/dist/skill/references/offer/publish-offer-bundle.md +117 -0
- package/dist/skill/references/offer/set-offer-capacities.md +78 -0
- package/dist/skill/references/offer/staff-input-if-node.md +112 -0
- package/dist/skill/references/offer/task-appointment-result.md +69 -0
- package/dist/skill/references/offer/task-family-request.md +69 -0
- package/dist/skill/references/offer/task-result-files.md +52 -0
- package/dist/skill/references/offer/template-document-node.md +113 -0
- package/dist/skill/references/offer/workflow-default-channels.md +69 -0
- package/dist/skill/references/resolver/manage-contract-resolver.md +161 -0
- package/dist/skill/references/retention/erase-due-applications.md +99 -0
- package/dist/skill/references/retention/erase-due-parties.md +107 -0
- package/dist/skill/references/roles/migrate-parent-assignments.md +31 -0
- package/dist/skill/references/signing/README.md +113 -0
- package/dist/skill/references/signing/connect-a-school.md +90 -0
- package/dist/skill/references/signing/rotate-the-key.md +41 -0
- package/dist/skill/references/stammdaten/purge-deleted-person.md +68 -0
- package/dist/skill/references/stripe/connect-a-school.md +107 -0
- package/dist/skill/references/stripe/request-and-verify-a-payment.md +78 -0
- package/dist/skill/references/test-actions/drive-a-test-application.md +69 -0
- package/dist/skill/references/users/add-platform-admin.md +59 -0
- package/dist/skill/references/users/add-platform-user.md +59 -0
- package/dist/skill/references/users/add-tenant-admin.md +75 -0
- package/dist/skill/references/users/change-login-email.md +55 -0
- package/dist/skill/references/users/cli-access-for-tenant-admin.md +117 -0
- package/dist/skill/references/users/delete-permission-group.md +76 -0
- package/dist/skill/references/users/grant-cross-tenant-access.md +57 -0
- package/dist/skill/references/users/grant-feedback-assignable.md +43 -0
- package/dist/skill/references/users/set-password.md +57 -0
- package/dist/skill/references/users/use-test-account.md +79 -0
- package/dist/skill/references/worker-ops/debug-worker-jobs.md +90 -0
- package/dist/skill/references/worker-ops/verify-migrations.md +51 -0
- package/dist/spec.snapshot.json +156821 -0
- package/package.json +53 -4
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Release an offer bundle — and check that it actually went live
|
|
2
|
+
|
|
3
|
+
**Use it to** take an edited bundle live on a tenant, stage it for testing first,
|
|
4
|
+
or find out why a change you applied is not visible to applicants.
|
|
5
|
+
|
|
6
|
+
## The model in one paragraph
|
|
7
|
+
|
|
8
|
+
An offer family is one ascending version stream. Exactly one generation is
|
|
9
|
+
**editable** (the draft, always the newest revision); at most one is **live**
|
|
10
|
+
(what real applicants run); at most one sits between them as the **internal
|
|
11
|
+
candidate** (testable, not public). Applying a bundle writes into the *draft*.
|
|
12
|
+
Publishing freezes the draft and spawns a fresh one. Applications already running
|
|
13
|
+
keep the generation they started on — forever, unless migrated explicitly.
|
|
14
|
+
|
|
15
|
+
So there are two different questions, and they have different answers:
|
|
16
|
+
|
|
17
|
+
| Question | Answered by |
|
|
18
|
+
|---|---|
|
|
19
|
+
| What would `offer apply` write into the draft? | `offer plan` / `offer diff` per-target rows |
|
|
20
|
+
| Is anything waiting to go live? | the `publish` row of `plan`/`diff`, and `offer status` |
|
|
21
|
+
|
|
22
|
+
## Check before you publish
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
plexus-cli --env staging offer status --tenant <slug> --slug <offer-key>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
field value
|
|
30
|
+
───────── ───────────────────────────────
|
|
31
|
+
manifest published
|
|
32
|
+
workflow bound
|
|
33
|
+
pedagogy Grundschule Beispielstadt
|
|
34
|
+
draft v12
|
|
35
|
+
internal -
|
|
36
|
+
live v11
|
|
37
|
+
publish PENDING (draft is ahead of live)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`publish: PENDING` means the draft says something the live generation does not —
|
|
41
|
+
regardless of whether *your* bundle changed anything. That is the state you are
|
|
42
|
+
in after an `offer apply` without `--publish`, or after someone saved in the
|
|
43
|
+
Workflow IDE.
|
|
44
|
+
|
|
45
|
+
## Publish
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Go live (default).
|
|
49
|
+
plexus-cli --env staging offer apply --tenant <slug> --dir ./bundle --publish
|
|
50
|
+
|
|
51
|
+
# Or stage as the testable candidate only — real applicants keep the live
|
|
52
|
+
# generation; a Testmodus application can be started against this one.
|
|
53
|
+
plexus-cli --env staging offer apply --tenant <slug> --dir ./bundle --publish --stage internal
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The final step of the report names what happened:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
step action detail
|
|
60
|
+
──────────────────── ────────── ──────
|
|
61
|
+
manifest unchanged
|
|
62
|
+
smartform:anmeldung unchanged
|
|
63
|
+
workflow created
|
|
64
|
+
offer published
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- `offer: published` — a new generation went live. With the detail *"promoted the
|
|
68
|
+
tested candidate"* it released the generation you staged with `--stage internal`
|
|
69
|
+
rather than cutting a fresh copy of it, so what goes live is what you tested.
|
|
70
|
+
- `offer: already-live` — the draft already equalled the live generation. Nothing
|
|
71
|
+
was flipped, and that is correct.
|
|
72
|
+
- `offer:internal: …` — the same two outcomes for `--stage internal`.
|
|
73
|
+
|
|
74
|
+
## Verify
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
plexus-cli --env staging offer status --tenant <slug> --slug <offer-key>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`live` must have moved to the draft's old revision and `publish` must read
|
|
81
|
+
`up to date`. In the app, the Workflow IDE's version picker shows the new
|
|
82
|
+
generation as **Aktuell**, and the one below it as **Vorgänger**.
|
|
83
|
+
|
|
84
|
+
## Gotchas
|
|
85
|
+
|
|
86
|
+
- **`--publish` publishes on the distance to the LIVE head, not to your file.**
|
|
87
|
+
Applying a bundle that matches the draft byte-for-byte still publishes when the
|
|
88
|
+
draft is ahead of live. This is deliberate: the older behaviour compared the
|
|
89
|
+
bundle against the draft, so a second run with `--publish` reported `unchanged`
|
|
90
|
+
everywhere and silently released nothing.
|
|
91
|
+
- **The form and workflow publish gates run only when something is released**,
|
|
92
|
+
and always before the flip — so a draft edited in the IDE is validated and
|
|
93
|
+
finalized before it is frozen, and an apply that releases nothing validates
|
|
94
|
+
nothing. A validation error aborts before any generation changes.
|
|
95
|
+
- **`--stage internal` discards an existing internal candidate** (it is archived).
|
|
96
|
+
That candidate never went live, so nothing an applicant sees changes.
|
|
97
|
+
- **Stage then release is two commands, not two generations.** `--stage internal`
|
|
98
|
+
followed by a plain `--publish` of the same bundle promotes the staged
|
|
99
|
+
generation in place. It does not archive it and release a copy.
|
|
100
|
+
- **Running applications are not migrated.** After a release, older applications
|
|
101
|
+
keep their generation and appear in the board's amber *Veraltete
|
|
102
|
+
Workflow-Version* section when their board layout differs.
|
|
103
|
+
- **`offer plan` is read-only and now carries a `publish` row too** — use it to
|
|
104
|
+
see a pending release without applying anything.
|
|
105
|
+
- **`offer plan` shows program stages as `~stage` / `+stage` / `-stage`** —
|
|
106
|
+
`apply` reconciles `program.stages` by bundle `key`, not by name (see
|
|
107
|
+
`../config/rename-program-stage.md`). Run `offer plan` before the first apply
|
|
108
|
+
on an env: a `-stage` row archives that level, and apply refuses it with
|
|
109
|
+
`[stage/hasActiveApplications]` while an active application sits on it.
|
|
110
|
+
**Runs** are still matched by display name and only ever added: a renamed run
|
|
111
|
+
becomes a second run.
|
|
112
|
+
|
|
113
|
+
## Related
|
|
114
|
+
|
|
115
|
+
- [Author a SmartForm as code](./author-smartform.md), [Add a workflow node](./add-workflow-node.md)
|
|
116
|
+
- [Manage a contract resolver](../resolver/manage-contract-resolver.md) — the
|
|
117
|
+
apply → assign → publish order
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Set an offer's seat capacities in its bundle
|
|
2
|
+
|
|
3
|
+
**When to use:** an offer's Schuljahr × Klassenstufe grid (seats per cohort, open or
|
|
4
|
+
closed) should travel with the bundle — a fresh environment gets the whole grid from one
|
|
5
|
+
`offer apply` instead of one `offer capacity set` per cell.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
- An offer bundle on disk (`offer export …`) whose `bundle.yaml` has a `program` block.
|
|
9
|
+
- A token with the tenant's offer-configuration rights (a tenant admin has them) for `offer plan` / `offer apply` on the target env.
|
|
10
|
+
- The stage and run keys of the offer (`program.stages[].key`, `program.runs[].key`) —
|
|
11
|
+
cells sit under a run (by its **bare** key) and are keyed by the **bare** stage key,
|
|
12
|
+
never by a name (names are `{ de, en }` display text).
|
|
13
|
+
|
|
14
|
+
## Steps
|
|
15
|
+
|
|
16
|
+
Each run lists its cells under `capacities`, keyed by stage key:
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
program:
|
|
20
|
+
unitKey: primary
|
|
21
|
+
stages:
|
|
22
|
+
- { key: grade-1, name: { de: Klassenstufe 1, en: Grade 1 }, gradeLevel: 1 }
|
|
23
|
+
- { key: grade-2, name: { de: Klassenstufe 2, en: Grade 2 }, gradeLevel: 2 }
|
|
24
|
+
runs:
|
|
25
|
+
- key: "2026-27"
|
|
26
|
+
name: { de: Schuljahr 2026/27, en: School year 2026/27 }
|
|
27
|
+
startDate: '2026-08-01'
|
|
28
|
+
endDate: '2027-07-31'
|
|
29
|
+
capacities:
|
|
30
|
+
grade-1: { isOpen: true, capacity: 24 } # 24 seats, accepting
|
|
31
|
+
grade-2: { isOpen: true, capacity: unlimited } # no cap
|
|
32
|
+
# grade-3: { isOpen: false, capacity: null } # null = unconfigured (clears a stored one)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
plexus-cli offer validate --dir <bundle> # offline: shape + stage keys
|
|
37
|
+
plexus-cli --env <env> offer plan --dir <bundle> # one `~cap run×stage from → to` row per changed cell
|
|
38
|
+
plexus-cli --env <env> offer apply --dir <bundle> # writes exactly those cells
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The fastest way to get the block right is to let the live offer write it:
|
|
42
|
+
`plexus-cli --env <env> offer export --slug <offer> --out <bundle>` emits every run with
|
|
43
|
+
all its cells.
|
|
44
|
+
|
|
45
|
+
## Verify
|
|
46
|
+
```bash
|
|
47
|
+
plexus-cli --env <env> offer plan --dir <bundle> # no `cap:` rows left = the grid matches
|
|
48
|
+
plexus-cli --env <env> offer show --slug <offer> # the live grid
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Idempotency / gotchas
|
|
52
|
+
- **A listed cell is authoritative** — both `isOpen` and `capacity`, every apply;
|
|
53
|
+
`capacity: null` clears a stored capacity. A cell the bundle does **not** list is left
|
|
54
|
+
alone. A re-apply of an unchanged bundle writes nothing.
|
|
55
|
+
- `capacity` is a whole number ≥ 0, `unlimited`, or `null`. **Unconfigured is not
|
|
56
|
+
unlimited:** an application to an open cell with `null` fails loud.
|
|
57
|
+
- A run with `capacities` must not also carry a run-level `isOpen` (validation error):
|
|
58
|
+
the open state lives on each cell. A NEW run with `capacities` is created closed and
|
|
59
|
+
then opened cell by cell. A run **without** `capacities` keeps the old behaviour — its
|
|
60
|
+
`isOpen` (default open) seeds every cell of a newly created run, and existing cells
|
|
61
|
+
are untouched.
|
|
62
|
+
- **Closing an existing Schuljahr = `isOpen: false` on each of its cells.** A run-level
|
|
63
|
+
`isOpen: false` on an existing run changes nothing and plans no row — a Schuljahr
|
|
64
|
+
"closed" that way stays open; the real close shows one `~cap … open → closed` row per
|
|
65
|
+
cell in the plan.
|
|
66
|
+
- For a real tenant the bundle on disk is a fresh `--env prod` export in a scratch dir,
|
|
67
|
+
never a stale or version-controlled copy — see
|
|
68
|
+
[edit-live-tenant-offer](./edit-live-tenant-offer.md).
|
|
69
|
+
- A cell naming a stage key that neither `program.stages` nor the live program has is
|
|
70
|
+
refused before anything is written (`[capacity/unknownStage]`). A stage the same bundle
|
|
71
|
+
adds plans as `(new)` and is written after the stage exists.
|
|
72
|
+
- `offer run add --capacity` / `offer capacity set` still exist for one-off edits; the
|
|
73
|
+
next `offer apply` of a bundle that lists the cell sets it back. `offer capacity set
|
|
74
|
+
--capacity unlimited` is accepted.
|
|
75
|
+
|
|
76
|
+
## Related
|
|
77
|
+
- [Rename / remove a program stage](../config/rename-program-stage.md) — the stage keys cells use.
|
|
78
|
+
- [Release an offer bundle](./publish-offer-bundle.md).
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Branch on a staff input („Eingabe" + `if` node)
|
|
2
|
+
|
|
3
|
+
**When to use:** the process should take a different path depending on something only
|
|
4
|
+
staff know when they start the application — e.g. a staff member's own child
|
|
5
|
+
(„Mitarbeiterbewerbung") skips the review and goes straight to the fee. Two
|
|
6
|
+
pieces: a **staff input** declared on the workflow (`graph.staffInputs`, a checkbox
|
|
7
|
+
staff tick in „+ Bewerbung"), and an **`if` node** whose condition reads it as
|
|
8
|
+
`$env.staffInput.<key>` and routes `true` / `false`. A family never sets a staff input:
|
|
9
|
+
a self-service start records every declared input as `false`.
|
|
10
|
+
|
|
11
|
+
In the workflow IDE the same declaration is the header button „Eingaben" (add an
|
|
12
|
+
input with its key, relabel, remove; the key is fixed once set), and the `if` node is a
|
|
13
|
+
diamond reading „if <pill>": the flow enters on the left, `true` leaves at the top,
|
|
14
|
+
`false` at the bottom.
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
- A bundle on disk (`offer export`, a live tenant's only into a private scratch dir —
|
|
18
|
+
[Edit a live tenant's offer](./edit-live-tenant-offer.md)). Authoring is offline.
|
|
19
|
+
- The node ids around the branch point: `plexus-cli offer workflow node list --dir <dir>`
|
|
20
|
+
(here: `form` routes `standard` → `review`, and the fee node is `fee`).
|
|
21
|
+
- A key: one identifier segment, lower-case first letter, letters and digits only
|
|
22
|
+
(`staffApplication`, not `staff-application`) — it is read as `$env.staffInput.<key>`.
|
|
23
|
+
|
|
24
|
+
## Rollout order and checks (a live tenant)
|
|
25
|
+
- **Deploy first, publish second.** `if` is a node type of the release: a
|
|
26
|
+
bundle using it publishes only on a deployment that registers it
|
|
27
|
+
(`[graph/unknownNodeType]` otherwise; the deploy needs a platform operator).
|
|
28
|
+
- **Running applications stay on their version** — the if node changes the
|
|
29
|
+
topology, so nobody is re-pinned; an application already past the form keeps
|
|
30
|
+
its fee step (the waiver „Zahlung überspringen" is the way out for a staff
|
|
31
|
+
child among them).
|
|
32
|
+
- **The Engage push may read the fee step** (`payment: $node.<fee>.payment`): it
|
|
33
|
+
passes the gate (`readSkipsAbsentProducer`) and skips the paid fields for a
|
|
34
|
+
staff child („Zahlungsschritt nicht durchlaufen" in the Übertragungsprotokoll).
|
|
35
|
+
- **Check the assignment rule**: a „+ Bewerbung" start is channel `staff`; a rule
|
|
36
|
+
row on `$env.applicant.channel == 'staff'` (creator responsible) now applies
|
|
37
|
+
to every staff child — decide whether that is wanted before publishing.
|
|
38
|
+
- Edit a live tenant's bundle only through [Edit a live tenant's offer](./edit-live-tenant-offer.md)
|
|
39
|
+
(export into a dated scratch dir, plan, `apply --publish --allow-prod`).
|
|
40
|
+
|
|
41
|
+
## Steps
|
|
42
|
+
```bash
|
|
43
|
+
# 1. Declare the staff input (type boolean = a checkbox; both label languages required).
|
|
44
|
+
plexus-cli offer workflow staff-input set staffApplication --dir <dir> \
|
|
45
|
+
--label "Mitarbeiterbewerbung" --label-en "Staff application"
|
|
46
|
+
plexus-cli offer workflow staff-input list --dir <dir>
|
|
47
|
+
|
|
48
|
+
# 2. Put an if node on the form's edge. `true` (a staff child) goes straight to
|
|
49
|
+
# the review, `false` to the fee — name both to be explicit.
|
|
50
|
+
plexus-cli offer workflow node add --dir <dir> --type if \
|
|
51
|
+
--id staffCheck --label "Mitarbeiterbewerbung?" --label-en "Staff application?" \
|
|
52
|
+
--on-edge form:standard \
|
|
53
|
+
--condition '$env.staffInput.staffApplication' \
|
|
54
|
+
--outcomes true:review,false:fee
|
|
55
|
+
|
|
56
|
+
# Later: change the condition or re-route an outcome in place.
|
|
57
|
+
plexus-cli offer workflow node set --dir <dir> --id staffCheck --condition '!$env.staffInput.staffApplication'
|
|
58
|
+
plexus-cli offer workflow node set --dir <dir> --id staffCheck --outcomes true:review,false:fee
|
|
59
|
+
|
|
60
|
+
# 3. Validate, plan, publish.
|
|
61
|
+
plexus-cli offer validate --dir <dir>
|
|
62
|
+
plexus-cli --env staging --tenant <t> offer plan --dir <dir>
|
|
63
|
+
plexus-cli --env staging --tenant <t> offer apply --publish --dir <dir>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Verify
|
|
67
|
+
```bash
|
|
68
|
+
plexus-cli offer workflow node list --dir <dir>
|
|
69
|
+
# form form … standard→staffCheck
|
|
70
|
+
# staffCheck if true, false true→review, false→fee
|
|
71
|
+
jq '.graph.staffInputs, (.graph.nodes[] | select(.type=="if"))' <dir>/workflow.json
|
|
72
|
+
|
|
73
|
+
# After publish: start one test application per answer and see where each stands.
|
|
74
|
+
E="--env staging --tenant <t>"
|
|
75
|
+
plexus-cli $E applications start-test --offer <offer> --run <run> --stage <stage> \
|
|
76
|
+
--applicant test@example.com --staff-input staffApplication=true
|
|
77
|
+
plexus-cli $E applications show <id> # after the form: review (true) / fee (false)
|
|
78
|
+
plexus-cli $E applications store <id> # $env.staffInput.staffApplication = true
|
|
79
|
+
```
|
|
80
|
+
In the cockpit, „+ Bewerbung" on this offer now shows the checkbox „Mitarbeiterbewerbung".
|
|
81
|
+
|
|
82
|
+
## Idempotency / gotchas
|
|
83
|
+
- **Every staff start sets every declared input.** The cockpit („+ Bewerbung", with or
|
|
84
|
+
without Stellvertretung), a lead conversion (Interessenten → „In Bewerbung umwandeln")
|
|
85
|
+
and `applications start-test --staff-input key=true|false` (once per input) are refused
|
|
86
|
+
with `[staffInput/missingValue]` when one is left out, `[staffInput/unknownKey]` for a key
|
|
87
|
+
the offer does not declare. A self-service start records each input `false` — there is
|
|
88
|
+
no applicant-callable way to set one.
|
|
89
|
+
- **Read only by a condition (v1).** A SmartForm, a document binding, a task condition or
|
|
90
|
+
an assignment rule cannot read `$env.staffInput.*` — `[address/staffInputConditionOnly]`
|
|
91
|
+
(a form/rule refuses it as an unknown key). Readers: the `if` node's condition, the
|
|
92
|
+
process-data view, the IDE's „Umgebungsvariablen".
|
|
93
|
+
- **`staff-input set` is idempotent by key:** a second call with the same key relabels in
|
|
94
|
+
place (`--label-en` alone re-translates; `--label` alone is refused while it would leave a
|
|
95
|
+
stale English). `staff-input remove <key>` fails loud on an unknown key, and is refused by
|
|
96
|
+
the gate (`[if/condition]`) while a condition still reads the input.
|
|
97
|
+
- **Publish rules:** `[staffInput/keyFormat]`, `[staffInput/duplicateKey]`,
|
|
98
|
+
`[staffInput/unknownType]` (only `boolean`), `[i18n/missingTranslation]` (label without a
|
|
99
|
+
language), `[if/condition]` (no / unparsable condition, or one naming an undeclared input
|
|
100
|
+
or an unknown `$env` leaf), `[if/unroutedOutcome]` (`true` or `false` goes nowhere),
|
|
101
|
+
`[address/envCohortBound]` (a condition on stage/run is refused).
|
|
102
|
+
- **The `if` node never parks** and brings no board column; it decides once, on entry.
|
|
103
|
+
`--condition` is required and never defaulted; it is refused on any other node type.
|
|
104
|
+
- **Running applications keep their version:** the inputs and the branch apply to
|
|
105
|
+
applications started on the newly published version; already-started ones have the
|
|
106
|
+
inputs of the version they pinned.
|
|
107
|
+
|
|
108
|
+
## Related
|
|
109
|
+
- [Add a workflow node](./add-workflow-node.md) · [Insert a node on one edge and rewire it](./insert-and-rewire-node.md)
|
|
110
|
+
- [Edit a workflow node](./edit-workflow-node.md) (`node set --outcomes`)
|
|
111
|
+
- [Start a test application](../applications/start-a-test-application.md) (`--staff-input`)
|
|
112
|
+
- [Name what a node reads](./addresses.md) (`$env` vocabulary)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Make a task close with an appointment („Termin vereinbaren")
|
|
2
|
+
|
|
3
|
+
**Use it to** turn a workflow-step task such as „Termin für das Kennenlerngespräch
|
|
4
|
+
vereinbaren" into one that is completed by entering an appointment: date,
|
|
5
|
+
time, duration, place and host. Completing it books the appointment at the
|
|
6
|
+
application, mails the family (portal owner) date/time/place with a `termin.ics`
|
|
7
|
+
calendar file, frees the task it
|
|
8
|
+
`blocks`, and lists the appointment under „Anstehende Termine" on the staff dashboard.
|
|
9
|
+
|
|
10
|
+
## The declaration
|
|
11
|
+
|
|
12
|
+
On the task (a composed `kind: "task"` in the node's `tasks`):
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"key": "termin",
|
|
17
|
+
"kind": "task",
|
|
18
|
+
"title": { "de": "Termin für das Kennenlerngespräch vereinbaren", "en": "…" },
|
|
19
|
+
"assign": { "group": "schulverwaltung" },
|
|
20
|
+
"blocks": { "templateKey": "gespraech" },
|
|
21
|
+
"result": {
|
|
22
|
+
"kind": "appointment",
|
|
23
|
+
"title": { "de": "Kennenlerngespräch", "en": "Introductory talk" },
|
|
24
|
+
"durationMinutes": 45
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- `result.title` is the **neutral** title the family's letter and every calendar file
|
|
30
|
+
carry — never a family name.
|
|
31
|
+
- `durationMinutes` (5–480) prefills the dialog; staff may change it per appointment.
|
|
32
|
+
- Publish refuses: an unknown `kind` (`[taskResult/unknownKind]`), a result on an
|
|
33
|
+
approval (`[taskResult/onApproval]`), a title missing `de` or `en`
|
|
34
|
+
(`[taskResult/appointmentMissingTitle]`), a bad duration
|
|
35
|
+
(`[taskResult/appointmentInvalidDuration]`), a stray field
|
|
36
|
+
(`[taskResult/unknownField]`). `offer validate` catches all of them offline.
|
|
37
|
+
|
|
38
|
+
## With the CLI
|
|
39
|
+
|
|
40
|
+
One spelling, the same verb as every other task result — no `:`-separated
|
|
41
|
+
free text, so a title may contain a colon:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
plexus-cli offer workflow node task add --dir $WORK/$S --node kennenlernen --key termin \
|
|
45
|
+
--title 'Termin für das Kennenlerngespräch vereinbaren' --team schulverwaltung --due-in-days 3 \
|
|
46
|
+
--blocks gespraech \
|
|
47
|
+
--result appointment --duration 45 \
|
|
48
|
+
--result-title 'Kennenlerngespräch' --result-title-en 'Introductory talk'
|
|
49
|
+
# change later: node task set … --duration 60 / --result-title '…'
|
|
50
|
+
plexus-cli offer workflow node list --dir $WORK/$S
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then the usual live-tenant path ([Edit a live tenant's offer](./edit-live-tenant-offer.md)):
|
|
54
|
+
`offer validate` → `--env prod offer plan` → `--env prod offer apply --allow-prod --publish` → plan
|
|
55
|
+
again until every row is `same`.
|
|
56
|
+
|
|
57
|
+
## Gotchas
|
|
58
|
+
|
|
59
|
+
- **Running applications keep their pinned graph.** An application already sitting on
|
|
60
|
+
the node with an open `termin` row keeps its old declaration until it is re-pinned;
|
|
61
|
+
new entries into the node get „Termin vereinbaren" in the action bar.
|
|
62
|
+
- **Staff books it from the action bar** („Termin vereinbaren"), like every task; the
|
|
63
|
+
dialog names every missing field, refuses a slot more than two years ahead, and
|
|
64
|
+
closes with a toast. In the Testmodus the shortcut „<Aufgabe> (Test): Termin morgen
|
|
65
|
+
10:00" books through the same verb.
|
|
66
|
+
- **No portal owner, no mail.** An application staff created has no portal account; the
|
|
67
|
+
appointment is saved and the toast warns „Keine Mail an die Familie …". The same
|
|
68
|
+
happens when the school's outgoing mail is off.
|
|
69
|
+
- The zone is the tenant's (`Europe/Berlin` for every DACH tenant today).
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Let a task ask the family and wait for its answer („Nachforderung")
|
|
2
|
+
|
|
3
|
+
**Use it to** turn „Nachforderung: auf Unterlagen der Familie warten" from a task the
|
|
4
|
+
team closes by hand into one that asks the family itself and completes itself the
|
|
5
|
+
moment the family sends. The family gets the field in its task „Offene Felder beantworten" in the portal
|
|
6
|
+
(the task letter `applicant.tasksOpen`, the task's deadline, a reminder before it); the
|
|
7
|
+
team's task waits and holds the decision until then. Nobody ticks it off.
|
|
8
|
+
|
|
9
|
+
## The declaration
|
|
10
|
+
|
|
11
|
+
On a task that a `spawnTasks` optional action references (and only there; at most
|
|
12
|
+
one such task per action):
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"key": "unterlagenAnfordern",
|
|
17
|
+
"kind": "task",
|
|
18
|
+
"title": { "de": "Nachforderung: auf Unterlagen der Familie warten", "en": "Follow-up: waiting for the family's documents" },
|
|
19
|
+
"assign": { "group": "bewerbungsteam" },
|
|
20
|
+
"params": { "dueInDays": 7 },
|
|
21
|
+
"familyRequest": {
|
|
22
|
+
"fields": [{ "scope": "node", "owner": "form", "key": "admission", "path": "kind.weitereUnterlagen" }]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- **What is asked is chosen at the click.** Pressing the action opens a dialog: the
|
|
28
|
+
presser ticks the fields and writes the text the family reads. The text becomes the
|
|
29
|
+
note at each asked field — in the portal and in the task letter.
|
|
30
|
+
- Exactly one of `fields` (the fields offered, each a full address
|
|
31
|
+
`$node.<formNode>.admission.<field>` — one form) or `from` (the form instance
|
|
32
|
+
`$node.<formNode>.admission`: every file field (`docRef`) of that form is offered). There
|
|
33
|
+
is no default form: an empty `familyRequest` is refused. A **free upload** is a `docRef` field
|
|
34
|
+
with `docRefConstraints: { multiple: true }` („Weitere Unterlagen") — the field must
|
|
35
|
+
exist in the application's form version.
|
|
36
|
+
- `params.dueInDays` is the family's deadline and the task's.
|
|
37
|
+
- Put the action into the decision's `requiresOneOf` if the decision should wait for it.
|
|
38
|
+
|
|
39
|
+
## CLI
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
plexus-cli offer workflow node task set --dir <dir> --node decision --key unterlagenAnfordern \
|
|
43
|
+
--due-in-days 7 \
|
|
44
|
+
--family-request-fields '$node.form.admission.kind.weitereUnterlagen'
|
|
45
|
+
# or --family-request-from '$node.form.admission': every file field of that form
|
|
46
|
+
plexus-cli offer validate --dir <dir>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Remove with `--clear-family-request`. The task must not carry `result`, `when`,
|
|
50
|
+
`locksEvidence` or `blocks` (and nothing may block it) — `offer validate` names each.
|
|
51
|
+
|
|
52
|
+
## Verify
|
|
53
|
+
|
|
54
|
+
1. Cockpit: press the action → the dialog offers the fields; tick one, write the text,
|
|
55
|
+
„Anfordern" → the task shows the clock (system-closed), the decision buttons read
|
|
56
|
+
„Wartet auf: …".
|
|
57
|
+
2. Portal `/e`: „Offene Felder beantworten" (field under „Nachzureichen"), the text at the field, „bis <Datum>".
|
|
58
|
+
3. Upload and send as the family → the task is `done`, the decision buttons are free.
|
|
59
|
+
|
|
60
|
+
## Gotchas
|
|
61
|
+
|
|
62
|
+
- A field already under an open request, hidden, or printed into a held document is not
|
|
63
|
+
offered; the trigger refuses it with the reason, and nothing is spawned.
|
|
64
|
+
- An empty or unchanged answer answers nothing (the same file again is no new document):
|
|
65
|
+
the field stays asked.
|
|
66
|
+
- Withdrawing the request („Zurückziehen") cancels the task; the decision stays held by
|
|
67
|
+
`requiresOneOf` until another action completes.
|
|
68
|
+
- Rows of applications on an OLDER offer version keep the old task; only applications
|
|
69
|
+
pinned to the published version get the family request.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Let a task's result carry files
|
|
2
|
+
|
|
3
|
+
**Use it to** let the person who completes a task attach files to their answer. Example: the
|
|
4
|
+
teacher's trial-day feedback with an English and a maths placement sheet, as PDF or photo.
|
|
5
|
+
|
|
6
|
+
## The model
|
|
7
|
+
|
|
8
|
+
- **One switch on the task:** `result.files: true`. It is allowed only on prose results:
|
|
9
|
+
`freetext`, `recommendation`, or a `choice` without `writes`. Publish refuses it anywhere
|
|
10
|
+
else (`[taskResult/filesOnKind]`).
|
|
11
|
+
- **Text and files are ONE message** in the chat the result names (`result.chat`). The row
|
|
12
|
+
keeps no text, and the chat keeps, shows and erases the files (GDPR erasure included).
|
|
13
|
+
- **Up to 20 files per result**.
|
|
14
|
+
A normal chat message takes 5. Each file may be PDF, JPEG, PNG or WebP of up to 25 MB;
|
|
15
|
+
an iPhone HEIC photo is converted to JPEG in the browser.
|
|
16
|
+
- **Where the files show:** in the result's message — cockpit → Verlauf/Aktivitäten (the
|
|
17
|
+
chat's tab); a click opens the file in the viewer. The task itself shows at most how
|
|
18
|
+
many files its result carries. Never in the „Dokumente" rail:
|
|
19
|
+
that lists the application's documents, not task attachments. Nothing is
|
|
20
|
+
copied, so deleting the message removes its files.
|
|
21
|
+
- **Who sees them:** whoever reads the feedback text — the SAME chat rule. In a
|
|
22
|
+
`team:<group>` chat that means the members of that group plus platform admins. A
|
|
23
|
+
`tenant_admin` who is in no such group sees neither the text nor the files. Before
|
|
24
|
+
choosing `team:principals`, check that the readers really hold that group (`plexus-cli
|
|
25
|
+
users show <email>`).
|
|
26
|
+
|
|
27
|
+
## CLI
|
|
28
|
+
|
|
29
|
+
Work in a scratch dir for a real tenant
|
|
30
|
+
([edit-live-tenant-offer.md](./edit-live-tenant-offer.md)).
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
plexus-cli --env prod --tenant <slug> offer export --slug <offer> --out <dir>
|
|
34
|
+
plexus-cli offer workflow node task set --dir <dir> --node decision --key tasterFeedback --files
|
|
35
|
+
plexus-cli offer validate --dir <dir>
|
|
36
|
+
plexus-cli --env prod --tenant <slug> offer plan --dir <dir> # workflow: change
|
|
37
|
+
# apply only with the school's go (the process owner):
|
|
38
|
+
plexus-cli --env prod --tenant <slug> offer apply --dir <dir> --publish --allow-prod
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
A new task: `node task add … --result freetext --chat <chat> --files`.
|
|
42
|
+
|
|
43
|
+
## Verify
|
|
44
|
+
|
|
45
|
+
1. Open the task in the cockpit. The dialog shows „Dateien (optional)" with the paperclip,
|
|
46
|
+
which accepts several files at once.
|
|
47
|
+
2. Complete it with two files. Verlauf, tab of the result's chat: one message with the
|
|
48
|
+
text and both files.
|
|
49
|
+
3. „Dokumente": neither file is listed there.
|
|
50
|
+
4. As staff outside the chat's team: neither the text nor the files show in Verlauf.
|
|
51
|
+
|
|
52
|
+
Test tenant: Grundschule → „Probetag auswerten" (Schulleitung) takes files.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Generate a letter from a Word template (governedDocument `production: 'template'`)
|
|
2
|
+
|
|
3
|
+
**Use it to** let a workflow document node (Platzangebot, Bestätigung, Bescheinigung) fill a tenant Word
|
|
4
|
+
template and render the PDF automatically — no resolver, no deploy.
|
|
5
|
+
|
|
6
|
+
## Prerequisites
|
|
7
|
+
|
|
8
|
+
- The Word template is uploaded in the tenant's template module (Vorlagen). Its placeholders use
|
|
9
|
+
`<< input.<feld> >>` (sections `<<#input.<feld>>> … <</input.<feld>>>`).
|
|
10
|
+
- **Exactly one language per letter (required):** every template node declares a
|
|
11
|
+
required enum field (options `de`, `en`), names it in `template.localeField`, binds it to
|
|
12
|
+
`$env.applicant.language` (the applicant's language — the default) and wraps each language's
|
|
13
|
+
text in a section `<<#input.<feld>.de>> … <</input.<feld>.de>>` /
|
|
14
|
+
`<<#input.<feld>.en>> … <</input.<feld>.en>>`. Exactly one section renders, and `input.*` prints
|
|
15
|
+
the way that language writes it (numbers, dates, Ja/Yes, prices, names). A node without
|
|
16
|
+
`localeField` is refused at publish (`[governedDocument/localeFieldMissing]`); a missing section
|
|
17
|
+
blocks publish and file replacement. The English twin `en.input.*` of the old fixed bilingual
|
|
18
|
+
letters is gone — a file naming it is `[governedDocument/unknownPlaceholder]`. A German-only
|
|
19
|
+
school declares one option `de` and binds the field to the fixed value `de`.
|
|
20
|
+
- **Switching a LIVE template to language sections — upload it under a NEW key.** A file
|
|
21
|
+
replacement is checked against EVERY published node naming the key, archived versions included
|
|
22
|
+
(`publishedNodesFillingTemplate` — they still carry running applications), and a node published
|
|
23
|
+
without `localeField` accepts no file at all. So the old key stays with the old versions for
|
|
24
|
+
good; the sectioned file goes in as a new template:
|
|
25
|
+
1. upload the sectioned Word file as a new template and give it a new key (§ 1, e.g.
|
|
26
|
+
`platzangebot-v2`);
|
|
27
|
+
2. in the exported bundle point `template.key` at it, add the field `sprache` to
|
|
28
|
+
`template.fields`, set `template.localeField: 'sprache'` and bind it:
|
|
29
|
+
`plexus-cli offer workflow node set-input --dir <bundle> --node <id> --input sprache --address '$env.applicant.language'`
|
|
30
|
+
([Bind a node input](./bind-node-input.md));
|
|
31
|
+
3. `offer validate` → `plan` → `apply --publish`.
|
|
32
|
+
Applications pinned to the old version produce no letter any more (the mask names
|
|
33
|
+
`localeFieldMissing`); a re-pin onto the new version is refused, because the new binding changes
|
|
34
|
+
the process topology — restart such a (test) application on the new version.
|
|
35
|
+
- The deployment already runs a release that knows template nodes (validators first, then
|
|
36
|
+
publish); if publish refuses the node type, that needs a platform operator.
|
|
37
|
+
|
|
38
|
+
## 1. Give the template its stable key
|
|
39
|
+
|
|
40
|
+
There is no UI field yet — set it through the public mutation (the same `updateMetadata` the UI calls,
|
|
41
|
+
so name / role / file-name pattern must be passed as they are):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
plexus-cli call templates:list --env staging client=<slug> --json # id, name, roleId
|
|
45
|
+
plexus-cli call templates:updateMetadata --kind mutation --env staging client=<slug> \
|
|
46
|
+
templateId=<id> name='Platzangebot' roleId=<roleId> configKey=platzangebot --json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The key is unique per tenant and cannot move while a published process names it.
|
|
50
|
+
|
|
51
|
+
## 2. Author the node in the bundle (JSON)
|
|
52
|
+
|
|
53
|
+
`plexus-cli offer workflow node add` cannot create a governedDocument node; edit `workflow.json`
|
|
54
|
+
(export → edit → plan → apply, see [Edit a live tenant's offer](./edit-live-tenant-offer.md)):
|
|
55
|
+
|
|
56
|
+
```jsonc
|
|
57
|
+
{
|
|
58
|
+
"id": "platzangebot", "type": "governedDocument", "label": "Platzangebot",
|
|
59
|
+
"production": "template", "approval": "release", "responseStrategy": "confirm",
|
|
60
|
+
"responseDueInDays": 14,
|
|
61
|
+
"template": {
|
|
62
|
+
"key": "platzangebot",
|
|
63
|
+
"fields": [
|
|
64
|
+
{ "key": "kind_vorname", "label": "Vorname des Kindes", "type": "string", "required": true },
|
|
65
|
+
{ "key": "datum", "label": "Datum", "type": "date", "processSource": "generatedAt" },
|
|
66
|
+
{ "key": "antwortfrist", "label": "Antwortfrist", "type": "number", "processSource": "responseDueInDays" },
|
|
67
|
+
{ "key": "sprache", "label": { "de": "Sprache des Schreibens", "en": "Language of the letter" },
|
|
68
|
+
"type": "enum", "required": true,
|
|
69
|
+
"options": [
|
|
70
|
+
{ "value": "de", "label": { "de": "Deutsch", "en": "German" } },
|
|
71
|
+
{ "value": "en", "label": { "de": "Englisch", "en": "English" } }
|
|
72
|
+
] }
|
|
73
|
+
],
|
|
74
|
+
"cockpitFields": [],
|
|
75
|
+
"localeField": "sprache"
|
|
76
|
+
},
|
|
77
|
+
"inputs": {
|
|
78
|
+
"kind_vorname": {
|
|
79
|
+
"kind": "binding",
|
|
80
|
+
"address": { "scope": "node", "owner": "form", "key": "admission", "path": "kind.vorname" }
|
|
81
|
+
},
|
|
82
|
+
"sprache": { "kind": "binding", "address": { "scope": "env", "key": "applicant.language" } }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
A bound form field must be exported by the form (`export: true`) and is named by its full address
|
|
88
|
+
(`$node.form.admission.kind.vorname` — bind it with
|
|
89
|
+
`plexus-cli offer workflow node set-input --node platzangebot --input kind_vorname --from form --field kind.vorname`);
|
|
90
|
+
a form that does not run before the node is `[address/notVisible]`, a field it does not export
|
|
91
|
+
`[address/pathNotInTarget]`. An old bundle (`wires`, a binding with `path`) is refused
|
|
92
|
+
— export it again. The deadline alone can be changed
|
|
93
|
+
with `plexus-cli offer workflow node set --id platzangebot --response-due-days 14` (`""` removes it).
|
|
94
|
+
|
|
95
|
+
## 3. Validate, plan, publish
|
|
96
|
+
|
|
97
|
+
`plexus-cli offer validate <dir>` checks the node shape offline (including the language field,
|
|
98
|
+
`[governedDocument/localeFieldMissing]`). **Publish** additionally refuses a
|
|
99
|
+
key no template carries (`[governedDocument/unknownTemplate]`) and any placeholder the fields cannot
|
|
100
|
+
fill (`[governedDocument/unknownPlaceholder]`).
|
|
101
|
+
|
|
102
|
+
## Verify
|
|
103
|
+
|
|
104
|
+
An application entering the node gets a run in `contract_generation_runs` (producer: the template)
|
|
105
|
+
and a PDF version on the node's slot within seconds; the cockpit's „Überarbeiten" opens the mask.
|
|
106
|
+
|
|
107
|
+
## Gotchas
|
|
108
|
+
|
|
109
|
+
- Generation is automatic only while the producer row is open and unblocked; after „Freigabe
|
|
110
|
+
anfordern" nothing regenerates. A changed template file is caught at submit („nicht mehr aktuell").
|
|
111
|
+
- Replacing the template file is refused while a published node could not fill its placeholders.
|
|
112
|
+
- **CLI gap:** the `template` pin (key, fields, cockpitFields) has no `offer workflow node` verb yet —
|
|
113
|
+
JSON only.
|