@opencxh/domain 1.160.0 → 1.162.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/dist/entities/assignment/types.d.ts +17 -12
- package/dist/entities/custom-field-def/types.d.ts +29 -2
- package/dist/entities/time-entry/duration.d.ts +22 -0
- package/dist/entities/time-entry/duration.test.d.ts +1 -0
- package/dist/entities/time-entry/index.d.ts +3 -0
- package/dist/entities/time-entry/types.d.ts +176 -0
- package/dist/entities/time-entry/work-type.d.ts +44 -0
- package/dist/entities/time-entry/work-type.test.d.ts +1 -0
- package/dist/entities/work/index.d.ts +3 -0
- package/dist/entities/work/keys.d.ts +57 -0
- package/dist/entities/work/keys.test.d.ts +1 -0
- package/dist/entities/work/ladder.d.ts +97 -0
- package/dist/entities/work/ladder.test.d.ts +1 -0
- package/dist/entities/work/types.d.ts +255 -0
- package/dist/index.cjs +8 -8
- package/dist/index.d.ts +2 -0
- package/dist/index.js +929 -776
- package/dist/platform/scope.d.ts +19 -0
- package/package.json +1 -1
|
@@ -106,22 +106,27 @@ export interface Assignment {
|
|
|
106
106
|
/**
|
|
107
107
|
* Wekt deze activity een opdracht die op een antwoord wacht?
|
|
108
108
|
*
|
|
109
|
-
* **
|
|
109
|
+
* **Vier regels, van hard naar zacht.**
|
|
110
110
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
111
|
+
* 1. **De klant antwoordt** (`inbound`): wekt altijd. Een klant kan niemand @-noemen, en zijn
|
|
112
|
+
* antwoord is per definitie het antwoord waar de opdracht op wachtte.
|
|
113
|
+
* 2. **De agent zelf schreef dit**: nooit. Zonder deze poort is de vraag die de agent stelt het
|
|
114
|
+
* signaal waarop hij wakker wordt — een lus die pas bij het beurtplafond stopt.
|
|
115
|
+
* 3. **De agent is genoemd**: wekt, ongeacht waarop hij wacht. Een vermelding is de expliciete
|
|
116
|
+
* vraag die de engine zelf niet kan beantwoorden: *is dit aan míjn agent gericht?*
|
|
117
|
+
* 4. **Een interne notitie terwijl hij op een antwoord wacht** (`waitingOn === "reply"`): wekt.
|
|
114
118
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
119
|
+
* Regel 4 is nieuw en vervangt "alleen als hij genoemd is" voor dit ene geval. **Als een agent
|
|
120
|
+
* expliciet om een antwoord heeft gevraagd, ís het volgende wat een collega schrijft dat antwoord** —
|
|
121
|
+
* dan iemand dwingen zijn eigen agent te @-noemen op de vraag die die agent net zelf stelde is een
|
|
122
|
+
* ritueel, niet een signaal. De oude vrees ("twee collega's die overleggen kosten elk een beurt")
|
|
123
|
+
* blijft gedekt door de rand eromheen: `waitingOn` moet `"reply"` zijn, en zodra hij op een taak
|
|
124
|
+
* wacht of nog bezig is, is de vermelding weer de enige weg naar binnen.
|
|
118
125
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* geraden zou stil het verkeerde doen. En het maakt zelf-wekken onmogelijk: een agent noemt
|
|
122
|
-
* zichzelf niet.
|
|
126
|
+
* @param waitingOn Waarop de opdracht wacht. Weggelaten = alleen de regels 1-3 (het gedrag van
|
|
127
|
+
* vóór regel 4), zodat een beller die het niet weet nooit te ruim wekt.
|
|
123
128
|
*/
|
|
124
|
-
export declare function wakesAssignment(activity: Activity, agentId: string): boolean;
|
|
129
|
+
export declare function wakesAssignment(activity: Activity, agentId: string, waitingOn?: WaitingOn["on"]): boolean;
|
|
125
130
|
/** Het onderwerp van een opdracht als één verwijzing, of `undefined`. */
|
|
126
131
|
export declare function assignmentSubject(assignment: {
|
|
127
132
|
subjectKind?: string;
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* De soorten aangepaste velden.
|
|
3
|
+
*
|
|
4
|
+
* Begon als `text | select`, want dat was alles wat een gesprek nodig had. Werkitems vragen
|
|
5
|
+
* er meer: een dealwaarde moet als bedrag getoond worden, een doorlooptijd als duur, en een
|
|
6
|
+
* klant als verwijzing naar het bedrijvenregister in plaats van als vrije tekst.
|
|
7
|
+
*
|
|
8
|
+
* Bewust **geen** `multi_select`: {@link CustomFieldDef.multiValued} bestaat al, wordt al
|
|
9
|
+
* opgeslagen en betekent precies dat. Twee manieren om één ding te zeggen levert een
|
|
10
|
+
* renderer op met een tak die niemand onderhoudt.
|
|
11
|
+
*/
|
|
12
|
+
export type CustomFieldType = "text" | "textarea" | "number" | "currency" | "date" | "duration" | "select" | "user" | "checkbox"
|
|
13
|
+
/** Verwijst naar een resource elders in het platform; zie {@link CustomFieldDef.refKinds}. */
|
|
14
|
+
| "resource_ref";
|
|
2
15
|
/** A selectable option for a `select` field. `label` falls back to `value` when absent. */
|
|
3
16
|
export interface CustomFieldOption {
|
|
4
17
|
value: string;
|
|
@@ -21,10 +34,24 @@ export interface CustomFieldDef {
|
|
|
21
34
|
options?: CustomFieldOption[];
|
|
22
35
|
/** `select` only: multi-select — the stored value becomes a string[]. */
|
|
23
36
|
multiValued?: boolean;
|
|
24
|
-
/** Resource kinds this field applies to, e.g. ["interaction"]. */
|
|
37
|
+
/** Resource kinds this field applies to, e.g. ["interaction"], ["work_item"]. */
|
|
25
38
|
appliesTo: string[];
|
|
26
39
|
/** Empty/absent = all teams; otherwise only shown when the resource belongs to one of these teams. */
|
|
27
40
|
teamIds?: string[];
|
|
41
|
+
/**
|
|
42
|
+
* Voor `resource_ref`: welke scope-soorten gekozen mogen worden (`"company"`,
|
|
43
|
+
* `"contact"`, `"work_item"`). Leeg = alles wat de kiezer aanbiedt.
|
|
44
|
+
*/
|
|
45
|
+
refKinds?: string[];
|
|
46
|
+
/**
|
|
47
|
+
* Welke work-projecten dit veld aanzetten. Leeg/afwezig = elk project, net als bij
|
|
48
|
+
* {@link CustomFieldDef.teamIds}.
|
|
49
|
+
*
|
|
50
|
+
* Bestaat omdat een velddefinitie platformbreed is maar een veldenset dat niet is: twee
|
|
51
|
+
* projecten mogen allebei een "Bedrag" kennen met een eigen betekenis. Additief, dus
|
|
52
|
+
* bestaande definities houden een lege lijst en blijven overal gelden.
|
|
53
|
+
*/
|
|
54
|
+
projectIds?: string[];
|
|
28
55
|
/**
|
|
29
56
|
* When true, this field becomes an analytics dimension `comms.cf.<key>`: every conversation-level
|
|
30
57
|
* metric can be grouped/filtered by its value. Opt-in to avoid turning free-text fields into
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { TimeEntry, TimeRounding } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* De verstreken tijd van een regel, in seconden.
|
|
4
|
+
*
|
|
5
|
+
* In `domain` en niet in de app-server, omdat client én server hetzelfde antwoord moeten geven:
|
|
6
|
+
* de server stempelt de duur bij het stoppen, het paneel laat 'm ondertussen elke seconde
|
|
7
|
+
* oplopen. Zou het paneel zelf tellen vanaf een eigen nulpunt, dan loopt de zichtbare klok weg
|
|
8
|
+
* van wat er uiteindelijk geboekt wordt zodra een tabblad even slaapt.
|
|
9
|
+
*/
|
|
10
|
+
export declare function elapsedSeconds(entry: Pick<TimeEntry, "accumulatedSeconds" | "runningSince">, now: number): number;
|
|
11
|
+
/**
|
|
12
|
+
* De geboekte duur die bij een gemeten duur hoort.
|
|
13
|
+
*
|
|
14
|
+
* Naar boven en met een minimum van één eenheid: wie "per 15 minuten" kiest, wil dat een klus
|
|
15
|
+
* van drie minuten een kwartier wordt en niet nul. Bij `exact` is de bodem één minuut, om
|
|
16
|
+
* dezelfde reden — een regel van nul seconden is geen regel.
|
|
17
|
+
*/
|
|
18
|
+
export declare function applyRounding(rawSeconds: number, rounding: TimeRounding | undefined): number;
|
|
19
|
+
/** `0:45` / `2:05` — uren en minuten, zoals de dagtotalen in het paneel. */
|
|
20
|
+
export declare function formatHm(totalSeconds: number): string;
|
|
21
|
+
/** `12:34` onder het uur, `1:02:03` erboven — de lopende klok. */
|
|
22
|
+
export declare function formatClock(totalSeconds: number): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { OwnerScope } from '../contact/types';
|
|
2
|
+
/**
|
|
3
|
+
* Waar een urenregel in zijn leven staat.
|
|
4
|
+
*
|
|
5
|
+
* `review` is met opzet een **server**-staat en niet iets wat alleen in het paneel leeft: het
|
|
6
|
+
* ontwerp laat je na het stoppen nog afronden, een werksoort kiezen en een notitie bijwerken
|
|
7
|
+
* voordat de regel telt. Zat die tussenstap alleen in de client, dan was een dichtgeslagen
|
|
8
|
+
* tabblad precies het moment waarop het gewerkte uur verdampt. Nu vindt het paneel de regel bij
|
|
9
|
+
* het volgende bezoek gewoon terug en staat de review-kaart er weer.
|
|
10
|
+
*
|
|
11
|
+
* `logged` is de enige staat die meetelt in totalen en in de rapportage.
|
|
12
|
+
*/
|
|
13
|
+
export type TimeEntryStatus = "running" | "paused" | "review" | "logged";
|
|
14
|
+
/** Hoe de gemeten tijd naar een boekbare duur wordt vertaald. */
|
|
15
|
+
export type TimeRounding = "exact" | "15" | "30";
|
|
16
|
+
/** Handmatig ingevoerd of daadwerkelijk geklokt — zichtbaar in de rapportage. */
|
|
17
|
+
export type TimeEntrySource = "timer" | "manual";
|
|
18
|
+
export interface TimeEntry {
|
|
19
|
+
id: string;
|
|
20
|
+
organizationId: string;
|
|
21
|
+
/** Wie de tijd schreef. Een regel hoort altijd bij precies één mens. */
|
|
22
|
+
userId: string;
|
|
23
|
+
/**
|
|
24
|
+
* Waarop geboekt wordt: `"interaction:abc"`, `"task:42"`, `"company:xyz"`. Afwezig =
|
|
25
|
+
* algemeen werk zonder object.
|
|
26
|
+
*
|
|
27
|
+
* Bewust een opake sleutel en geen `interactionId`: de app die de soort bezit beantwoordt
|
|
28
|
+
* de toegangsvraag (`assertScopeAccess`), dus urenregistratie hoeft geen enkele andere app
|
|
29
|
+
* te kennen om er tijd op te kunnen schrijven.
|
|
30
|
+
*/
|
|
31
|
+
scopeKey?: string;
|
|
32
|
+
/** Menselijk etiket op schrijfmoment ("Gesprek · RE: EYLO"), zodat een lijst leesbaar blijft
|
|
33
|
+
* zonder per regel de bron-app te bevragen. */
|
|
34
|
+
scopeLabel?: string;
|
|
35
|
+
/** Dossiersleutels van de resource, platgeslagen bij schrijven — zie de `keys`-conventie. */
|
|
36
|
+
keys?: string[];
|
|
37
|
+
/** Eigenaarschap zoals de bron-app het ziet; bepaalt wie de regel in een teamweergave ziet. */
|
|
38
|
+
ownerScope?: OwnerScope;
|
|
39
|
+
status: TimeEntryStatus;
|
|
40
|
+
/** Epoch ms, gezet door de server. De client rekent alleen af hoe laat het nu is. */
|
|
41
|
+
startedAt: number;
|
|
42
|
+
/** Epoch ms; gezet zodra de timer stopt. Afwezig zolang hij loopt of gepauzeerd staat. */
|
|
43
|
+
endedAt?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Seconden die vóór het huidige segment al gebankt zijn.
|
|
46
|
+
*
|
|
47
|
+
* Pauzeren mag geen tweede rij opleveren — het ontwerp belooft één regel per klus, met een
|
|
48
|
+
* pauzeknop erin. Daarom telt de server bij elke pauze het gelopen segment hierbij op en
|
|
49
|
+
* laat `runningSince` los; hervatten zet alleen `runningSince` opnieuw. De verstreken tijd
|
|
50
|
+
* is dus altijd `accumulatedSeconds + (runningSince ? now - runningSince : 0)`, en dat
|
|
51
|
+
* antwoord is hetzelfde op elk apparaat.
|
|
52
|
+
*/
|
|
53
|
+
accumulatedSeconds: number;
|
|
54
|
+
/** Epoch ms waarop het lopende segment begon. Afwezig = gepauzeerd of gestopt. */
|
|
55
|
+
runningSince?: number;
|
|
56
|
+
/** Wat de klok werkelijk mat. Blijft staan als afronding de geboekte duur optrekt. */
|
|
57
|
+
rawSeconds?: number;
|
|
58
|
+
/**
|
|
59
|
+
* De geboekte duur in seconden — dít telt in totalen en facturatie.
|
|
60
|
+
*
|
|
61
|
+
* Los van `rawSeconds` omdat afronding een keuze van de gebruiker is en geen meting: wie
|
|
62
|
+
* later vraagt "waar komt dat kwartier vandaan" moet de gemeten twee minuten nog kunnen zien.
|
|
63
|
+
*/
|
|
64
|
+
durationSeconds?: number;
|
|
65
|
+
rounding?: TimeRounding;
|
|
66
|
+
billable: boolean;
|
|
67
|
+
/** Verwijst naar `WorkType.key`. */
|
|
68
|
+
workType?: string;
|
|
69
|
+
note?: string;
|
|
70
|
+
source: TimeEntrySource;
|
|
71
|
+
createdAt?: number;
|
|
72
|
+
updatedAt?: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Een beheerbare werksoort in plaats van een rijtje in code: welke soorten een organisatie
|
|
76
|
+
* kent verschilt per bedrijf, en de soort bepaalt of iets standaard declarabel is.
|
|
77
|
+
*/
|
|
78
|
+
export interface WorkType {
|
|
79
|
+
id: string;
|
|
80
|
+
organizationId: string;
|
|
81
|
+
/**
|
|
82
|
+
* Stabiele sleutel die op de urenregel belandt.
|
|
83
|
+
*
|
|
84
|
+
* Uniek binnen de organisatie en niet binnen de scope, hoewel de soort een team kan
|
|
85
|
+
* toebehoren: `TimeEntry.workType` bewaart alléén deze sleutel en de rapportage groepeert
|
|
86
|
+
* erop. Twee teams met elk een eigen `installatie` zouden daar als één regel uitkomen, met
|
|
87
|
+
* twee betekenissen erin.
|
|
88
|
+
*/
|
|
89
|
+
key: string;
|
|
90
|
+
label: string;
|
|
91
|
+
/** Standaardwaarde voor `TimeEntry.billable`; per regel te overrulen. */
|
|
92
|
+
defaultBillable: boolean;
|
|
93
|
+
/** Meegenomen als dimensie in de rapportage. */
|
|
94
|
+
reportable?: boolean;
|
|
95
|
+
order?: number;
|
|
96
|
+
archived?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Van wie deze soort is: de hele organisatie of één team. Bepaalt wie hem in zijn
|
|
99
|
+
* urenpaneel ziet en wie hem mag beheren.
|
|
100
|
+
*
|
|
101
|
+
* Geen `personal`, om dezelfde reden als bij `Topic`: een soort die maar één mens kent,
|
|
102
|
+
* maakt het geboekte uur onleesbaar voor de collega die de week overneemt. Afwezig telt als
|
|
103
|
+
* org-breed — dat is wat elke soort vóór dit veld feitelijk was.
|
|
104
|
+
*/
|
|
105
|
+
ownerScope?: Extract<OwnerScope, {
|
|
106
|
+
kind: "org";
|
|
107
|
+
} | {
|
|
108
|
+
kind: "team";
|
|
109
|
+
}>;
|
|
110
|
+
createdAt?: number;
|
|
111
|
+
updatedAt?: number;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Wat het beheerscherm naar de server stuurt.
|
|
115
|
+
*
|
|
116
|
+
* Zonder `id` is het een nieuwe soort. `key` staat er met opzet niet in: die leidt de server af
|
|
117
|
+
* uit de naam en ligt daarna vast, omdat geboekte uren ernaar verwijzen.
|
|
118
|
+
*/
|
|
119
|
+
export interface SaveWorkTypeRequest {
|
|
120
|
+
id?: string;
|
|
121
|
+
label: string;
|
|
122
|
+
defaultBillable?: boolean;
|
|
123
|
+
reportable?: boolean;
|
|
124
|
+
order?: number;
|
|
125
|
+
archived?: boolean;
|
|
126
|
+
ownerScope?: WorkType["ownerScope"];
|
|
127
|
+
}
|
|
128
|
+
/** Een kandidaat waarop de gebruiker kan klokken, aangeboden in de leegstaat van het paneel. */
|
|
129
|
+
export interface TimeTarget {
|
|
130
|
+
scopeKey?: string;
|
|
131
|
+
/** Soortlabel boven de titel ("GESPREK OP JE SCHERM", "TAAK"). */
|
|
132
|
+
kindLabel: string;
|
|
133
|
+
title: string;
|
|
134
|
+
/** Lucide-icoonnaam. */
|
|
135
|
+
icon: string;
|
|
136
|
+
keys?: string[];
|
|
137
|
+
}
|
|
138
|
+
export interface StartTimerRequest {
|
|
139
|
+
scopeKey?: string;
|
|
140
|
+
scopeLabel?: string;
|
|
141
|
+
keys?: string[];
|
|
142
|
+
workType?: string;
|
|
143
|
+
note?: string;
|
|
144
|
+
}
|
|
145
|
+
/** Wat er bij het opslaan van een regel nog aan te passen valt. */
|
|
146
|
+
export interface SaveTimeEntryRequest {
|
|
147
|
+
id: string;
|
|
148
|
+
rounding?: TimeRounding;
|
|
149
|
+
workType?: string;
|
|
150
|
+
billable?: boolean;
|
|
151
|
+
note?: string;
|
|
152
|
+
/** Naar een ander object boeken dan waar de timer op startte. */
|
|
153
|
+
scopeKey?: string;
|
|
154
|
+
scopeLabel?: string;
|
|
155
|
+
keys?: string[];
|
|
156
|
+
}
|
|
157
|
+
export interface ManualTimeEntryRequest {
|
|
158
|
+
scopeKey?: string;
|
|
159
|
+
scopeLabel?: string;
|
|
160
|
+
keys?: string[];
|
|
161
|
+
/** Epoch ms; de dag waarop de regel valt. */
|
|
162
|
+
startedAt: number;
|
|
163
|
+
durationSeconds: number;
|
|
164
|
+
workType?: string;
|
|
165
|
+
billable?: boolean;
|
|
166
|
+
note?: string;
|
|
167
|
+
}
|
|
168
|
+
/** Wat het paneel in één keer ophaalt: de lopende timer plus de dag eromheen. */
|
|
169
|
+
export interface TimeDaySummary {
|
|
170
|
+
/** De regel die nu loopt, gepauzeerd staat of op afronding wacht — er is er hooguit één. */
|
|
171
|
+
active?: TimeEntry;
|
|
172
|
+
/** Afgeronde regels van de opgevraagde dag, oplopend op starttijd. */
|
|
173
|
+
entries: TimeEntry[];
|
|
174
|
+
totalSeconds: number;
|
|
175
|
+
billableSeconds: number;
|
|
176
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { WorkType } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* De sleutel die bij een naam hoort: kleine letters, streepjes, geen accenten.
|
|
4
|
+
*
|
|
5
|
+
* In `domain` en niet in de app-server omdat het beheerscherm hem al tijdens het typen laat
|
|
6
|
+
* zien — wat de gebruiker daar ziet moet exact zijn wat de server straks wegschrijft, anders
|
|
7
|
+
* belooft het formulier `installatie-op-locatie` en staat er `installatieoplocatie` in de
|
|
8
|
+
* rapportage.
|
|
9
|
+
*
|
|
10
|
+
* `normalize("NFD")` plus het weghalen van de combineertekens, zodat "Béta" `beta` wordt en niet
|
|
11
|
+
* `b-ta`: een leesbare sleutel is het halve doel.
|
|
12
|
+
*/
|
|
13
|
+
export declare function workTypeKey(label: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Dezelfde sleutel, maar gegarandeerd nog vrij binnen de organisatie.
|
|
16
|
+
*
|
|
17
|
+
* Botsingen zijn echt: de sleutel is org-uniek terwijl soorten per team beheerd worden, dus twee
|
|
18
|
+
* teams die allebei "Installatie" aanmaken komen hier uit. De tweede krijgt `installatie-2` in
|
|
19
|
+
* plaats van een foutmelding — een beheerder die een naam kiest, hoort niet te struikelen over
|
|
20
|
+
* een sleutel die hij nooit heeft gezien.
|
|
21
|
+
*
|
|
22
|
+
* `taken` bevat óók gearchiveerde sleutels: die staan nog op geboekte uren, dus hergebruiken zou
|
|
23
|
+
* oud werk stilzwijgend onder een nieuwe soort schuiven.
|
|
24
|
+
*/
|
|
25
|
+
export declare function uniqueWorkTypeKey(label: string, taken: Iterable<string>): string;
|
|
26
|
+
/**
|
|
27
|
+
* Ziet deze gebruiker deze soort?
|
|
28
|
+
*
|
|
29
|
+
* Een soort zonder scope is org-breed: rijen zijn ouder dan het veld, en ze wegfilteren zou een
|
|
30
|
+
* bestaande organisatie zijn hele lijst kosten.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isWorkTypeVisible(workType: Pick<WorkType, "ownerScope">, teamIds: string[]): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Wat het paneel toont: niet-gearchiveerd, in de juiste volgorde.
|
|
35
|
+
*
|
|
36
|
+
* Zonder team-argument, en dat is met opzet: de server heeft de teamzichtbaarheid al afgekapt
|
|
37
|
+
* voordat de lijst de client bereikt (dat is een rechtenvraag). Zou de client hier nóg eens op
|
|
38
|
+
* `teamIds` filteren, dan moest hij het lidmaatschap van de gebruiker kennen — en met een lege
|
|
39
|
+
* lijst, zoals hij hem nu heeft, zou hij precies de teamsoorten wegfilteren die de server net
|
|
40
|
+
* heeft goedgekeurd.
|
|
41
|
+
*/
|
|
42
|
+
export declare function activeWorkTypes(workTypes: WorkType[]): WorkType[];
|
|
43
|
+
/** De volgorde van de chips: `order` eerst, gelijke waarden alfabetisch zodat hij stabiel is. */
|
|
44
|
+
export declare function compareWorkTypes(a: WorkType, b: WorkType): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { WorkItem, WorkProject } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Sleutels: de scopeKeys waarmee andere apps werk aanwijzen, en de menselijke itemsleutel.
|
|
4
|
+
*
|
|
5
|
+
* Beide staan in `domain` en niet in de app, omdat het contracten zijn: de client bouwt een
|
|
6
|
+
* scopeKey om een timer te starten, de server autoriseert hem, en twee plekken die het
|
|
7
|
+
* voorvoegsel los uitschrijven lopen bij de eerste typefout stil uiteen — met "geen toegang
|
|
8
|
+
* tot deze scope" als gevolg in plaats van een foutmelding die zegt wat er mis is.
|
|
9
|
+
*/
|
|
10
|
+
/** De scope-soorten die `apps/work` claimt via `/provider/scope/describe`. */
|
|
11
|
+
export declare const WORK_ITEM_SCOPE_KIND = "work_item";
|
|
12
|
+
export declare const WORK_PROJECT_SCOPE_KIND = "work_project";
|
|
13
|
+
export declare function workItemScopeKey(itemId: string): string;
|
|
14
|
+
export declare function workProjectScopeKey(projectId: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* De menselijke sleutel: `SAL-142`, of `SAL-142-1` voor een subitem.
|
|
17
|
+
*
|
|
18
|
+
* Alleen voor items mét project — een los item heeft er geen, en dat is geen omissie maar het
|
|
19
|
+
* verschil tussen "werk dat een plek in een proces heeft" en "iets wat ik nog moet doen".
|
|
20
|
+
*/
|
|
21
|
+
export declare function formatItemKey(projectKey: string, sequenceNumber: number, subSequence?: number): string;
|
|
22
|
+
/** Wat er in een `SAL-142-1` zit, of `null` als het geen itemsleutel is. */
|
|
23
|
+
export interface ParsedItemKey {
|
|
24
|
+
projectKey: string;
|
|
25
|
+
sequenceNumber: number;
|
|
26
|
+
subSequence?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Leest een sleutel die iemand heeft ingetypt of geplakt.
|
|
30
|
+
*
|
|
31
|
+
* Hoofdletterongevoelig aan de invoerkant en genormaliseerd aan de uitvoerkant, want mensen
|
|
32
|
+
* typen `sal-142` in een zoekveld en verwachten hun item. Het projectvoorvoegsel is
|
|
33
|
+
* `[A-Z0-9]{2,8}`, wat een streepje uitsluit — anders zou `SAL-142-1` net zo goed te lezen
|
|
34
|
+
* zijn als project `SAL-142`, item `1`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseItemKey(input: string): ParsedItemKey | null;
|
|
37
|
+
/** De projectsleutel die bij een naam hoort: `"Sales pipeline"` → `"SAL"`. */
|
|
38
|
+
export declare function suggestProjectKey(name: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* De dossiersleutels van een item: waarmee uren, geheugen en attributen het terugvinden.
|
|
41
|
+
*
|
|
42
|
+
* **Klein houden.** Dit is de set van de rij zelf en niet zijn hele omgeving — deze sleutels
|
|
43
|
+
* rijden mee op elke autorisatie, inclusief elke geheugen-schrijfactie, dus de kinderen van
|
|
44
|
+
* dit item horen er niet in. Dezelfde afweging die `companyDossierKeys` maakt tegenover
|
|
45
|
+
* `keySetForCompany` in crm.
|
|
46
|
+
*
|
|
47
|
+
* De harde bovengrens is er voor het pathologische geval: een item met tweehonderd partijen
|
|
48
|
+
* mag geen array van tweehonderd sleutels op elke autorisatie leggen.
|
|
49
|
+
*/
|
|
50
|
+
export declare function itemDossierKeys(item: Pick<WorkItem, "id" | "projectId" | "ancestorKeys" | "partyKeys">, limit?: number): string[];
|
|
51
|
+
/**
|
|
52
|
+
* De dossiersleutels van een project: alleen zichzelf.
|
|
53
|
+
*
|
|
54
|
+
* Een project heeft onbegrensd veel items, dus zijn kinderen meegeven zou een onbegrensd
|
|
55
|
+
* array op elke autorisatie leggen. Wie de items wil, vraagt ernaar.
|
|
56
|
+
*/
|
|
57
|
+
export declare function projectDossierKeys(project: Pick<WorkProject, "id">): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { WorkProject, WorkStatus, WorkStatusCategory } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* De statusladder — de enige plek die weet of een item een project heeft.
|
|
4
|
+
*
|
|
5
|
+
* `WorkItem.projectId` is optioneel, en zonder discipline betekent dat een `if (projectId)`
|
|
6
|
+
* in elke lijst, elke teller en elke statuskiezer. Alles wat "welke statussen kan dit item
|
|
7
|
+
* hebben" of "is dit open" vraagt gaat daarom door {@link ladderFor} en {@link categoryOf},
|
|
8
|
+
* en nergens anders.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* De vaste ladder voor werk zónder project.
|
|
12
|
+
*
|
|
13
|
+
* Deze drie sleutels zijn **gereserveerd**: een project mag ze niet claimen, want dan zou
|
|
14
|
+
* dezelfde sleutel in twee ladders een andere categorie kunnen hebben en werd de
|
|
15
|
+
* org-brede "wat staat er open"-query stil onjuist. `validateStatuses` bewaakt dat.
|
|
16
|
+
*
|
|
17
|
+
* De labels zijn **i18n-sleutels, geen tekst** — een letterlijke string hier zou de taal op
|
|
18
|
+
* moduleniveau vastzetten voor elke consument, en dat is precies wat de i18n-regel in
|
|
19
|
+
* `DESIGN_SYSTEM.md` §11 verbiedt. Ze staan zonder app-voorvoegsel omdat de app-sdk dat er
|
|
20
|
+
* bij het opzoeken zelf voor zet; een `work:`-voorvoegsel hier zou in de eigen app
|
|
21
|
+
* verdubbelen tot `work:work:status_open`.
|
|
22
|
+
*
|
|
23
|
+
* Een projectstatus heeft géén sleutel maar een naam die een beheerder heeft ingetypt. Alleen
|
|
24
|
+
* deze drie zijn vertaalbaar, want alleen deze zijn van ons.
|
|
25
|
+
*/
|
|
26
|
+
export declare const LOOSE_STATUSES: readonly WorkStatus[];
|
|
27
|
+
/** De sleutels die {@link LOOSE_STATUSES} bezet houdt. */
|
|
28
|
+
export declare const RESERVED_STATUS_KEYS: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* De ladder die voor dit item geldt.
|
|
31
|
+
*
|
|
32
|
+
* Een project zónder statussen valt terug op de vaste ladder in plaats van een lege lijst op
|
|
33
|
+
* te leveren: een statuskiezer met nul opties is een doodlopende weg, en een half aangemaakt
|
|
34
|
+
* project hoort niet onbruikbaar te zijn.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ladderFor(project?: WorkProject | null): readonly WorkStatus[];
|
|
37
|
+
/**
|
|
38
|
+
* De categorie van een statussleutel binnen een ladder.
|
|
39
|
+
*
|
|
40
|
+
* Valt terug op `"todo"` bij een onbekende sleutel, en dat is de veilige kant: een item dat
|
|
41
|
+
* door een verwijderde status wees zou anders als afgerond uit elke telling verdwijnen. Beter
|
|
42
|
+
* zichtbaar op de verkeerde plek dan onzichtbaar.
|
|
43
|
+
*/
|
|
44
|
+
export declare function categoryOf(statusKey: string, ladder: readonly WorkStatus[]): WorkStatusCategory;
|
|
45
|
+
/** De status zelf, als de ladder hem kent. */
|
|
46
|
+
export declare function statusIn(statusKey: string, ladder: readonly WorkStatus[]): WorkStatus | undefined;
|
|
47
|
+
/** Waar een nieuw item begint: de expliciete keuze van het project, anders de eerste stap. */
|
|
48
|
+
export declare function defaultStatusKey(project?: WorkProject | null): string;
|
|
49
|
+
/** `order` eerst, gelijke waarden op label zodat de volgorde stabiel is. */
|
|
50
|
+
export declare function sortStatuses(ladder: readonly WorkStatus[]): WorkStatus[];
|
|
51
|
+
/**
|
|
52
|
+
* Is dit item afgerond? De enige juiste manier om die vraag te stellen.
|
|
53
|
+
*
|
|
54
|
+
* `isWorkClosed` en niet `isClosed`, want `entities/interaction` exporteert die naam al voor
|
|
55
|
+
* een gesprek. Twee `isClosed`en in één barrel is precies de verwarring waar dit veld niet
|
|
56
|
+
* op zit te wachten.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isWorkClosed(statusKey: string, ladder: readonly WorkStatus[]): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* De statussleutel die bij een naam hoort: kleine letters, streepjes, geen accenten.
|
|
61
|
+
*
|
|
62
|
+
* Dezelfde vorm en dezelfde reden als `workTypeKey` bij de werksoorten: het beheerscherm
|
|
63
|
+
* toont hem al tijdens het typen, dus wat de gebruiker daar ziet moet precies zijn wat de
|
|
64
|
+
* server wegschrijft. `normalize("NFD")` plus het weghalen van de combineertekens, zodat
|
|
65
|
+
* "Béta" `beta` wordt en niet `b-ta`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function workStatusKey(label: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Dezelfde sleutel, gegarandeerd nog vrij binnen de organisatie.
|
|
70
|
+
*
|
|
71
|
+
* Botsingen zijn hier de regel en niet de uitzondering: sleutels zijn org-uniek terwijl
|
|
72
|
+
* statussen per project beheerd worden, dus twee projecten die allebei een "Review" aanmaken
|
|
73
|
+
* komen hier uit. De tweede krijgt `review-2` in plaats van een foutmelding — een beheerder
|
|
74
|
+
* die een naam kiest hoort niet te struikelen over een sleutel die hij nooit heeft gezien.
|
|
75
|
+
*
|
|
76
|
+
* `taken` moet óók de sleutels van verwijderde statussen bevatten zolang er items naar
|
|
77
|
+
* wijzen, en altijd {@link RESERVED_STATUS_KEYS}.
|
|
78
|
+
*/
|
|
79
|
+
export declare function uniqueWorkStatusKey(label: string, taken: Iterable<string>): string;
|
|
80
|
+
/** Wat er mis is met een voorgestelde ladder, als leesbare redenen. */
|
|
81
|
+
export interface StatusLadderProblem {
|
|
82
|
+
/** Index in de aangeleverde lijst, of `-1` als het de lijst als geheel betreft. */
|
|
83
|
+
index: number;
|
|
84
|
+
reason: "empty" | "duplicate_key" | "reserved_key" | "missing_key" | "no_open" | "no_done";
|
|
85
|
+
key?: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Valideert een ladder vóór hij wordt opgeslagen.
|
|
89
|
+
*
|
|
90
|
+
* Bestaat omdat de store hier niets bewaakt: `statuses` is één ingebed array, dus uniciteit
|
|
91
|
+
* en volledigheid zijn de verantwoordelijkheid van de schrijver. Puur en zonder I/O, zodat
|
|
92
|
+
* het beheerscherm dezelfde regels kan tonen als de server afdwingt.
|
|
93
|
+
*
|
|
94
|
+
* De twee laatste regels zijn geen smaak: een ladder zonder open-categorie maakt elk nieuw
|
|
95
|
+
* item meteen afgerond, en een ladder zonder `done` maakt afronden onmogelijk.
|
|
96
|
+
*/
|
|
97
|
+
export declare function validateStatuses(statuses: readonly WorkStatus[]): StatusLadderProblem[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|