@topconsultnpm/sdkui-react 6.22.0-dev2.2 → 6.22.0-dev2.21
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/lib/components/NewComponents/ContextMenu/TMContextMenu.js +1 -1
- package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +1 -1
- package/lib/components/base/TMButton.d.ts +13 -0
- package/lib/components/base/TMButton.js +137 -5
- package/lib/components/base/TMDataGrid.d.ts +2 -0
- package/lib/components/base/TMDataGrid.js +68 -4
- package/lib/components/base/TMEditorBase.d.ts +2 -0
- package/lib/components/base/TMModal.d.ts +4 -0
- package/lib/components/base/TMModal.js +42 -11
- package/lib/components/base/TMPanel.d.ts +1 -0
- package/lib/components/base/TMPanel.js +79 -20
- package/lib/components/base/TMTooltip.d.ts +2 -1
- package/lib/components/base/TMTooltip.js +23 -6
- package/lib/components/base/TMTreeView.js +108 -67
- package/lib/components/choosers/TMDcmtTypeChooser.js +4 -3
- package/lib/components/choosers/TMDistinctValues.d.ts +9 -7
- package/lib/components/choosers/TMDistinctValues.js +149 -142
- package/lib/components/choosers/TMMetadataChooser.js +1 -1
- package/lib/components/choosers/TMQuickSearchInfo.d.ts +12 -0
- package/lib/components/choosers/TMQuickSearchInfo.js +279 -0
- package/lib/components/choosers/TMQuickSearchSettingsForm.d.ts +16 -0
- package/lib/components/choosers/TMQuickSearchSettingsForm.js +94 -0
- package/lib/components/choosers/TMSelectedValuesSummary.d.ts +13 -0
- package/lib/components/choosers/TMSelectedValuesSummary.js +91 -0
- package/lib/components/editors/TMDropDown.d.ts +3 -0
- package/lib/components/editors/TMDropDown.js +197 -5
- package/lib/components/editors/TMMetadataEditor.d.ts +5 -1
- package/lib/components/editors/TMMetadataEditor.js +8 -3
- package/lib/components/editors/TMTextBox.js +15 -15
- package/lib/components/features/documents/TMBatchUpdateForm.js +19 -5
- package/lib/components/features/documents/TMDcmtForm.d.ts +7 -0
- package/lib/components/features/documents/TMDcmtForm.js +104 -42
- package/lib/components/features/documents/TMDcmtFormActionButtons.d.ts +11 -0
- package/lib/components/features/documents/TMDcmtFormActionButtons.js +74 -7
- package/lib/components/features/documents/TMMasterDetailDcmts.js +95 -33
- package/lib/components/features/documents/TMMasterInfoFields.d.ts +19 -0
- package/lib/components/features/documents/TMMasterInfoFields.js +172 -0
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.d.ts +40 -0
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +277 -0
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +354 -0
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +234 -0
- package/lib/components/features/documents/TMRelationViewer.d.ts +14 -1
- package/lib/components/features/documents/TMRelationViewer.js +69 -20
- package/lib/components/features/search/TMSearch.js +29 -16
- package/lib/components/features/search/TMSearchQueryEditor.js +5 -3
- package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -1
- package/lib/components/features/search/TMSearchQueryPanel.js +28 -7
- package/lib/components/features/search/TMSearchResult.d.ts +9 -0
- package/lib/components/features/search/TMSearchResult.js +323 -92
- package/lib/components/features/search/TMTreeSelector.js +18 -5
- package/lib/components/features/workflow/TMWorkflowPopup.d.ts +10 -0
- package/lib/components/features/workflow/TMWorkflowPopup.js +8 -4
- package/lib/components/layout/panelManager/TMPanelManagerContainer.d.ts +1 -0
- package/lib/components/layout/panelManager/TMPanelManagerContainer.js +24 -14
- package/lib/components/layout/panelManager/TMPanelWrapper.js +2 -2
- package/lib/components/layout/panelManager/types.d.ts +3 -0
- package/lib/components/viewers/TMMidViewer.js +4 -1
- package/lib/components/viewers/TMTidViewer.js +25 -11
- package/lib/helper/SDKUI_Globals.d.ts +55 -0
- package/lib/helper/SDKUI_Globals.js +82 -0
- package/lib/helper/SDKUI_Localizator.d.ts +24 -0
- package/lib/helper/SDKUI_Localizator.js +240 -0
- package/lib/helper/TMIcons.d.ts +1 -0
- package/lib/helper/TMIcons.js +3 -0
- package/lib/helper/dataGridSearchHelper.d.ts +28 -0
- package/lib/helper/dataGridSearchHelper.js +51 -0
- package/lib/helper/dcmtsHelper.d.ts +20 -0
- package/lib/helper/dcmtsHelper.js +58 -1
- package/lib/helper/helpers.d.ts +20 -0
- package/lib/helper/helpers.js +40 -0
- package/lib/helper/index.d.ts +1 -0
- package/lib/helper/index.js +1 -0
- package/lib/helper/queryHelper.d.ts +2 -0
- package/lib/helper/queryHelper.js +3 -1
- package/lib/hooks/tmDistinctValuesGridHelper.d.ts +16 -0
- package/lib/hooks/tmDistinctValuesGridHelper.js +31 -0
- package/lib/hooks/useArchiveListForm.d.ts +39 -0
- package/lib/hooks/useArchiveListForm.js +46 -0
- package/lib/hooks/useCaseFlowApprove.d.ts +56 -0
- package/lib/hooks/useCaseFlowApprove.js +157 -0
- package/lib/hooks/useDataUserIdItem.d.ts +1 -0
- package/lib/hooks/useDataUserIdItem.js +1 -0
- package/lib/hooks/useDocumentOperations.d.ts +2 -0
- package/lib/hooks/useDocumentOperations.js +28 -5
- package/lib/hooks/useMultiMasterDetailDcmts.d.ts +98 -0
- package/lib/hooks/useMultiMasterDetailDcmts.js +567 -0
- package/lib/hooks/useRelatedDocuments.js +2 -26
- package/lib/hooks/useTMDistinctValuesMetadataDisplay.d.ts +18 -0
- package/lib/hooks/useTMDistinctValuesMetadataDisplay.js +103 -0
- package/lib/hooks/useTMDistinctValuesQuickSearch.d.ts +49 -0
- package/lib/hooks/useTMDistinctValuesQuickSearch.js +307 -0
- package/lib/hooks/useTMDistinctValuesSelection.d.ts +42 -0
- package/lib/hooks/useTMDistinctValuesSelection.js +103 -0
- package/lib/hooks/useTMDistinctValuesSource.d.ts +29 -0
- package/lib/hooks/useTMDistinctValuesSource.js +105 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/services/caseflow/CaseFlowService.d.ts +236 -0
- package/lib/services/caseflow/CaseFlowService.js +693 -0
- package/lib/services/caseflow/CaseFlowTemplateCacheService.d.ts +28 -0
- package/lib/services/caseflow/CaseFlowTemplateCacheService.js +121 -0
- package/lib/services/caseflow/caseFlowNameCache.d.ts +32 -0
- package/lib/services/caseflow/caseFlowNameCache.js +116 -0
- package/lib/services/caseflow/caseFlowWorkItemUtils.d.ts +61 -0
- package/lib/services/caseflow/caseFlowWorkItemUtils.js +113 -0
- package/lib/services/index.d.ts +4 -0
- package/lib/services/index.js +4 -0
- package/lib/services/platform_services.d.ts +1 -1
- package/package.json +66 -66
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
import { SDK_Globals, BlogPost, CaseFlowDcmt, CaseFlowInstanceDescriptor, CaseFlowParticipant, CaseFlowWIOperationDescriptor, MetadataValueDescriptor, CF_ParticipantRoles, CF_States, CF_WIOperations, CF_WorkItemCategories, CF_WorkItemStates, UserListCacheService, DcmtTypeListCacheService, } from '@topconsultnpm/sdk-ts';
|
|
2
|
+
import { CaseFlowTemplateCacheService } from './CaseFlowTemplateCacheService';
|
|
3
|
+
import { getTemplateNameSync, primeTemplateName, primeTemplateNames, primeActionNames } from './caseFlowNameCache';
|
|
4
|
+
export class CaseFlowService {
|
|
5
|
+
// ----- Template / tipi documento (invariati: cache condivise) ------------
|
|
6
|
+
/** Elenco dei template reali (via CaseFlowTemplateCacheService). */
|
|
7
|
+
static async getTemplates() {
|
|
8
|
+
const templates = await CaseFlowTemplateCacheService.GetAllAsync();
|
|
9
|
+
// Popola la cache dei nomi template, così i componenti possono risolvere
|
|
10
|
+
// `templateName` dal `cftid` senza augmentare i descrittori del SDK.
|
|
11
|
+
primeTemplateNames(templates);
|
|
12
|
+
return templates;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Singolo template per id. Usa GetAsync (non un find su GetAllAsync): così i
|
|
16
|
+
* template di cui l'utente NON può creare istanze — ma di cui è destinatario
|
|
17
|
+
* di un workitem (es. Completamento dati) — vengono risolti on-demand via
|
|
18
|
+
* RetrieveAsync mirata invece di restituire undefined.
|
|
19
|
+
*/
|
|
20
|
+
static async getTemplate(id) {
|
|
21
|
+
const template = await CaseFlowTemplateCacheService.GetAsync(id);
|
|
22
|
+
if (template)
|
|
23
|
+
primeTemplateNames([template]);
|
|
24
|
+
return template;
|
|
25
|
+
}
|
|
26
|
+
/** Invalida la cache dei template (dopo creazione/modifica/eliminazione). */
|
|
27
|
+
static invalidateTemplatesCache() {
|
|
28
|
+
CaseFlowTemplateCacheService.Invalidate();
|
|
29
|
+
}
|
|
30
|
+
/** DcmtTypeDescriptor (copertina) per il TID indicato. */
|
|
31
|
+
static async getDcmtTypeAsync(tid) {
|
|
32
|
+
try {
|
|
33
|
+
return await DcmtTypeListCacheService.GetAsync(tid, true);
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
console.warn(`[CaseFlow] DcmtType ${tid} non disponibile:`, e);
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// ----- Lettura istanze ---------------------------------------------------
|
|
41
|
+
/** Tutte le istanze visibili all'utente (unione di tutti i template). */
|
|
42
|
+
static async getCaseflows() {
|
|
43
|
+
return this.loadAllInstances();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Singola istanza COMPLETA per id. `RetrieveAllAsync` (usata dalla dashboard)
|
|
47
|
+
* porta i dati necessari alle card, ma per il dettaglio completo
|
|
48
|
+
* (documenti/bacheca/timeline) serve comunque una `RetrieveAsync(cftid, id)`
|
|
49
|
+
* che idrata l'istanza. Il `cftid` va passato quando noto (evita una
|
|
50
|
+
* scansione); in mancanza viene risolto dalla lista.
|
|
51
|
+
*
|
|
52
|
+
* ATTENZIONE sui `workItems` della RetrieveAll: contengono i soli workitem
|
|
53
|
+
* ANCORA APERTI, non lo storico. Una pratica conclusa arriva con
|
|
54
|
+
* `workItems: []`, perché non ha più nulla di pendente. Chi deve ricostruire
|
|
55
|
+
* la STORIA di una pratica (chi ha fatto cosa e quando) non può quindi basarsi
|
|
56
|
+
* sull'elenco: serve la Retrieve completa, e la fonte dei passaggi già
|
|
57
|
+
* avvenuti sono i messaggi di sistema della bacheca (`blogPosts` con
|
|
58
|
+
* allegato di riferimento all'azione).
|
|
59
|
+
*/
|
|
60
|
+
static async getCaseflow(id, cftid) {
|
|
61
|
+
try {
|
|
62
|
+
return await this.retrieveCaseflow(id, cftid);
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
console.warn(`[CaseFlow] RetrieveAsync istanza id=${id} (cftid=${cftid}) fallita:`, e);
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Come {@link getCaseflow} ma PROPAGA l'errore invece di inghiottirlo.
|
|
71
|
+
* Da usare nei punti in cui l'utente apre esplicitamente l'istanza (apertura
|
|
72
|
+
* scheda, deep link): il fallimento della Retrieve deve essere notificato,
|
|
73
|
+
* non mascherato da una vista incompleta ("non sei un partecipante").
|
|
74
|
+
*/
|
|
75
|
+
static async retrieveCaseflow(id, cftid) {
|
|
76
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
77
|
+
if (!engine)
|
|
78
|
+
return undefined;
|
|
79
|
+
let resolvedCftid = cftid;
|
|
80
|
+
if (resolvedCftid == null) {
|
|
81
|
+
const all = await this.loadAllInstances();
|
|
82
|
+
resolvedCftid = all.find(cf => cf.id === id)?.cftid;
|
|
83
|
+
}
|
|
84
|
+
if (resolvedCftid == null) {
|
|
85
|
+
console.warn(`[CaseFlow] impossibile risolvere il cftid per l'istanza id=${id}.`);
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const full = await engine.RetrieveAsync(resolvedCftid, id);
|
|
89
|
+
if (!full)
|
|
90
|
+
return undefined;
|
|
91
|
+
if (full.cftid == null)
|
|
92
|
+
full.cftid = resolvedCftid;
|
|
93
|
+
// Risolvi il template (best-effort, on-demand se non creabile dall'utente)
|
|
94
|
+
// per l'etichetta del modello e i nomi azione.
|
|
95
|
+
const tpl = await CaseFlowTemplateCacheService.GetAsync(full.cftid);
|
|
96
|
+
if (tpl) {
|
|
97
|
+
primeTemplateName(tpl.id, tpl.name);
|
|
98
|
+
primeActionNames(tpl);
|
|
99
|
+
}
|
|
100
|
+
await this.resolveUserNames([full]);
|
|
101
|
+
return full;
|
|
102
|
+
}
|
|
103
|
+
/** Istanze di cui l'utente indicato è proprietario. */
|
|
104
|
+
static async getMyCaseflows(userId) {
|
|
105
|
+
const all = await this.loadAllInstances();
|
|
106
|
+
return all.filter(cf => cf.ownerID === userId);
|
|
107
|
+
}
|
|
108
|
+
/** Ricerca avanzata sulle istanze applicando i filtri indicati. */
|
|
109
|
+
static async searchCaseflows(criteria) {
|
|
110
|
+
const all = await this.loadAllInstances();
|
|
111
|
+
const lowerIncludes = (haystack, needle) => (haystack ?? '').toLowerCase().includes(needle.toLowerCase());
|
|
112
|
+
return all.filter(cf => {
|
|
113
|
+
if (criteria.templateId != null && cf.cftid !== criteria.templateId)
|
|
114
|
+
return false;
|
|
115
|
+
if (criteria.name && !lowerIncludes(cf.name, criteria.name))
|
|
116
|
+
return false;
|
|
117
|
+
if (criteria.description && !lowerIncludes(cf.description, criteria.description))
|
|
118
|
+
return false;
|
|
119
|
+
if (criteria.state && cf.state !== criteria.state)
|
|
120
|
+
return false;
|
|
121
|
+
if (criteria.dateFrom) {
|
|
122
|
+
const from = new Date(criteria.dateFrom);
|
|
123
|
+
if (cf.creationTime && cf.creationTime < from)
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
if (criteria.dateTo) {
|
|
127
|
+
const to = new Date(criteria.dateTo);
|
|
128
|
+
if (cf.creationTime && cf.creationTime > to)
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
if (criteria.metadataFilters && criteria.metadataFilters.length > 0) {
|
|
132
|
+
for (const mf of criteria.metadataFilters) {
|
|
133
|
+
if (!mf.value.trim())
|
|
134
|
+
continue;
|
|
135
|
+
const cfMeta = (cf.cover ?? []).find(m => m.mid === mf.mid);
|
|
136
|
+
if (!cfMeta || !lowerIncludes(cfMeta.value, mf.value))
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (criteria.freeTextMetadata && criteria.freeTextMetadata.trim()) {
|
|
141
|
+
const needle = criteria.freeTextMetadata.trim();
|
|
142
|
+
const matchesCommon = lowerIncludes(cf.name, needle)
|
|
143
|
+
|| lowerIncludes(cf.description, needle)
|
|
144
|
+
|| lowerIncludes(cf.ownerName, needle)
|
|
145
|
+
|| lowerIncludes(getTemplateNameSync(cf.cftid), needle);
|
|
146
|
+
const matchesMeta = (cf.cover ?? []).some(m => lowerIncludes(m.value, needle) || lowerIncludes(m.metadataName, needle));
|
|
147
|
+
if (!matchesCommon && !matchesMeta)
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/** WorkItem pendenti assegnati all'utente, raggruppati per istanza. */
|
|
154
|
+
static async getMyPendingWorkItems(userId) {
|
|
155
|
+
return this.collectWorkItemsByState(userId, CF_WorkItemStates.Pending);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Richieste di "maggiori informazioni" INVIATE dall'utente e ancora in
|
|
159
|
+
* attesa di risposta, raggruppate per istanza.
|
|
160
|
+
*
|
|
161
|
+
* Non basta filtrare `toUserID === userId && state === MoreInfo` (il solo
|
|
162
|
+
* workitem "padre" bloccato intestato a me): a seconda di come il backend
|
|
163
|
+
* materializza la richiesta, l'unico workitem visibile può essere quello
|
|
164
|
+
* INVIATO al destinatario (`fromUserID === userId`, categoria MoreInfo,
|
|
165
|
+
* ancora Pending). In quel caso il filtro su `toUserID` non lo intercetta e
|
|
166
|
+
* l'istanza sparisce da TUTTE le sezioni di "Per te" (non è più Pending per
|
|
167
|
+
* me, non è MoreInfo intestato a me, e non ne sono owner). Consideriamo
|
|
168
|
+
* quindi entrambe le rappresentazioni, preferendo il figlio inviato (che
|
|
169
|
+
* porta il destinatario) ed evitando di mostrare padre + figlio della
|
|
170
|
+
* stessa richiesta.
|
|
171
|
+
*/
|
|
172
|
+
static async getMyMoreInfoRequests(userId) {
|
|
173
|
+
const all = await this.loadAllInstances();
|
|
174
|
+
const result = [];
|
|
175
|
+
for (const cf of all) {
|
|
176
|
+
const wis = cf.workItems ?? [];
|
|
177
|
+
// Richieste che HO inviato e attendono risposta da un altro utente.
|
|
178
|
+
const sent = wis.filter(wi => wi.fromUserID === userId
|
|
179
|
+
&& wi.workItemCategory === CF_WorkItemCategories.MoreInfo
|
|
180
|
+
&& wi.state === CF_WorkItemStates.Pending);
|
|
181
|
+
// Mia attività messa "in attesa" (workitem padre intestato a me).
|
|
182
|
+
const waitingParents = wis.filter(wi => wi.toUserID === userId && wi.state === CF_WorkItemStates.MoreInfo);
|
|
183
|
+
const parentsWithoutChild = waitingParents.filter(p => !sent.some(s => s.parentWorkItemID === p.id));
|
|
184
|
+
const workItems = [...sent, ...parentsWithoutChild];
|
|
185
|
+
if (workItems.length > 0)
|
|
186
|
+
result.push({ caseflow: cf, workItems });
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
static async collectWorkItemsByState(userId, state) {
|
|
191
|
+
const all = await this.loadAllInstances();
|
|
192
|
+
const result = [];
|
|
193
|
+
for (const cf of all) {
|
|
194
|
+
const workItems = (cf.workItems ?? []).filter(wi => wi.toUserID === userId && wi.state === state);
|
|
195
|
+
if (workItems.length > 0)
|
|
196
|
+
result.push({ caseflow: cf, workItems });
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
// ----- Creazione / eliminazione / aggiornamento istanza ------------------
|
|
201
|
+
/**
|
|
202
|
+
* Crea una nuova istanza a partire da un template tramite CreateAsync.
|
|
203
|
+
* I metadati di copertina (metadataValues: mid -> valore) vengono passati
|
|
204
|
+
* nel campo `cover`. Il backend materializza azioni/stati/partecipanti dal
|
|
205
|
+
* template identificato da `cftid`.
|
|
206
|
+
* @returns id della nuova istanza.
|
|
207
|
+
*/
|
|
208
|
+
static async CreateCaseFlowInstance(template, instanceName, instanceDescription, metadataValues) {
|
|
209
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
210
|
+
if (!engine)
|
|
211
|
+
throw new Error('Motore delle istanze CaseFlow non disponibile.');
|
|
212
|
+
if (template.id == null)
|
|
213
|
+
throw new Error('Template privo di id (cftid) per la creazione dell\'istanza.');
|
|
214
|
+
const d = new CaseFlowInstanceDescriptor();
|
|
215
|
+
d.cftid = template.id;
|
|
216
|
+
d.name = instanceName;
|
|
217
|
+
d.description = instanceDescription ?? '';
|
|
218
|
+
d.state = CF_States.New;
|
|
219
|
+
d.progress = 0;
|
|
220
|
+
d.cover = Object.entries(metadataValues).map(([mid, value]) => MetadataValueDescriptor.fromJS({ mid: Number(mid), value }));
|
|
221
|
+
const id = await engine.CreateAsync(d);
|
|
222
|
+
return id ?? 0;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Elimina un'istanza tramite DeleteAsync(cftid, id). Il cftid è preso dal
|
|
226
|
+
* template fornito (se disponibile) oppure risolto dall'istanza.
|
|
227
|
+
*/
|
|
228
|
+
static async DeleteCaseFlowInstance(instanceId, template) {
|
|
229
|
+
if (!instanceId)
|
|
230
|
+
throw new Error('instanceId non valido per la cancellazione.');
|
|
231
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
232
|
+
if (!engine)
|
|
233
|
+
throw new Error('Motore delle istanze CaseFlow non disponibile.');
|
|
234
|
+
const cftid = template?.id ?? (await this.getCaseflow(instanceId))?.cftid;
|
|
235
|
+
if (cftid == null)
|
|
236
|
+
throw new Error(`Impossibile risolvere il template (cftid) per l'istanza id=${instanceId}.`);
|
|
237
|
+
await engine.DeleteAsync(cftid, instanceId);
|
|
238
|
+
return { instanceDID: instanceId, complete: true, orphaned: [] };
|
|
239
|
+
}
|
|
240
|
+
/** Aggiorna un'istanza esistente tramite UpdateAsync. */
|
|
241
|
+
static async saveCaseflow(caseflow) {
|
|
242
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
243
|
+
if (!engine)
|
|
244
|
+
throw new Error('Motore delle istanze CaseFlow non disponibile.');
|
|
245
|
+
await engine.UpdateAsync(caseflow);
|
|
246
|
+
}
|
|
247
|
+
// ----- Bacheca (blog dell'istanza) ---------------------------------------
|
|
248
|
+
/**
|
|
249
|
+
* Aggiunge un post in bacheca tramite BlogPostAddAsync(cftid, id, bp). Il
|
|
250
|
+
* cftid è passato quando noto (evita una Retrieve) oppure risolto dall'istanza.
|
|
251
|
+
* @returns il post creato (transitorio: la ricarica leggerà quelli reali).
|
|
252
|
+
*/
|
|
253
|
+
static async addBlogPost(caseFlowId, text, authorId, authorName, attachments, cftid) {
|
|
254
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
255
|
+
if (!engine)
|
|
256
|
+
throw new Error('Motore delle istanze CaseFlow non disponibile.');
|
|
257
|
+
const resolvedCftid = cftid ?? (await this.getCaseflow(caseFlowId))?.cftid;
|
|
258
|
+
if (resolvedCftid == null)
|
|
259
|
+
throw new Error(`Impossibile risolvere il template (cftid) per l'istanza id=${caseFlowId}.`);
|
|
260
|
+
const attachmentsJson = attachments?.map(a => a.toJSON()) ?? [];
|
|
261
|
+
const bp = BlogPost.fromJS({
|
|
262
|
+
ownerID: authorId,
|
|
263
|
+
ownerName: authorName,
|
|
264
|
+
description: text,
|
|
265
|
+
isDel: 0,
|
|
266
|
+
isSys: 0,
|
|
267
|
+
attachments: attachmentsJson,
|
|
268
|
+
});
|
|
269
|
+
const newId = await engine.BlogPostAddAsync(resolvedCftid, caseFlowId, bp);
|
|
270
|
+
// Post transitorio: rispecchia quanto inviato in attesa della ricarica.
|
|
271
|
+
return BlogPost.fromJS({
|
|
272
|
+
id: newId ?? 0,
|
|
273
|
+
ownerID: authorId,
|
|
274
|
+
ownerName: authorName,
|
|
275
|
+
description: text,
|
|
276
|
+
creationTime: new Date().toISOString(),
|
|
277
|
+
isDel: 0,
|
|
278
|
+
isSys: 0,
|
|
279
|
+
attachments: attachmentsJson,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Elimina (o ripristina) un post in bacheca tramite
|
|
284
|
+
* BlogPostDeleteOrUndeleteAsync(cftid, id, postID, del).
|
|
285
|
+
*/
|
|
286
|
+
static async deleteOrUndeleteBlogPost(caseFlowId, postId, del, cftid) {
|
|
287
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
288
|
+
if (!engine)
|
|
289
|
+
throw new Error('Motore delle istanze CaseFlow non disponibile.');
|
|
290
|
+
const resolvedCftid = cftid ?? (await this.getCaseflow(caseFlowId))?.cftid;
|
|
291
|
+
if (resolvedCftid == null)
|
|
292
|
+
throw new Error(`Impossibile risolvere il template (cftid) per l'istanza id=${caseFlowId}.`);
|
|
293
|
+
await engine.BlogPostDeleteOrUndeleteAsync(resolvedCftid, caseFlowId, postId, del);
|
|
294
|
+
}
|
|
295
|
+
// ----- Documenti della pratica --------------------------------------------
|
|
296
|
+
//
|
|
297
|
+
// Come per i partecipanti, l'engine espone UNA sola API,
|
|
298
|
+
// DcmtAddOrRemoveAsync(cftid, id, tid, did, remove): con remove=false aggancia
|
|
299
|
+
// il documento all'istanza, con remove=true lo sgancia.
|
|
300
|
+
/**
|
|
301
|
+
* Aggancia un documento (nuovo archiviato o già presente in archivio)
|
|
302
|
+
* all'istanza tramite DcmtAddOrRemoveAsync(remove=false).
|
|
303
|
+
* @returns descrittore transitorio del documento agganciato (la ricarica
|
|
304
|
+
* dell'istanza leggerà quello reale).
|
|
305
|
+
*/
|
|
306
|
+
static async addDcmtToInstance(caseFlowId, tid, did, cftid) {
|
|
307
|
+
await this.dcmtAddOrRemove(caseFlowId, tid, did, false, cftid);
|
|
308
|
+
return CaseFlowDcmt.fromJS({ tid, did, creationTime: new Date().toISOString() });
|
|
309
|
+
}
|
|
310
|
+
/** Sgancia un documento dall'istanza tramite DcmtAddOrRemoveAsync(remove=true). */
|
|
311
|
+
static async removeDcmtFromInstance(caseFlowId, tid, did, cftid) {
|
|
312
|
+
await this.dcmtAddOrRemove(caseFlowId, tid, did, true, cftid);
|
|
313
|
+
}
|
|
314
|
+
// ----- Regole documenti attesi dell'istanza (dcmtRules) -------------------
|
|
315
|
+
//
|
|
316
|
+
// Finché l'istanza non ha regole proprie (dcmtRules assente o vuota) i
|
|
317
|
+
// documenti attesi — con min e max — sono i dcmts del template. Un
|
|
318
|
+
// supervisor può personalizzarle per la SOLA istanza tramite l'unica API
|
|
319
|
+
// DcmtRuleAddOrRemoveAsync(cftid, id, tid, min, max, remove): con
|
|
320
|
+
// remove=false funge da UPSERT della regola per il TID, con remove=true la
|
|
321
|
+
// rimuove. Rimuovendo tutte le regole l'istanza torna a seguire il template.
|
|
322
|
+
/** Aggiunge o aggiorna (upsert) la regola documenti del TID sulla sola istanza. */
|
|
323
|
+
static async saveDcmtRule(caseFlowId, tid, min, max, cftid) {
|
|
324
|
+
await this.dcmtRuleAddOrRemove(caseFlowId, tid, min, max, false, cftid);
|
|
325
|
+
}
|
|
326
|
+
/** Rimuove la regola documenti del TID dalla sola istanza. */
|
|
327
|
+
static async removeDcmtRule(caseFlowId, tid, cftid) {
|
|
328
|
+
await this.dcmtRuleAddOrRemove(caseFlowId, tid, 0, 0, true, cftid);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Chiama DcmtRuleAddOrRemoveAsync(cftid, id, tid, min, max, remove)
|
|
332
|
+
* risolvendo il cftid quando non passato (come per bacheca e partecipanti).
|
|
333
|
+
*/
|
|
334
|
+
static async dcmtRuleAddOrRemove(caseFlowId, tid, min, max, remove, cftid) {
|
|
335
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
336
|
+
if (!engine)
|
|
337
|
+
throw new Error('Motore delle istanze CaseFlow non disponibile.');
|
|
338
|
+
const resolvedCftid = cftid ?? (await this.getCaseflow(caseFlowId))?.cftid;
|
|
339
|
+
if (resolvedCftid == null)
|
|
340
|
+
throw new Error(`Impossibile risolvere il template (cftid) per l'istanza id=${caseFlowId}.`);
|
|
341
|
+
await engine.DcmtRuleAddOrRemoveAsync(resolvedCftid, caseFlowId, tid, min, max, remove);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Chiama DcmtAddOrRemoveAsync(cftid, id, tid, did, remove) risolvendo il
|
|
345
|
+
* cftid quando non passato (come per bacheca e partecipanti).
|
|
346
|
+
*/
|
|
347
|
+
static async dcmtAddOrRemove(caseFlowId, tid, did, remove, cftid) {
|
|
348
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
349
|
+
if (!engine)
|
|
350
|
+
throw new Error('Motore delle istanze CaseFlow non disponibile.');
|
|
351
|
+
const resolvedCftid = cftid ?? (await this.getCaseflow(caseFlowId))?.cftid;
|
|
352
|
+
if (resolvedCftid == null)
|
|
353
|
+
throw new Error(`Impossibile risolvere il template (cftid) per l'istanza id=${caseFlowId}.`);
|
|
354
|
+
await engine.DcmtAddOrRemoveAsync(resolvedCftid, caseFlowId, tid, did, remove);
|
|
355
|
+
}
|
|
356
|
+
// ----- Partecipanti ------------------------------------------------------
|
|
357
|
+
//
|
|
358
|
+
// Per scelta del backend l'engine espone UNA sola API,
|
|
359
|
+
// ParticipantsAddOrRemoveAsync(cftid, id, d, remove): con remove=false funge
|
|
360
|
+
// da UPSERT (aggiunge il partecipante oppure ne aggiorna ruolo/permessi Guest
|
|
361
|
+
// se già presente), con remove=true lo rimuove. Non esiste — né esisterà — un
|
|
362
|
+
// metodo di Update dedicato: modifica ruolo e permessi passano dallo stesso
|
|
363
|
+
// upsert. Il partecipante identifica l'utente col campo `id` (ex `userId`).
|
|
364
|
+
/**
|
|
365
|
+
* Aggiunge un partecipante all'istanza (o ne aggiorna ruolo/permessi se già
|
|
366
|
+
* presente) tramite ParticipantsAddOrRemoveAsync(remove=false).
|
|
367
|
+
*/
|
|
368
|
+
static async addParticipant(caseFlowId, userId, userName, role, guestPermissions, cftid) {
|
|
369
|
+
const participant = CaseFlowParticipant.fromJS({
|
|
370
|
+
id: userId,
|
|
371
|
+
name: userName,
|
|
372
|
+
role,
|
|
373
|
+
guestPermissions: guestPermissions?.toJSON(),
|
|
374
|
+
});
|
|
375
|
+
await this.participantsAddOrRemove(caseFlowId, participant, false, cftid);
|
|
376
|
+
return participant;
|
|
377
|
+
}
|
|
378
|
+
/** Rimuove un partecipante dall'istanza tramite ParticipantsAddOrRemoveAsync(remove=true). */
|
|
379
|
+
static async removeParticipant(caseFlowId, userId, cftid) {
|
|
380
|
+
const participant = CaseFlowParticipant.fromJS({ id: userId });
|
|
381
|
+
await this.participantsAddOrRemove(caseFlowId, participant, true, cftid);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Cambia il ruolo di un partecipante ri-applicando l'upsert col nuovo ruolo
|
|
385
|
+
* (non esiste un'Update dedicata). Passando a Guest si conservano/impostano i
|
|
386
|
+
* relativi permessi.
|
|
387
|
+
*/
|
|
388
|
+
static async changeParticipantRole(caseFlowId, userId, role, guestPermissions, cftid) {
|
|
389
|
+
const participant = CaseFlowParticipant.fromJS({
|
|
390
|
+
id: userId,
|
|
391
|
+
role,
|
|
392
|
+
guestPermissions: role === CF_ParticipantRoles.Guest ? guestPermissions?.toJSON() : undefined,
|
|
393
|
+
});
|
|
394
|
+
await this.participantsAddOrRemove(caseFlowId, participant, false, cftid);
|
|
395
|
+
}
|
|
396
|
+
/** Aggiorna i permessi Guest di un partecipante (upsert col ruolo Guest). */
|
|
397
|
+
static async updateParticipantGuestPermissions(caseFlowId, userId, permissions, cftid) {
|
|
398
|
+
const participant = CaseFlowParticipant.fromJS({
|
|
399
|
+
id: userId,
|
|
400
|
+
role: CF_ParticipantRoles.Guest,
|
|
401
|
+
guestPermissions: permissions.toJSON(),
|
|
402
|
+
});
|
|
403
|
+
await this.participantsAddOrRemove(caseFlowId, participant, false, cftid);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Chiama ParticipantsAddOrRemoveAsync(cftid, id, d, remove) risolvendo il
|
|
407
|
+
* cftid quando non passato (come per la bacheca).
|
|
408
|
+
*/
|
|
409
|
+
static async participantsAddOrRemove(caseFlowId, participant, remove, cftid) {
|
|
410
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
411
|
+
if (!engine)
|
|
412
|
+
throw new Error('Motore delle istanze CaseFlow non disponibile.');
|
|
413
|
+
const resolvedCftid = cftid ?? (await this.getCaseflow(caseFlowId))?.cftid;
|
|
414
|
+
if (resolvedCftid == null)
|
|
415
|
+
throw new Error(`Impossibile risolvere il template (cftid) per l'istanza id=${caseFlowId}.`);
|
|
416
|
+
await engine.ParticipantsAddOrRemoveAsync(resolvedCftid, caseFlowId, participant, remove);
|
|
417
|
+
}
|
|
418
|
+
// ----- Azioni personali dell'utente (Preferiti / Segui) -------------------
|
|
419
|
+
//
|
|
420
|
+
// Riguardano la relazione fra UTENTE e istanza, non il ciclo di vita della
|
|
421
|
+
// pratica. Come per documenti e partecipanti, l'engine espone un'unica API
|
|
422
|
+
// add-or-remove per ciascuna delle due.
|
|
423
|
+
//
|
|
424
|
+
// ATTENZIONE all'asimmetria: dei Preferiti si può leggere l'elenco
|
|
425
|
+
// (FavoritesRetrieveAllAsync), del Segui NO. Nessuna API dice se l'utente
|
|
426
|
+
// corrente segue una pratica: le `FollowAddOrRemove` sono di sola scrittura,
|
|
427
|
+
// qui come per pratiche-dossier e gruppi di lavoro. Finché il backend non
|
|
428
|
+
// esporrà `IsFollow` sul descrittore d'istanza, lo stato del comando Segui
|
|
429
|
+
// resta a carico del chiamante.
|
|
430
|
+
/** Istanze marcate come preferite dall'utente corrente. */
|
|
431
|
+
static async getFavorites() {
|
|
432
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
433
|
+
if (!engine)
|
|
434
|
+
throw new Error('Motore delle istanze CaseFlow non disponibile.');
|
|
435
|
+
return await engine.FavoritesRetrieveAllAsync() ?? [];
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Aggiunge o rimuove l'istanza dai preferiti dell'utente corrente tramite
|
|
439
|
+
* FavoritesAddOrRemoveAsync(cftid, id, remove).
|
|
440
|
+
*/
|
|
441
|
+
static async setFavorite(caseFlowId, favorite, cftid) {
|
|
442
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
443
|
+
if (!engine)
|
|
444
|
+
throw new Error('Motore delle istanze CaseFlow non disponibile.');
|
|
445
|
+
const resolvedCftid = cftid ?? (await this.getCaseflow(caseFlowId))?.cftid;
|
|
446
|
+
if (resolvedCftid == null)
|
|
447
|
+
throw new Error(`Impossibile risolvere il template (cftid) per l'istanza id=${caseFlowId}.`);
|
|
448
|
+
await engine.FavoritesAddOrRemoveAsync(resolvedCftid, caseFlowId, !favorite);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Inizia o smette di seguire l'istanza tramite
|
|
452
|
+
* FollowAddOrRemoveAsync(cftid, id, remove).
|
|
453
|
+
*/
|
|
454
|
+
static async setFollow(caseFlowId, follow, cftid) {
|
|
455
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
456
|
+
if (!engine)
|
|
457
|
+
throw new Error('Motore delle istanze CaseFlow non disponibile.');
|
|
458
|
+
const resolvedCftid = cftid ?? (await this.getCaseflow(caseFlowId))?.cftid;
|
|
459
|
+
if (resolvedCftid == null)
|
|
460
|
+
throw new Error(`Impossibile risolvere il template (cftid) per l'istanza id=${caseFlowId}.`);
|
|
461
|
+
await engine.FollowAddOrRemoveAsync(resolvedCftid, caseFlowId, !follow);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Stato "l'utente corrente segue questa pratica" letto dal descrittore.
|
|
465
|
+
*
|
|
466
|
+
* Restituisce `undefined` quando il descrittore non porta l'informazione —
|
|
467
|
+
* cioè SEMPRE, finché il backend non aggiunge `IsFollow` in output a
|
|
468
|
+
* `usp_Cfi_RetrieveAll`/`usp_Cfi_Retrieve` e la relativa proprietà al
|
|
469
|
+
* contratto. Il tipo è tollerante perché la colonna è un BIT/NUMBER e può
|
|
470
|
+
* arrivare come booleano, come 0/1 o come stringa.
|
|
471
|
+
*
|
|
472
|
+
* Da NON dedurre dalla bacheca Home come fanno pratiche-dossier e gruppi di
|
|
473
|
+
* lavoro (filtro su `HomeBlogPost.classID`): quella query prende `TOP 30`
|
|
474
|
+
* righe e il solo ultimo post non di sistema per oggetto, quindi chi segue
|
|
475
|
+
* un oggetto senza post risulta "non seguace"; per le istanze di CaseFlow,
|
|
476
|
+
* poi, non esiste nemmeno un ramo nella UNION.
|
|
477
|
+
*/
|
|
478
|
+
static readFollowFlag(cf) {
|
|
479
|
+
const raw = cf.isFollow;
|
|
480
|
+
if (typeof raw === 'boolean')
|
|
481
|
+
return raw;
|
|
482
|
+
if (typeof raw === 'number')
|
|
483
|
+
return raw !== 0;
|
|
484
|
+
if (raw === '0' || raw === '1')
|
|
485
|
+
return raw === '1';
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
// ----- Workitem: movimentazione del flusso -------------------------------
|
|
489
|
+
//
|
|
490
|
+
// L'engine espone UNA sola API per la movimentazione dei workitem,
|
|
491
|
+
// ExecuteWorkItemOperationAsync(cftid, id, operation): il tipo di operazione
|
|
492
|
+
// (Complete, Reject, RequestMoreInfo, RespondMoreInfo, Assign, ForceComplete,
|
|
493
|
+
// ForceReject, ...) è indicato in `operation.operation` e i soli dati pertinenti
|
|
494
|
+
// (wiid, actionID, toUserID, comment, metadataValues) vengono valorizzati in
|
|
495
|
+
// base all'operazione. I messaggi in bacheca (nota di completamento, motivo del
|
|
496
|
+
// rifiuto, richiesta informazioni) sono inseriti DIRETTAMENTE dal backend a
|
|
497
|
+
// partire dal `comment`: la UI non deve più creare post di bacheca a mano.
|
|
498
|
+
/** Completa (approva) un workitem. La nota facoltativa viene pubblicata in bacheca dal backend. */
|
|
499
|
+
static async completeWorkItem(caseFlowId, workItemId, response, cftid) {
|
|
500
|
+
await this.executeWorkItemOperation(caseFlowId, CaseFlowWIOperationDescriptor.fromJS({
|
|
501
|
+
operation: CF_WIOperations.Complete,
|
|
502
|
+
wiid: workItemId,
|
|
503
|
+
comment: response,
|
|
504
|
+
}), cftid);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Completa un workitem di "Completamento dati": i metadati valorizzati
|
|
508
|
+
* (mid -> valore) viaggiano in `metadataValues`; il backend li applica al
|
|
509
|
+
* documento master dell'istanza e completa il workitem.
|
|
510
|
+
*/
|
|
511
|
+
static async completeDataCompletionWorkItem(caseFlowId, workItemId, metadataValues, response, cftid) {
|
|
512
|
+
await this.executeWorkItemOperation(caseFlowId, CaseFlowWIOperationDescriptor.fromJS({
|
|
513
|
+
operation: CF_WIOperations.Complete,
|
|
514
|
+
wiid: workItemId,
|
|
515
|
+
comment: response,
|
|
516
|
+
metadataValues: Object.entries(metadataValues).map(([mid, value]) => ({ mid: Number(mid), value })),
|
|
517
|
+
}), cftid);
|
|
518
|
+
}
|
|
519
|
+
/** Rifiuta un workitem. Il motivo viene pubblicato in bacheca dal backend. */
|
|
520
|
+
static async rejectWorkItem(caseFlowId, workItemId, response, cftid) {
|
|
521
|
+
await this.executeWorkItemOperation(caseFlowId, CaseFlowWIOperationDescriptor.fromJS({
|
|
522
|
+
operation: CF_WIOperations.Reject,
|
|
523
|
+
wiid: workItemId,
|
|
524
|
+
comment: response,
|
|
525
|
+
}), cftid);
|
|
526
|
+
}
|
|
527
|
+
/** Assegna un'azione a un utente creando il relativo workitem (operazione Assign). */
|
|
528
|
+
static async createWorkItemForAction(caseFlowId, actionID, toUserID, description, cftid) {
|
|
529
|
+
await this.executeWorkItemOperation(caseFlowId, CaseFlowWIOperationDescriptor.fromJS({
|
|
530
|
+
operation: CF_WIOperations.Assign,
|
|
531
|
+
actionID,
|
|
532
|
+
toUserID,
|
|
533
|
+
comment: description,
|
|
534
|
+
}), cftid);
|
|
535
|
+
}
|
|
536
|
+
/** Forza il completamento di un'azione (supervisor). */
|
|
537
|
+
static async forceCompleteAction(caseFlowId, actionID, reason, cftid) {
|
|
538
|
+
await this.executeWorkItemOperation(caseFlowId, CaseFlowWIOperationDescriptor.fromJS({
|
|
539
|
+
operation: CF_WIOperations.ForceComplete,
|
|
540
|
+
actionID,
|
|
541
|
+
comment: reason,
|
|
542
|
+
}), cftid);
|
|
543
|
+
}
|
|
544
|
+
/** Forza il rifiuto di un'azione (supervisor). Il motivo è obbligatorio. */
|
|
545
|
+
static async forceRejectAction(caseFlowId, actionID, reason, cftid) {
|
|
546
|
+
await this.executeWorkItemOperation(caseFlowId, CaseFlowWIOperationDescriptor.fromJS({
|
|
547
|
+
operation: CF_WIOperations.ForceReject,
|
|
548
|
+
actionID,
|
|
549
|
+
comment: reason,
|
|
550
|
+
}), cftid);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Richiede maggiori informazioni a un altro utente: il backend crea il
|
|
554
|
+
* workitem MoreInfo figlio assegnato a `targetUserId` e registra la richiesta.
|
|
555
|
+
*/
|
|
556
|
+
static async requestMoreInfo(caseFlowId, workItemId, targetUserId, message, cftid) {
|
|
557
|
+
await this.executeWorkItemOperation(caseFlowId, CaseFlowWIOperationDescriptor.fromJS({
|
|
558
|
+
operation: CF_WIOperations.RequestMoreInfo,
|
|
559
|
+
wiid: workItemId,
|
|
560
|
+
toUserID: targetUserId,
|
|
561
|
+
comment: message,
|
|
562
|
+
}), cftid);
|
|
563
|
+
}
|
|
564
|
+
/** Risponde a una richiesta di informazioni completando il workitem MoreInfo. */
|
|
565
|
+
static async respondToMoreInfo(caseFlowId, moreInfoWorkItemId, response, cftid) {
|
|
566
|
+
await this.executeWorkItemOperation(caseFlowId, CaseFlowWIOperationDescriptor.fromJS({
|
|
567
|
+
operation: CF_WIOperations.RespondMoreInfo,
|
|
568
|
+
wiid: moreInfoWorkItemId,
|
|
569
|
+
comment: response,
|
|
570
|
+
}), cftid);
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Chiama ExecuteWorkItemOperationAsync(cftid, id, operation) risolvendo il
|
|
574
|
+
* cftid quando non passato (come per bacheca, documenti e partecipanti).
|
|
575
|
+
*/
|
|
576
|
+
static async executeWorkItemOperation(caseFlowId, operation, cftid) {
|
|
577
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
578
|
+
if (!engine)
|
|
579
|
+
throw new Error('Motore delle istanze CaseFlow non disponibile.');
|
|
580
|
+
const resolvedCftid = cftid ?? (await this.getCaseflow(caseFlowId))?.cftid;
|
|
581
|
+
if (resolvedCftid == null)
|
|
582
|
+
throw new Error(`Impossibile risolvere il template (cftid) per l'istanza id=${caseFlowId}.`);
|
|
583
|
+
await engine.ExecuteWorkItemOperationAsync(resolvedCftid, caseFlowId, operation);
|
|
584
|
+
}
|
|
585
|
+
static loadAllInstances() {
|
|
586
|
+
if (this._inFlightInstances)
|
|
587
|
+
return this._inFlightInstances;
|
|
588
|
+
const p = this.doLoadAllInstances().finally(() => {
|
|
589
|
+
if (this._inFlightInstances === p)
|
|
590
|
+
this._inFlightInstances = undefined;
|
|
591
|
+
});
|
|
592
|
+
this._inFlightInstances = p;
|
|
593
|
+
return p;
|
|
594
|
+
}
|
|
595
|
+
static async doLoadAllInstances() {
|
|
596
|
+
const engine = SDK_Globals.tmSession?.NewCaseFlowInstanceEngine();
|
|
597
|
+
if (!engine)
|
|
598
|
+
return [];
|
|
599
|
+
// cftid=0 => TUTTE le istanze visibili all'utente in un'unica chiamata,
|
|
600
|
+
// cross-template (owner, partecipante di template o di singola istanza),
|
|
601
|
+
// incluse quelle di template NON visibili all'utente.
|
|
602
|
+
//
|
|
603
|
+
// TRANSIZIONE: finché il backend non supporta `cftid=0`, la chiamata può
|
|
604
|
+
// fallire o tornare vuota. In quel caso si ripiega sull'enumerazione
|
|
605
|
+
// per-template (copre gli utenti che vedono i template). Rimuovere il
|
|
606
|
+
// fallback quando `cftid=0` sarà stabile lato backend.
|
|
607
|
+
let list = [];
|
|
608
|
+
try {
|
|
609
|
+
list = (await engine.RetrieveAllAsync(0)) ?? [];
|
|
610
|
+
}
|
|
611
|
+
catch (e) {
|
|
612
|
+
console.warn('[CaseFlow] RetrieveAll(0) non disponibile, ripiego per-template:', e);
|
|
613
|
+
}
|
|
614
|
+
if (list.length === 0) {
|
|
615
|
+
list = await this.loadInstancesPerTemplate(engine);
|
|
616
|
+
}
|
|
617
|
+
const instances = [];
|
|
618
|
+
const seen = new Set();
|
|
619
|
+
for (const inst of list) {
|
|
620
|
+
// Salvaguardia anti-duplicati per id.
|
|
621
|
+
if (inst.id != null) {
|
|
622
|
+
if (seen.has(inst.id))
|
|
623
|
+
continue;
|
|
624
|
+
seen.add(inst.id);
|
|
625
|
+
}
|
|
626
|
+
instances.push(inst);
|
|
627
|
+
}
|
|
628
|
+
// Risolvi i template di TUTTI i cftid presenti per popolare le cache di
|
|
629
|
+
// nome template e nome azione (usate in rendering). GetAsync risolve
|
|
630
|
+
// on-demand anche i template non creabili dall'utente (di cui però
|
|
631
|
+
// partecipa a un'istanza), che altrimenti la cache non conoscerebbe.
|
|
632
|
+
// Best-effort: se un template non è risolvibile, l'etichetta degrada su
|
|
633
|
+
// `Modello #<cftid>` senza nascondere l'istanza.
|
|
634
|
+
const cftids = [...new Set(instances.map(i => i.cftid).filter((c) => c != null))];
|
|
635
|
+
await Promise.all(cftids.map(async (cftid) => {
|
|
636
|
+
const tpl = await CaseFlowTemplateCacheService.GetAsync(cftid);
|
|
637
|
+
if (tpl) {
|
|
638
|
+
primeTemplateName(tpl.id, tpl.name);
|
|
639
|
+
primeActionNames(tpl);
|
|
640
|
+
}
|
|
641
|
+
}));
|
|
642
|
+
await this.resolveUserNames(instances);
|
|
643
|
+
return instances;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Fallback di TRANSIZIONE: enumera le istanze template per template usando
|
|
647
|
+
* l'elenco dei template visibili all'utente. Non copre le istanze di template
|
|
648
|
+
* NON visibili (partecipante a livello di istanza): per quelle serve la
|
|
649
|
+
* RetrieveAll cross-template (cftid=0). Da rimuovere quando cftid=0 è stabile.
|
|
650
|
+
*/
|
|
651
|
+
static async loadInstancesPerTemplate(engine) {
|
|
652
|
+
const templates = await this.getTemplates();
|
|
653
|
+
const collected = [];
|
|
654
|
+
await Promise.all(templates.map(async (t) => {
|
|
655
|
+
if (t.id == null)
|
|
656
|
+
return;
|
|
657
|
+
try {
|
|
658
|
+
const l = await engine.RetrieveAllAsync(t.id);
|
|
659
|
+
for (const inst of l ?? []) {
|
|
660
|
+
if (inst.cftid == null)
|
|
661
|
+
inst.cftid = t.id;
|
|
662
|
+
collected.push(inst);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
catch (e) {
|
|
666
|
+
console.warn(`[CaseFlow] RetrieveAll istanze per template ${t.id} fallito:`, e);
|
|
667
|
+
}
|
|
668
|
+
}));
|
|
669
|
+
return collected;
|
|
670
|
+
}
|
|
671
|
+
/** Valorizza `ownerName` sulle istanze (best-effort, via cache utenti). */
|
|
672
|
+
static async resolveUserNames(instances) {
|
|
673
|
+
const ids = new Set();
|
|
674
|
+
for (const inst of instances) {
|
|
675
|
+
if (inst.ownerID != null)
|
|
676
|
+
ids.add(inst.ownerID);
|
|
677
|
+
}
|
|
678
|
+
const nameById = new Map();
|
|
679
|
+
await Promise.all([...ids].map(async (id) => {
|
|
680
|
+
try {
|
|
681
|
+
const u = await UserListCacheService.GetAsync(id);
|
|
682
|
+
nameById.set(id, u?.name ?? `Utente ${id}`);
|
|
683
|
+
}
|
|
684
|
+
catch {
|
|
685
|
+
nameById.set(id, `Utente ${id}`);
|
|
686
|
+
}
|
|
687
|
+
}));
|
|
688
|
+
for (const inst of instances) {
|
|
689
|
+
if (inst.ownerID != null && !inst.ownerName)
|
|
690
|
+
inst.ownerName = nameById.get(inst.ownerID);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|