@topconsultnpm/sdkui-react 6.22.0-dev4.16 → 6.22.0-dev4.18

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.
@@ -13,7 +13,12 @@ export declare enum InvocationContext {
13
13
  /** Invocato dalla pagina Todo/Task */
14
14
  Todo = "todo",
15
15
  /** Invocato dalla pagina WorkflowCtrl */
16
- WorkflowCtrl = "workflowCtrl"
16
+ WorkflowCtrl = "workflowCtrl",
17
+ /**
18
+ * Invocato dal dettaglio di una pratica: il Toppy non mostra le attività di
19
+ * pratica, perché la pratica le ha già nella propria barra azioni.
20
+ */
21
+ CaseFlow = "caseFlow"
17
22
  }
18
23
  interface ITMDcmtFormProps {
19
24
  TID?: number;
@@ -4,7 +4,7 @@ import TMDcmtPreview from './TMDcmtPreview';
4
4
  import { AccessLevels, AccessLevelsEx, AppModules, ArchiveConstraints, ArchiveEngineByID, DcmtTypeDescriptor, DcmtTypeListCacheService, DcmtTypePermission, DossierCacheService, LayoutCacheService, LayoutModes, MetadataDataDomains, MetadataDataTypes, MetadataFormats, ObjectClasses, ResultTypes, SDK_Globals, SDK_Localizator, SystemMIDsAsNumber, SystemTIDs, Task_States, TID_DID, UpdateEngineByID, UserListCacheService, ValidationItem, WorkflowCacheService, WorkingGroupCacheService, WorkItemMetadataNames } from '@topconsultnpm/sdk-ts';
5
5
  import { FormModes, SearchResultContext } from '../../../ts';
6
6
  import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
7
- import { getWorkItemSetIDAsync, handleArchiveVisibility, searchResultToMetadataValues } from '../../../helper/queryHelper';
7
+ import { getDcmtMetadataValuesAsync, getTraceShowFileMids, getWorkItemSetIDAsync, handleArchiveVisibility, searchResultToMetadataValues } from '../../../helper/queryHelper';
8
8
  import { genUniqueId, IconShow, SDKUI_Localizator, updateMruTids, IconBoard, IconDcmtTypeSys, calcIsModified, IconMenuVertical, Globalization, getListMaxItems, getSystemMetadata, IconBoxArchiveIn, IconClear, IconUndo, SDKUI_Globals, IconPreview, isTaskMoreInfo, IconWorkflow, IconSearch, deepCompare, IconActivity, getExceptionMessage, isApprovalWorkflowView, getDcmtCicoStatus, buildWorkItemsFromWFCtrl, IconLock, getDcmtFormToolbarVisibility, taskModalSizes, IconArchiveMultipleDetailDocuments, isNotGrantedDraftsDTD } from '../../../helper';
9
9
  import { hasDetailRelations, hasMasterRelations } from '../../../helper/dcmtsHelper';
10
10
  import { Gutters, TMColors } from '../../../utils/theme';
@@ -56,6 +56,11 @@ export var InvocationContext;
56
56
  InvocationContext["Todo"] = "todo";
57
57
  /** Invocato dalla pagina WorkflowCtrl */
58
58
  InvocationContext["WorkflowCtrl"] = "workflowCtrl";
59
+ /**
60
+ * Invocato dal dettaglio di una pratica: il Toppy non mostra le attività di
61
+ * pratica, perché la pratica le ha già nella propria barra azioni.
62
+ */
63
+ InvocationContext["CaseFlow"] = "caseFlow";
59
64
  // Aggiungi qui altri contesti futuri secondo necessità
60
65
  })(InvocationContext || (InvocationContext = {}));
61
66
  let abortControllerLocal = new AbortController();
@@ -385,6 +390,29 @@ const TMDcmtForm = ({ TID, DID, groupId, layoutMode = LayoutModes.Update, formMo
385
390
  const triggerPreviewRefresh = useCallback(async () => {
386
391
  setRefreshPreviewTrigger(prev => prev + 1);
387
392
  }, []);
393
+ // Dopo il recupero del file in anteprima aggiorna solo i metadati "Mostra file"
394
+ const refreshTraceShowFileMetadataAsync = useCallback(async () => {
395
+ const traceMids = getTraceShowFileMids(fromDTDRef.current);
396
+ if (!TID || !DID || traceMids.length === 0)
397
+ return;
398
+ try {
399
+ const res = await getDcmtMetadataValuesAsync(TID, DID, traceMids);
400
+ const row = res?.dtdResult?.rows?.[0];
401
+ const mids = res?.selectMIDs ?? [];
402
+ if (!row)
403
+ return;
404
+ // Anche l'originale, altrimenti il form risulterebbe modificato
405
+ const patchTraceValues = (list) => list.map(md => traceMids.includes(md.mid) && mids.includes(md.mid) ? { ...md, value: row[mids.indexOf(md.mid)] } : md);
406
+ setFormData(patchTraceValues);
407
+ setFormDataOrig(patchTraceValues);
408
+ formDataOrigRef.current = patchTraceValues(formDataOrigRef.current);
409
+ // Aggiorna anche la riga della griglia dei risultati con lo stesso risultato
410
+ await onSavedAsyncCallback?.(TID, DID, res);
411
+ }
412
+ catch (e) {
413
+ console.error('Errore aggiornamento metadati "Mostra file":', e);
414
+ }
415
+ }, [TID, DID, onSavedAsyncCallback]);
388
416
  const openTaskFormHandler = (onTaskCreated) => {
389
417
  if (!currentDcmt || !fromDTD)
390
418
  return;
@@ -814,8 +842,10 @@ const TMDcmtForm = ({ TID, DID, groupId, layoutMode = LayoutModes.Update, formMo
814
842
  return dcmtReferences.some(ref => ref.objClass === ObjectClasses.Dossier || ref.objClass === ObjectClasses.WorkingGroup);
815
843
  }, [allowButtonsRefs, layoutMode, dcmtReferences, isOpenDetails, isOpenMaster]);
816
844
  // Attività di pratica: stesse condizioni dell'approvazione workflow (documento
817
- // in consultazione, senza errori, non dentro master/detail).
818
- const showToppyForCaseFlow = useMemo(() => layoutMode === LayoutModes.Update && !fetchError && caseFlowWorkItems.length > 0 && !isOpenDetails && !isOpenMaster, [layoutMode, fetchError, caseFlowWorkItems.length, isOpenDetails, isOpenMaster]);
845
+ // in consultazione, senza errori, non dentro master/detail). Aperto dal
846
+ // dettaglio di una pratica, il form non le ripete.
847
+ const showToppyForCaseFlow = useMemo(() => layoutMode === LayoutModes.Update && !fetchError && caseFlowWorkItems.length > 0 && !isOpenDetails && !isOpenMaster
848
+ && invocationContext !== InvocationContext.CaseFlow, [layoutMode, fetchError, caseFlowWorkItems.length, isOpenDetails, isOpenMaster, invocationContext]);
819
849
  const isToppyVisible = useMemo(() => Boolean((showToppyForApprove || showToppyForCompleteMoreInfo || showToppyForReferences || showToppyForCaseFlow) && !openS4TViewer && !showTodoDcmtForm && !editPdfForm), [showToppyForApprove, showToppyForCompleteMoreInfo, showToppyForReferences, showToppyForCaseFlow, openS4TViewer, showTodoDcmtForm, editPdfForm]);
820
850
  const handleClearForm = useCallback(() => {
821
851
  let data = structuredClone(formData);
@@ -1262,7 +1292,7 @@ const TMDcmtForm = ({ TID, DID, groupId, layoutMode = LayoutModes.Update, formMo
1262
1292
  ]);
1263
1293
  const tmBlog = useMemo(() => _jsx(TMDcmtBlog, { tid: TID, did: DID, fetchBlogDataTrigger: refreshBlogTrigger, onRefreshBlogDatagrid: onRefreshBlogDatagrid, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }), [TID, DID, allTasks, refreshBlogTrigger, handleNavigateToWGs, handleNavigateToDossiers]);
1264
1294
  const tmSysMetadata = useMemo(() => _jsx(TMMetadataValues, { layoutMode: layoutMode, openChooserBySingleClick: !isOpenDistinctValues, TID: TID, isReadOnly: true, deviceType: deviceType, metadataValues: formData.filter(o => (o.mid != undefined && o.mid <= 100)), metadataValuesOrig: formData.filter(o => (o.mid != undefined && o.mid <= 100)), validationItems: [], inputMids: inputMids }), [TID, layoutMode, formData, deviceType, inputMids]);
1265
- const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, fromDTD: fromDTD, currentDcmt: currentDcmt, dcmtFile: dcmtFile ?? inputFile, deviceType: deviceType, onBack: getOnBackHandler(), layoutMode: layoutMode, onFileUpload: (file) => { setDcmtFile(file); }, openFileUploaderPdfEditor: openFileUploaderPdfEditor, enableDragDropOverlay: enableDragDropOverlay, onScanRequest: onScanRequest }), [currentDcmt, dcmtFile, deviceType, fromDTD, layoutMode, inputFile, enableDragDropOverlay, setDcmtFile, onScanRequest, openFileUploaderPdfEditor, refreshPreviewTrigger]);
1295
+ const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, fromDTD: fromDTD, currentDcmt: currentDcmt, dcmtFile: dcmtFile ?? inputFile, deviceType: deviceType, onBack: getOnBackHandler(), layoutMode: layoutMode, onFileUpload: (file) => { setDcmtFile(file); }, openFileUploaderPdfEditor: openFileUploaderPdfEditor, enableDragDropOverlay: enableDragDropOverlay, onScanRequest: onScanRequest, onFileRetrieved: refreshTraceShowFileMetadataAsync }), [currentDcmt, dcmtFile, deviceType, fromDTD, layoutMode, inputFile, enableDragDropOverlay, setDcmtFile, onScanRequest, openFileUploaderPdfEditor, refreshPreviewTrigger, refreshTraceShowFileMetadataAsync]);
1266
1296
  const tmWF = useMemo(() => {
1267
1297
  if (isWFDataLoading) {
1268
1298
  return (_jsx("div", { style: {
@@ -1868,11 +1898,11 @@ const PanelDisabledStateHandler = ({ isWFDisabled, isSysMetadataDisabled, isBoar
1868
1898
  }, [isPreviewDisabled]);
1869
1899
  return null;
1870
1900
  };
1871
- const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, fromDTD, currentDcmt, layoutMode, dcmtFile, deviceType, isVisible, onFileUpload, openFileUploaderPdfEditor, enableDragDropOverlay = false, onScanRequest, onBack }) => {
1901
+ const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, fromDTD, currentDcmt, layoutMode, dcmtFile, deviceType, isVisible, onFileUpload, openFileUploaderPdfEditor, enableDragDropOverlay = false, onScanRequest, onBack, onFileRetrieved }) => {
1872
1902
  const { setPanelVisibilityById, toggleMaximize, isResizingActive, countVisibleLeafPanels, panelVisibility } = useTMPanelManagerContext();
1873
1903
  const isMobile = deviceType === DeviceType.MOBILE;
1874
1904
  return (layoutMode === LayoutModes.Update ?
1875
- _jsx(TMDcmtPreview, { dcmtData: currentDcmt, isVisible: isVisible, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('tmDcmtPreview', false) : undefined, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("tmDcmtPreview") : undefined, onBack: onBack, isResizingActive: isResizingActive }, refreshPreviewTrigger) :
1905
+ _jsx(TMDcmtPreview, { dcmtData: currentDcmt, isVisible: isVisible, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('tmDcmtPreview', false) : undefined, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("tmDcmtPreview") : undefined, onBack: onBack, isResizingActive: isResizingActive, onFileRetrieved: onFileRetrieved }, refreshPreviewTrigger) :
1876
1906
  _jsx(TMFileUploader, { fromDTD: fromDTD, onFileUpload: onFileUpload, openFileUploaderPdfEditor: openFileUploaderPdfEditor, onClose: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('tmDcmtPreview', false) : undefined, isRequired: fromDTD?.archiveConstraint === ArchiveConstraints.ContentCompulsory && dcmtFile === null, defaultBlob: dcmtFile, deviceType: deviceType, isResizingActive: isResizingActive, enableDragDropOverlay: panelVisibility['tmDcmtPreview'] && enableDragDropOverlay, onScanRequest: onScanRequest }));
1877
1907
  };
1878
1908
  const Ribbon = styled.div `
@@ -8,7 +8,7 @@ import TMButton from "../../base/TMButton";
8
8
  import TMModal from "../../base/TMModal";
9
9
  import TMTooltip from "../../base/TMTooltip";
10
10
  import { WorkFlowOperationButtons } from "../workflow/TMWorkflowPopup";
11
- import { getWorkItemActionName, isSignatureWorkItem } from "../../../services/caseflow/caseFlowWorkItemUtils";
11
+ import { getWorkItemActionName, isAcknowledgmentWorkItem, isSignatureWorkItem } from "../../../services/caseflow/caseFlowWorkItemUtils";
12
12
  const TMDcmtFormActionButtons = (props) => {
13
13
  const { showToppyForApprove, workItems, caseFlowWorkItems, onCompleteCaseFlowWorkItem, onRejectCaseFlowWorkItem, deviceType, isMobile, handleSignApprove, updateShowApprovePopup, updateShowRejectPopup, updateShowReAssignPopup, updateShowMoreInfoPopup, fromDTD, showToppyForCompleteMoreInfo, moreInfoTasks, setShowCommentForm, showToppyForReferences, dcmtReferences, referenceActionMap, handleNavigateToReference, setShowMoreInfoTaskPopup, setShowMoreInfoTaskTask } = props;
14
14
  const [showAllMoreInfoModal, setShowAllMoreInfoModal] = useState(false);
@@ -103,7 +103,10 @@ const TMDcmtFormActionButtons = (props) => {
103
103
  whiteSpace: 'pre-line',
104
104
  }, children: description })), _jsx(WorkFlowOperationButtons, { dtd: fromDTD, deviceType: deviceType, signFlow: requiresSignature, approveDisable: isBusy, signApproveDisable: isBusy, rejectDisable: isBusy, onSignApprove: handleSignApprove, onApprove: onCompleteCaseFlowWorkItem
105
105
  ? () => void runCaseFlowAction(item, () => onCompleteCaseFlowWorkItem(item))
106
- : undefined, onReject: onRejectCaseFlowWorkItem
106
+ : undefined,
107
+ // La presa visione non si rifiuta, come nella pratica: senza
108
+ // handler il pulsante non viene mostrato.
109
+ onReject: onRejectCaseFlowWorkItem && !isAcknowledgmentWorkItem(workItem)
107
110
  ? () => { setRejectReason(''); setRejectingItem(item); }
108
111
  : undefined })] }, workItem.id));
109
112
  };
@@ -12,6 +12,8 @@ interface ITMDcmtPreviewProps {
12
12
  onBack?: () => void;
13
13
  allowMaximize?: boolean;
14
14
  onMaximizePanel?: () => void;
15
+ /** File scaricato dal server con tracciatura "Mostra file": i metadati trace sono stati aggiornati */
16
+ onFileRetrieved?: (tid: number | undefined, did: number | undefined) => void;
15
17
  }
16
18
  declare const TMDcmtPreview: React.FC<ITMDcmtPreviewProps>;
17
19
  export default TMDcmtPreview;
@@ -22,7 +22,7 @@ const ErrorContent = ({ error, isAbortError, onRetry }) => {
22
22
  }
23
23
  return _jsx(TMNothingToShow, { icon: _jsx(IconCloseOutline, { fontSize: 92, color: TMColors.error }), text: error });
24
24
  };
25
- const TMDcmtPreview = ({ dcmtData, isResizingActive, isVisible, canNext, canPrev, onClosePanel, onBack, onNext, onPrev, allowMaximize = true, onMaximizePanel }) => {
25
+ const TMDcmtPreview = ({ dcmtData, isResizingActive, isVisible, canNext, canPrev, onClosePanel, onBack, onNext, onPrev, allowMaximize = true, onMaximizePanel, onFileRetrieved }) => {
26
26
  const [dcmtBlob, setDcmtBlob] = useState(undefined);
27
27
  const [showPreview, setShowPreview] = useState(false);
28
28
  const [isFromCache, setIsFromCache] = useState(false);
@@ -122,20 +122,25 @@ const TMDcmtPreview = ({ dcmtData, isResizingActive, isVisible, canNext, canPrev
122
122
  }
123
123
  }
124
124
  };
125
- const loadDocumentWithCache = async () => {
125
+ /** @param isShown false se a video resta la richiesta di conferma: il file non è visualizzato */
126
+ const loadDocumentWithCache = async (isShown = true) => {
126
127
  const rfo = new RetrieveFileOptions();
127
- rfo.retrieveReason = DcmtOpers.ShowFile;
128
+ // Senza impostazione salvata l'anteprima non aggiorna la tracciatura "Mostra file"
129
+ rfo.retrieveReason = isShown && SDKUI_Globals.userSettings?.searchSettings?.previewTracksShowFile === true ? DcmtOpers.ShowFile : DcmtOpers.None;
128
130
  rfo.generalRetrieveFormat = GeneralRetrieveFormats.OriginalUnsigned;
129
131
  rfo.cvtFormat = extensionHandler(dcmtData?.fileExt) === FileExtensionHandler.CONVERTIBLE ? FileFormats.PDF : FileFormats.None;
130
132
  rfo.invoiceRetrieveFormat = SDKUI_Globals.userSettings?.searchSettings.invoiceRetrieveFormat;
131
133
  rfo.orderRetrieveFormat = SDKUI_Globals.userSettings?.searchSettings.orderRetrieveFormat;
132
134
  try {
133
135
  await sleep(300); //wait for DX-DataGrid selectionChanged
134
- let dcmtFile = await getDcmtFileAsync({ TID: dcmtData?.tid, DID: dcmtData?.did, FILEEXT: dcmtData?.fileExt }, rfo, 'Anteprima', false);
136
+ let dcmtFile = await getDcmtFileAsync({ TID: dcmtData?.tid, DID: dcmtData?.did, FILEEXT: dcmtData?.fileExt }, rfo, 'Anteprima', false, !isShown);
135
137
  setDcmtBlob(dcmtFile?.file);
136
138
  setIsFromCache(!!dcmtFile?.isFromCache);
137
139
  setError('');
138
140
  setIsAbortError(false);
141
+ // Dalla cache l'API non è stata chiamata: la tracciatura non è cambiata
142
+ if (dcmtFile?.file && !dcmtFile.isFromCache && rfo.retrieveReason === DcmtOpers.ShowFile)
143
+ onFileRetrieved?.(dcmtData?.tid, dcmtData?.did);
139
144
  }
140
145
  catch (ex) {
141
146
  const err = ex;
@@ -179,7 +184,7 @@ const TMDcmtPreview = ({ dcmtData, isResizingActive, isVisible, canNext, canPrev
179
184
  setIsAbortError(false);
180
185
  setDcmtBlob(undefined);
181
186
  try {
182
- isBasketMode ? await loadBasketFile() : await loadDocumentWithCache();
187
+ isBasketMode ? await loadBasketFile() : await loadDocumentWithCache(showPreview);
183
188
  }
184
189
  catch (error) {
185
190
  console.error('Error reopening document:', error);
@@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useSta
3
3
  import styled from 'styled-components';
4
4
  import { LoadIndicator } from 'devextreme-react';
5
5
  import { AppModules, DataColumnTypes, DataListViewModes, DcmtTypeListCacheService, LayoutCacheService, LayoutModes, MetadataDataDomains, SDK_Globals, SystemMIDsAsNumber, UserListCacheService, } from '@topconsultnpm/sdk-ts';
6
- import { customizeSearchByDisplayText, deepCompare, formatDateTimeByMetadataFormat, formatNumberByMetadataFormat, generateUniqueColumnKeys, genUniqueId, getColumnFormatInfo, getDateDisplayText, getNumberDisplayText, getSearchToolbarVisibility, IconAll, IconBoard, IconDcmtTypeSys, IconDelete, IconDuplicate, IconEdit, IconMenuVertical, IconPlatform, IconRefresh, IconSearchCheck, IconShow, isApprovalWorkflowView, isSign4TopEnabled, searchResultDescriptorToSimpleArray, searchResultToMetadataValues, SDKUI_Globals, SDKUI_Localizator, IconCloseOutline, getSearchResultPanelLayout, getSearchResultPanelLayoutSignature, hasSearchResultPanelLayout, saveSearchResultPanelLayout, subscribeSearchResultPanelLayout, VIRTUAL_RENDERING_THRESHOLD, VIRTUAL_RENDERING_CELL_THRESHOLD } from '../../../helper';
6
+ import { customizeSearchByDisplayText, deepCompare, formatDateTimeByMetadataFormat, formatNumberByMetadataFormat, generateUniqueColumnKeys, genUniqueId, getColumnFormatInfo, getDateDisplayText, getNumberDisplayText, getSearchToolbarVisibility, IconAll, IconBoard, IconDcmtTypeSys, IconDelete, IconDuplicate, IconEdit, IconMenuVertical, IconPlatform, IconRefresh, IconSearchCheck, IconShow, isApprovalWorkflowView, isSign4TopEnabled, searchResultDescriptorToSimpleArray, searchResultToMetadataValues, getTraceShowFileMids, getDcmtMetadataValuesAsync, SDKUI_Globals, SDKUI_Localizator, IconCloseOutline, getSearchResultPanelLayout, getSearchResultPanelLayoutSignature, hasSearchResultPanelLayout, saveSearchResultPanelLayout, subscribeSearchResultPanelLayout, VIRTUAL_RENDERING_THRESHOLD, VIRTUAL_RENDERING_CELL_THRESHOLD } from '../../../helper';
7
7
  import { CounterItemKey, formatCount } from '../../base/TMCounterContainer';
8
8
  import { getDcmtCicoStatus } from '../../../helper/checkinCheckoutManager';
9
9
  import { DcmtOperationTypes, SearchResultContext, } from '../../../ts';
@@ -285,6 +285,23 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
285
285
  if (refreshUI)
286
286
  setLastUpdateSearchTime(new Date(Date.now()));
287
287
  };
288
+ // Ref alla versione corrente: l'anteprima è memoizzata e la funzione si ricrea a ogni render
289
+ const refreshFocusedDataRowRef = useRef(refreshFocusedDataRowAsync);
290
+ refreshFocusedDataRowRef.current = refreshFocusedDataRowAsync;
291
+ // Anteprima scaricata dal server con tracciatura "Mostra file": aggiorna solo la riga del documento
292
+ const onPreviewFileRetrieved = useCallback(async (tid, did) => {
293
+ const traceMids = getTraceShowFileMids(fromDTD);
294
+ if (!tid || !did || traceMids.length === 0)
295
+ return;
296
+ try {
297
+ const res = await getDcmtMetadataValuesAsync(tid, did, traceMids);
298
+ if (res)
299
+ await refreshFocusedDataRowRef.current(tid, did, true, res);
300
+ }
301
+ catch (e) {
302
+ console.error('Errore aggiornamento metadati "Mostra file":', e);
303
+ }
304
+ }, [fromDTD]);
288
305
  // Refreshes metadata for selected documents (or focused one) and triggers grid re-render
289
306
  const onRefreshDataRowsAsync = async () => {
290
307
  let dcmts = getSelectedDcmtsOrFocused(selectedItems, focusedItem);
@@ -829,7 +846,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
829
846
  const isSearchResultOnScreen = isVisible && !isOpenDcmtForm && !isOpenDetails && !isOpenMaster;
830
847
  const tmBlog = useMemo(() => _jsx(TMDcmtBlog, { tid: focusedItem?.TID, did: focusedItem?.DID, isPageVisible: isSearchResultOnScreen, fetchBlogDataTrigger: refreshBlogTrigger, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }), [focusedItem, allTasks, refreshBlogTrigger, isSearchResultOnScreen, handleNavigateToWGs, handleNavigateToDossiers, onRefreshBlogDatagrid]);
831
848
  const tmSysMetadata = useMemo(() => _jsx(TMMetadataValues, { layoutMode: LayoutModes.Update, openChooserBySingleClick: true, TID: focusedItem?.TID, isReadOnly: true, deviceType: deviceType, metadataValues: currentMetadataValues.filter(o => (o.mid != undefined && o.mid <= 100)), metadataValuesOrig: currentMetadataValues.filter(o => (o.mid != undefined && o.mid <= 100)), validationItems: [] }), [focusedItem, currentMetadataValues, deviceType]);
832
- const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, currentDcmt: currentDcmt, isPageVisible: isSearchResultOnScreen, onBack: backHandlerSecondary }), [currentDcmt, refreshPreviewTrigger, isSearchResultOnScreen, backHandlerSecondary]);
849
+ const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, currentDcmt: currentDcmt, isPageVisible: isSearchResultOnScreen, onBack: backHandlerSecondary, onFileRetrieved: onPreviewFileRetrieved }), [currentDcmt, refreshPreviewTrigger, isSearchResultOnScreen, backHandlerSecondary, onPreviewFileRetrieved]);
833
850
  // Auto-fetch indexing info when drawer is open and focusedItem changes
834
851
  useEffect(() => {
835
852
  if (!focusedItem || !showIndexingInfo)
@@ -1746,11 +1763,11 @@ const PanelDisabledStateHandler = ({ isBoardDisabled, savedBlogVisible = false }
1746
1763
  }, [isBoardDisabled, savedBlogVisible]);
1747
1764
  return null;
1748
1765
  };
1749
- const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, currentDcmt, isVisible, isPageVisible = true, onBack }) => {
1766
+ const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, currentDcmt, isVisible, isPageVisible = true, onBack, onFileRetrieved }) => {
1750
1767
  const { setPanelVisibilityById, toggleMaximize, isResizingActive, countVisibleLeafPanels } = useTMPanelManagerContext();
1751
1768
  const deviceType = useDeviceType();
1752
1769
  const isMobile = deviceType === DeviceType.MOBILE;
1753
- return (_jsx(TMDcmtPreview, { dcmtData: currentDcmt, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('tmDcmtPreview', false) : undefined, onBack: onBack, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("tmDcmtPreview") : undefined, isResizingActive: isResizingActive, isVisible: isVisible && isPageVisible }, refreshPreviewTrigger));
1770
+ return (_jsx(TMDcmtPreview, { dcmtData: currentDcmt, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('tmDcmtPreview', false) : undefined, onBack: onBack, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("tmDcmtPreview") : undefined, isResizingActive: isResizingActive, isVisible: isVisible && isPageVisible, onFileRetrieved: onFileRetrieved }, refreshPreviewTrigger));
1754
1771
  };
1755
1772
  // Styled Components
1756
1773
  const StyledPlaceholder = styled.div `
@@ -66,7 +66,7 @@ export const WorkFlowOperationButtons = (props) => {
66
66
  }
67
67
  return false;
68
68
  }, [dtd, signFlow]);
69
- return (_jsx(StyledWorkFlowOperationButtonsContainer, { $isMobile: isMobile, children: isSignWorkflow ? (_jsxs(_Fragment, { children: [_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconSignaturePencil, {}), caption: SDKUI_Localizator.SignatureAndApprove, width: "160px", disabled: signApproveDisable, onClick: () => !signApproveDisable && onSignApprove?.(), onMouseDown: e => e.stopPropagation(), advancedColor: "#1a9a49", color: "success" }), _jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconCloseOutline, {}), caption: SDKUI_Localizator.Reject, disabled: rejectDisable, onClick: () => !rejectDisable && onReject?.(), onMouseDown: e => e.stopPropagation(), advancedColor: TMColors.error, color: "error" }), onMoreInfo && (_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconInfo, { fontSize: 16 }), caption: SDKUI_Localizator.MoreInformation, width: "180px", disabled: infoDisable, onClick: () => !infoDisable && onMoreInfo?.(), onMouseDown: e => e.stopPropagation(), advancedColor: TMColors.info, color: "info" }))] })) : (_jsxs(_Fragment, { children: [_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconApply, {}), caption: SDKUI_Localizator.Approve, disabled: approveDisable, onClick: () => !approveDisable && onApprove?.(), advancedColor: "#1a9a49", color: "success" }), _jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconCloseOutline, {}), caption: SDKUI_Localizator.Reject, disabled: rejectDisable, onClick: () => !rejectDisable && onReject?.(), advancedColor: TMColors.error, color: "error" }), onReAssign && (_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconUser, { fontSize: 16 }), caption: SDKUI_Localizator.Reassign, disabled: reassignDisable, onClick: () => !reassignDisable && onReAssign?.(), advancedColor: TMColors.tertiary, color: "tertiary" })), onMoreInfo && (_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconInfo, { fontSize: 16 }), caption: SDKUI_Localizator.MoreInformation, width: "180px", disabled: infoDisable, onClick: () => !infoDisable && onMoreInfo?.(), advancedColor: TMColors.info, color: "info" }))] })) }));
69
+ return (_jsx(StyledWorkFlowOperationButtonsContainer, { $isMobile: isMobile, children: isSignWorkflow ? (_jsxs(_Fragment, { children: [_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconSignaturePencil, {}), caption: SDKUI_Localizator.SignatureAndApprove, width: "160px", disabled: signApproveDisable, onClick: () => !signApproveDisable && onSignApprove?.(), onMouseDown: e => e.stopPropagation(), advancedColor: "#1a9a49", color: "success" }), onReject && (_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconCloseOutline, {}), caption: SDKUI_Localizator.Reject, disabled: rejectDisable, onClick: () => !rejectDisable && onReject?.(), onMouseDown: e => e.stopPropagation(), advancedColor: TMColors.error, color: "error" })), onMoreInfo && (_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconInfo, { fontSize: 16 }), caption: SDKUI_Localizator.MoreInformation, width: "180px", disabled: infoDisable, onClick: () => !infoDisable && onMoreInfo?.(), onMouseDown: e => e.stopPropagation(), advancedColor: TMColors.info, color: "info" }))] })) : (_jsxs(_Fragment, { children: [_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconApply, {}), caption: SDKUI_Localizator.Approve, disabled: approveDisable, onClick: () => !approveDisable && onApprove?.(), advancedColor: "#1a9a49", color: "success" }), onReject && (_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconCloseOutline, {}), caption: SDKUI_Localizator.Reject, disabled: rejectDisable, onClick: () => !rejectDisable && onReject?.(), advancedColor: TMColors.error, color: "error" })), onReAssign && (_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconUser, { fontSize: 16 }), caption: SDKUI_Localizator.Reassign, disabled: reassignDisable, onClick: () => !reassignDisable && onReAssign?.(), advancedColor: TMColors.tertiary, color: "tertiary" })), onMoreInfo && (_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconInfo, { fontSize: 16 }), caption: SDKUI_Localizator.MoreInformation, width: "180px", disabled: infoDisable, onClick: () => !infoDisable && onMoreInfo?.(), advancedColor: TMColors.info, color: "info" }))] })) }));
70
70
  };
71
71
  export const WorkFlowApproveRejectPopUp = ({ TID = 0, DID = 0, deviceType = DeviceType.DESKTOP, isReject, selectedItems = [], onClose, onCompleted }) => {
72
72
  const { refreshWorkflowApprove } = useWorkflowApprove();
@@ -92,6 +92,7 @@ export declare class SearchSettings {
92
92
  mruTIDs: number[];
93
93
  defaultTree: number;
94
94
  previewThreshold: number;
95
+ previewTracksShowFile: boolean;
95
96
  pageSize: number;
96
97
  maxDcmtsToBeReturned: number;
97
98
  floatingMenuBar: FloatingMenuBarSettings;
@@ -158,6 +158,7 @@ export class SearchSettings {
158
158
  this.mruTIDs = [];
159
159
  this.defaultTree = -1;
160
160
  this.previewThreshold = DEFAULT_PREVIEW_THRESHOLD;
161
+ this.previewTracksShowFile = false; // Se attivo, l'anteprima aggiorna la tracciatura "Mostra file" del documento
161
162
  this.pageSize = DEFAULT_PAGE_SIZE;
162
163
  this.maxDcmtsToBeReturned = DEFAULT_MAX_DCMTS_TO_BE_RETURNED;
163
164
  this.floatingMenuBar = new FloatingMenuBarSettings();
@@ -1,4 +1,4 @@
1
- import { MetadataDescriptor, MetadataDataDomains, SystemMIDsAsNumber, MetadataDataTypes, QueryDescriptor, SelectItem, DataTableDescriptor, LayoutModes, QueryOperators, SavedQueryDescriptor } from '@topconsultnpm/sdk-ts';
1
+ import { MetadataDescriptor, MetadataDataDomains, SystemMIDsAsNumber, MetadataDataTypes, QueryDescriptor, SelectItem, DataTableDescriptor, LayoutModes, QueryOperators, SavedQueryDescriptor, DcmtTypeDescriptor, SearchResultDescriptor } from '@topconsultnpm/sdk-ts';
2
2
  import { MetadataValueDescriptorEx } from '../ts';
3
3
  export type TID_Alias = {
4
4
  tid: number;
@@ -8,7 +8,7 @@ export declare const getTIDsByQd: (qd: QueryDescriptor | undefined) => TID_Alias
8
8
  /** Restituisce i tipi documento (con metadati) referenziati da qd.
9
9
  * Con refreshCache = true i descrittori vengono riletti dal server, così la lista dei metadati
10
10
  * recepisce quelli aggiunti o rimossi dopo il popolamento della cache. */
11
- export declare const getDcmtTypesByQdAsync: (qd: QueryDescriptor, refreshCache?: boolean) => Promise<import("@topconsultnpm/sdk-ts").DcmtTypeDescriptor[]>;
11
+ export declare const getDcmtTypesByQdAsync: (qd: QueryDescriptor, refreshCache?: boolean) => Promise<DcmtTypeDescriptor[]>;
12
12
  export declare const displayMetadataValue: (md: MetadataDescriptor | undefined, value: string | undefined, valueEmptyDescription?: string) => string;
13
13
  export declare const IsParametricQuery: (qd: QueryDescriptor | undefined) => boolean;
14
14
  export declare const addHiddenSelectItem: (select: SelectItem[], tid: number | undefined, mid: SystemMIDsAsNumber) => void;
@@ -20,5 +20,12 @@ export declare const getWorkItemSetIDAsync: (vid: number, did: number) => Promis
20
20
  /** Id assegnato alla ricerca di sistema con tutti i documenti: non è in cache, va ricostruita a runtime con getSysAllDcmtsSQD */
21
21
  export declare const SYS_ALL_DCMTS_SQD_ID = 1;
22
22
  export declare const getSysAllDcmtsSQD: (tid: number | undefined, easyOr: boolean) => Promise<SavedQueryDescriptor>;
23
+ /** MID dei metadati con tracciatura "Mostra file" del tipo documento */
24
+ export declare const getTraceShowFileMids: (dtd: DcmtTypeDescriptor | undefined) => number[];
25
+ /**
26
+ * Legge solo i metadati indicati del documento con una ricerca mirata.
27
+ * Se la ricerca fallisce o non li restituisce tutti (es. permessi), ripiega sulla GetMetadata completa.
28
+ */
29
+ export declare const getDcmtMetadataValuesAsync: (tid: number, did: number, mids: number[]) => Promise<SearchResultDescriptor | undefined>;
23
30
  export declare const searchResultToMetadataValues: (tid: number | undefined, dtd: DataTableDescriptor | undefined, rows: string[], mids: number[], metadata: MetadataDescriptor[], layoutMode: LayoutModes, isReadOnlyOrigin?: boolean) => MetadataValueDescriptorEx[];
24
31
  export declare const handleArchiveVisibility: (md: MetadataDescriptor) => boolean;
@@ -1,4 +1,4 @@
1
- import { AccessLevels, MetadataDataDomains, DcmtTypeListCacheService, SystemMIDsAsNumber, MetadataDataTypes, QueryDescriptor, QueryFunctions, SelectItem, SelectItemVisibilities, FromItem, LayoutModes, QueryOperators, SavedQueryDescriptor, SearchEngine, WhereItem, OrderByItem, SDK_Globals, AppModules, SystemTIDs, WorkItemMetadataNames, PlatformObjectValidator } from '@topconsultnpm/sdk-ts';
1
+ import { AccessLevels, MetadataDataDomains, DcmtTypeListCacheService, SystemMIDsAsNumber, MetadataDataTypes, QueryDescriptor, QueryFunctions, SelectItem, SelectItemVisibilities, FromItem, LayoutModes, QueryOperators, SavedQueryDescriptor, SearchEngine, WhereItem, OrderByItem, SDK_Globals, AppModules, SystemTIDs, WorkItemMetadataNames, PlatformObjectValidator, MetadataSpecials } from '@topconsultnpm/sdk-ts';
2
2
  import { DateDisplayTypes, Globalization } from './Globalization';
3
3
  import { ChronologyMIDs, DraftsMIDs, MetadataValueDescriptorEx } from '../ts';
4
4
  import { SDKUI_Localizator } from './SDKUI_Localizator';
@@ -265,6 +265,44 @@ export const getSysAllDcmtsSQD = async (tid, easyOr) => {
265
265
  sqd.qd = await getQD(tid, easyOr, SDKUI_Globals.userSettings.searchSettings?.maxDcmtsToBeReturned ?? 1000);
266
266
  return sqd;
267
267
  };
268
+ /** MID dei metadati con tracciatura "Mostra file" del tipo documento */
269
+ export const getTraceShowFileMids = (dtd) => dtd?.metadata?.filter(md => md.special === MetadataSpecials.Trace_ShowFile && md.id !== undefined).map(md => md.id) ?? [];
270
+ /**
271
+ * Legge solo i metadati indicati del documento con una ricerca mirata.
272
+ * Se la ricerca fallisce o non li restituisce tutti (es. permessi), ripiega sulla GetMetadata completa.
273
+ */
274
+ export const getDcmtMetadataValuesAsync = async (tid, did, mids) => {
275
+ const searchEngine = SDK_Globals.tmSession?.NewSearchEngine();
276
+ try {
277
+ let qd = SearchEngine.NewQueryDescriptor();
278
+ if (qd.from) {
279
+ qd.from.tid = tid;
280
+ qd.select = mids.map(mid => {
281
+ let si = new SelectItem();
282
+ si.tid = tid;
283
+ si.mid = mid;
284
+ si.visibility = SelectItemVisibilities.Visible;
285
+ return si;
286
+ });
287
+ let wiDID = new WhereItem();
288
+ wiDID.tid = tid;
289
+ wiDID.mid = SystemMIDsAsNumber.DID;
290
+ wiDID.operator = QueryOperators.Equal;
291
+ wiDID.value1 = did.toString();
292
+ wiDID.or = false;
293
+ wiDID.leftBrackets = '(';
294
+ wiDID.rightBrackets = ')';
295
+ qd.where = [wiDID];
296
+ const result = await searchEngine?.SearchByIDAsync(qd);
297
+ if (result?.dtdResult?.rows?.[0] && mids.every(mid => result.selectMIDs?.includes(mid)))
298
+ return result;
299
+ }
300
+ }
301
+ catch (e) {
302
+ console.warn('Ricerca mirata dei metadati non riuscita, uso GetMetadata:', e);
303
+ }
304
+ return await searchEngine?.GetMetadataAsync(tid, did, true);
305
+ };
268
306
  export const searchResultToMetadataValues = (tid, dtd, rows, mids, metadata, layoutMode, isReadOnlyOrigin = false) => {
269
307
  if (!metadata || metadata.length === 0) {
270
308
  return [];
@@ -1,7 +1,8 @@
1
1
  import { useCallback, useEffect, useState } from 'react';
2
- import { SDK_Globals } from '@topconsultnpm/sdk-ts';
2
+ import { CF_WorkItemCategories, SDK_Globals } from '@topconsultnpm/sdk-ts';
3
3
  import { CaseFlowService } from '../services/caseflow/CaseFlowService';
4
- import { getWorkItemTargetDoc, isMoreInfoWorkItem, isSignatureWorkItem } from '../services/caseflow/caseFlowWorkItemUtils';
4
+ import { getWorkItemTargetDoc, isSignatureWorkItem } from '../services/caseflow/caseFlowWorkItemUtils';
5
+ import { isFinalActionSync } from '../services/caseflow/caseFlowNameCache';
5
6
  const CASEFLOW_APPROVE_CHANGED_EVENT = 'onCaseFlowApproveChange';
6
7
  let cache = [];
7
8
  let inFlight;
@@ -16,10 +17,27 @@ const notifyChanged = () => {
16
17
  return;
17
18
  window.dispatchEvent(new CustomEvent(CASEFLOW_APPROVE_CHANGED_EVENT));
18
19
  };
20
+ /**
21
+ * Categorie che si completano DAL DOCUMENTO: firmarlo, approvarlo, prenderne
22
+ * visione. Le altre si completano nella pratica, perché il Toppy del form offre
23
+ * solo Approva/Rifiuta e non saprebbe fare il resto:
24
+ * - Caricamento manuale (Upload): il commento è obbligatorio (TMC-2822);
25
+ * - Generica / caricamento automatico: si chiude da sola quando il documento
26
+ * viene agganciato;
27
+ * - Completamento dati: vanno inseriti i valori dei metadati;
28
+ * - Verifica bloccante: sblocca o interrompe la pratica;
29
+ * - Richiesta informazioni: ha una risposta, non un esito.
30
+ */
31
+ const DOCUMENT_CATEGORIES = new Set([
32
+ CF_WorkItemCategories.Approval,
33
+ CF_WorkItemCategories.Signature,
34
+ CF_WorkItemCategories.Acknowledgment,
35
+ ]);
19
36
  /**
20
37
  * Ricarica la cache: i workitem pendenti dell'utente, risolti sul documento su
21
- * cui lavorano. Le richieste di informazioni sono escluse (si rispondono nella
22
- * pratica, non sul documento) come i workitem senza documento.
38
+ * cui lavorano. Restano fuori le categorie che non si completano dal documento
39
+ * (vedi DOCUMENT_CATEGORIES), le azioni che chiudono la pratica e i workitem
40
+ * senza documento.
23
41
  */
24
42
  const reload = async () => {
25
43
  const userId = SDK_Globals.tmSession?.SessionDescr?.userID;
@@ -30,8 +48,7 @@ const reload = async () => {
30
48
  const groups = await CaseFlowService.getMyPendingWorkItems(userId);
31
49
  const resolved = [];
32
50
  for (const { caseflow, workItems } of groups) {
33
- // Le richieste di informazioni si rispondono nella pratica, non sul documento.
34
- const relevant = workItems.filter(wi => !isMoreInfoWorkItem(wi));
51
+ const relevant = workItems.filter(wi => DOCUMENT_CATEGORIES.has(wi.workItemCategory));
35
52
  if (relevant.length === 0)
36
53
  continue;
37
54
  // Il TID dell'azione è autorato sul TEMPLATE e non materializzato
@@ -47,6 +64,14 @@ const reload = async () => {
47
64
  instance = (await CaseFlowService.getCaseflow(instance.id, instance.cftid)) ?? instance;
48
65
  }
49
66
  for (const wi of relevant) {
67
+ // Un'azione che chiude la pratica richiede il motivo, che il Toppy del
68
+ // documento non chiede: si completa nella pratica. Il template è già
69
+ // risolto qui sopra, quindi `isFinal` è noto.
70
+ // La Firma resta: firmare si può solo da qui (l'ospite non ha altra
71
+ // strada). Fallirà il solo completamento automatico, che l'utente fa
72
+ // poi nella pratica scrivendo il motivo, con la firma già apposta.
73
+ if (!isSignatureWorkItem(wi) && isFinalActionSync(caseflow.cftid, wi.actionID))
74
+ continue;
50
75
  const target = getWorkItemTargetDoc(instance, wi);
51
76
  if (!target)
52
77
  continue;
@@ -30,3 +30,8 @@ export declare function primeActionNames(template?: CaseFlowTemplateDescriptor |
30
30
  * l'azione non lavora su un documento (o il template non è ancora risolto).
31
31
  */
32
32
  export declare function getActionTidSync(cftid?: number, actionID?: number): number | undefined;
33
+ /**
34
+ * true se l'azione del template chiude la pratica (`isFinal`). Con il template
35
+ * non ancora risolto ritorna false.
36
+ */
37
+ export declare function isFinalActionSync(cftid?: number, actionID?: number): boolean;
@@ -95,6 +95,11 @@ export function primeActionNames(template) {
95
95
  actionNameCache.set(actionKey(cftid, a.id), a.name ?? '');
96
96
  if (a.actionTID != null && a.actionTID > 0)
97
97
  actionTidCache.set(actionKey(cftid, a.id), a.actionTID);
98
+ // Il template può essere stato modificato: la chiave va anche tolta.
99
+ if (a.isFinal)
100
+ finalActionKeys.add(actionKey(cftid, a.id));
101
+ else
102
+ finalActionKeys.delete(actionKey(cftid, a.id));
98
103
  }
99
104
  }
100
105
  // ---- TID documento per azione (per template) --------------------------------
@@ -114,3 +119,17 @@ export function getActionTidSync(cftid, actionID) {
114
119
  return undefined;
115
120
  return actionTidCache.get(actionKey(cftid, actionID));
116
121
  }
122
+ // ---- Azioni che chiudono la pratica (per template) ---------------------------
123
+ //
124
+ // Anche `isFinal` vive solo SUL TEMPLATE: sull'azione d'istanza arriva sempre
125
+ // false. Popolata da {@link primeActionNames} come le cache qui sopra.
126
+ const finalActionKeys = new Set();
127
+ /**
128
+ * true se l'azione del template chiude la pratica (`isFinal`). Con il template
129
+ * non ancora risolto ritorna false.
130
+ */
131
+ export function isFinalActionSync(cftid, actionID) {
132
+ if (cftid == null || actionID == null)
133
+ return false;
134
+ return finalActionKeys.has(actionKey(cftid, actionID));
135
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.22.0-dev4.16",
3
+ "version": "6.22.0-dev4.18",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",