@masterteam/work-center 0.0.65 → 0.0.66

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.
@@ -1752,6 +1752,156 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1752
1752
  ], template: "<div\r\n class=\"mt-modal-content flex h-full min-h-0 flex-col gap-4 p-4 lg:overflow-hidden\"\r\n *transloco=\"let t; prefix: 'workCenter'\"\r\n>\r\n <div\r\n class=\"flex flex-1 min-h-0 flex-col gap-4 lg:grid lg:grid-cols-[13rem_minmax(0,1fr)] lg:gap-6\"\r\n >\r\n @if (tabOptions().length > 1) {\r\n <mt-tabs\r\n [(active)]=\"activeTab\"\r\n [options]=\"tabOptions()\"\r\n mode=\"vertical\"\r\n size=\"small\"\r\n />\r\n }\r\n\r\n <div class=\"min-w-0 flex-1 min-h-0 lg:overflow-hidden\">\r\n <div\r\n class=\"h-full min-h-0 lg:overflow-hidden\"\r\n [hidden]=\"activeTab() !== 'details'\"\r\n >\r\n <div\r\n class=\"min-w-0 h-full min-h-0 lg:overflow-y-auto lg:overscroll-contain lg:pr-2\"\r\n >\r\n @if (canRenderForm()) {\r\n <mt-client-form\r\n [moduleKey]=\"moduleKey()\"\r\n [operationKey]=\"operationKey()\"\r\n [moduleId]=\"formModuleId()\"\r\n [levelId]=\"formLevelId()\"\r\n [levelDataId]=\"formLevelDataId()\"\r\n [moduleDataId]=\"moduleDataId()\"\r\n [readonly]=\"true\"\r\n [lookups]=\"formLookups()\"\r\n />\r\n } @else {\r\n <div\r\n class=\"flex min-h-[22rem] items-center justify-center rounded-lg border border-dashed border-surface-300 bg-surface-50 p-6\"\r\n >\r\n <p class=\"max-w-md text-center text-sm text-surface-500\">\r\n {{ t(\"context.moduleFormUnavailable\") }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (!readOnly()) {\r\n <div\r\n class=\"h-full min-h-0 overflow-hidden\"\r\n [hidden]=\"activeTab() !== 'discussion'\"\r\n >\r\n @if (canRenderDiscussion()) {\r\n <div\r\n class=\"h-[32rem] min-h-[22rem] overflow-hidden rounded-lg border border-surface-200 bg-surface-50 lg:h-full lg:min-h-0\"\r\n >\r\n <mt-discussion-thread\r\n [moduleType]=\"discussionModuleType()\"\r\n [recordId]=\"resolvedInstanceId()\"\r\n [mentionSearchEndpoint]=\"'Identity/users'\"\r\n [mentionSearchParam]=\"'query'\"\r\n [mentionSearchDataPath]=\"'data'\"\r\n [uploadEndpoint]=\"'uploader'\"\r\n [attachmentDownloadEndpoint]=\"'uploader'\"\r\n [showParticipants]=\"true\"\r\n [autoMarkRead]=\"true\"\r\n [refreshIntervalMs]=\"0\"\r\n [styleClass]=\"'h-full'\"\r\n />\r\n </div>\r\n } @else {\r\n <div\r\n class=\"flex min-h-[22rem] items-center justify-center rounded-lg border border-dashed border-surface-300 bg-surface-50 p-6\"\r\n >\r\n <p class=\"max-w-md text-center text-sm text-surface-500\">\r\n {{ t(\"context.moduleDiscussionUnavailable\") }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</div>\r\n" }]
1753
1753
  }], ctorParameters: () => [], propDecorators: { details: [{ type: i0.Input, args: [{ isSignal: true, alias: "details", required: true }] }], lookups: [{ type: i0.Input, args: [{ isSignal: true, alias: "lookups", required: false }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], clientForm: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ClientForm), { isSignal: true }] }] } });
1754
1754
 
1755
+ /**
1756
+ * Approval-history row projection for the process preview.
1757
+ *
1758
+ * The runtime stores one `step` record per *target user*, so a group step
1759
+ * fans out into one record per member, and every workflow attempt (return →
1760
+ * resubmit) creates a fresh fan-out for the same step schema. The table shows
1761
+ * one row per attempt: sibling member records collapse, but separate attempts
1762
+ * — and the completed history they carry — are preserved.
1763
+ */
1764
+ const PENDING = 'pending';
1765
+ const TERMINATED = 'terminated';
1766
+ /** Statuses whose actor is not the assigned group/role, so the actor wins. */
1767
+ const ACTOR_OVERRIDES_TARGET = new Set([TERMINATED]);
1768
+ function buildApprovalRows(steps, options) {
1769
+ const { resolveDisplayName } = options;
1770
+ const currentIds = new Set((options.currentStepIds ?? []).map((id) => String(id)));
1771
+ const rows = [];
1772
+ for (const run of splitIntoRuns(steps)) {
1773
+ const groupName = resolveGroupName(run[0].target, resolveDisplayName);
1774
+ if (groupName) {
1775
+ rows.push(buildGroupRow(run, groupName, currentIds, resolveDisplayName));
1776
+ continue;
1777
+ }
1778
+ // Non-group targets keep one row per record, minus the pending siblings
1779
+ // that were superseded when another member of the same fan-out acted.
1780
+ const runHasAction = run.some(hasAction);
1781
+ for (const step of run) {
1782
+ if (runHasAction &&
1783
+ run.length > 1 &&
1784
+ isSupersededPending(step, currentIds)) {
1785
+ continue;
1786
+ }
1787
+ rows.push({
1788
+ stepName: resolveStepName(step, resolveDisplayName),
1789
+ status: step.status,
1790
+ user: {
1791
+ kind: 'user',
1792
+ value: step.actionUserInfo ?? step.targetUser ?? null,
1793
+ },
1794
+ createdAt: step.createdAt ?? '',
1795
+ actionDate: step.actionDate ?? '',
1796
+ });
1797
+ }
1798
+ }
1799
+ return rows;
1800
+ }
1801
+ function buildGroupRow(run, groupName, currentIds, resolveDisplayName) {
1802
+ const pendingOnGroup = run.find((step) => isCurrent(step, currentIds));
1803
+ // While the attempt is open the row belongs to the group as a whole; once it
1804
+ // closes, the record that carries the outcome represents it.
1805
+ const repr = pendingOnGroup ?? resolveOutcome(run);
1806
+ const actor = repr.actionUserInfo;
1807
+ const showActor = !pendingOnGroup && !!actor && ACTOR_OVERRIDES_TARGET.has(statusKey(repr));
1808
+ return {
1809
+ stepName: resolveStepName(repr, resolveDisplayName),
1810
+ status: repr.status,
1811
+ user: showActor
1812
+ ? { kind: 'user', value: actor }
1813
+ : { kind: 'text', value: groupName },
1814
+ createdAt: run[0].createdAt ?? '',
1815
+ actionDate: pendingOnGroup ? '' : (repr.actionDate ?? ''),
1816
+ };
1817
+ }
1818
+ /**
1819
+ * Consecutive records sharing a step schema + target belong to the same
1820
+ * attempt. The API orders steps by creation, so a later attempt on the same
1821
+ * step is always separated by the records that sent the request back.
1822
+ */
1823
+ function splitIntoRuns(steps) {
1824
+ const runs = [];
1825
+ let currentKey = null;
1826
+ for (const step of steps) {
1827
+ const key = runKey(step);
1828
+ if (key !== currentKey || runs.length === 0) {
1829
+ runs.push([step]);
1830
+ currentKey = key;
1831
+ continue;
1832
+ }
1833
+ runs[runs.length - 1].push(step);
1834
+ }
1835
+ return runs;
1836
+ }
1837
+ function runKey(step) {
1838
+ return `${step.stepSchemaId ?? ''}|${step.target?.groupKey ?? ''}`;
1839
+ }
1840
+ /** The record holding the attempt's outcome (Returned/Rejected/Terminated…). */
1841
+ function resolveOutcome(run) {
1842
+ for (let index = run.length - 1; index >= 0; index -= 1) {
1843
+ const step = run[index];
1844
+ if (statusKey(step) !== PENDING && statusKey(step) !== '') {
1845
+ return step;
1846
+ }
1847
+ }
1848
+ return findLast(run, hasAction) ?? run[run.length - 1];
1849
+ }
1850
+ function isCurrent(step, currentIds) {
1851
+ if (typeof step.isCurrent === 'boolean') {
1852
+ return step.isCurrent;
1853
+ }
1854
+ if (currentIds.size > 0) {
1855
+ return currentIds.has(String(step.stepId ?? ''));
1856
+ }
1857
+ return step.isActive !== false && statusKey(step) === PENDING;
1858
+ }
1859
+ /** A pending record left behind after a sibling acted — never actionable. */
1860
+ function isSupersededPending(step, currentIds) {
1861
+ return (statusKey(step) === PENDING &&
1862
+ !hasAction(step) &&
1863
+ !isCurrent(step, currentIds));
1864
+ }
1865
+ function hasAction(step) {
1866
+ if (step.actionUserInfo) {
1867
+ return true;
1868
+ }
1869
+ const key = statusKey(step);
1870
+ return key !== '' && key !== PENDING;
1871
+ }
1872
+ /** Status comes off the lookup key, never the localized display text. */
1873
+ function statusKey(step) {
1874
+ const status = step.status;
1875
+ if (typeof status === 'string') {
1876
+ return status.trim().toLowerCase();
1877
+ }
1878
+ if (status && typeof status === 'object') {
1879
+ const key = status['key'];
1880
+ if (typeof key === 'string') {
1881
+ return key.trim().toLowerCase();
1882
+ }
1883
+ }
1884
+ return '';
1885
+ }
1886
+ function resolveStepName(step, resolveDisplayName) {
1887
+ return resolveDisplayName(step.stepName) || String(step.stepId ?? '--');
1888
+ }
1889
+ function resolveGroupName(target, resolveDisplayName) {
1890
+ if (!target || target.type !== 'Group' || !target.group) {
1891
+ return '';
1892
+ }
1893
+ return (resolveDisplayName(target.displayName) ||
1894
+ resolveDisplayName(target.group.name));
1895
+ }
1896
+ function findLast(items, predicate) {
1897
+ for (let index = items.length - 1; index >= 0; index -= 1) {
1898
+ if (predicate(items[index])) {
1899
+ return items[index];
1900
+ }
1901
+ }
1902
+ return undefined;
1903
+ }
1904
+
1755
1905
  class WorkCenterProcessPreview {
1756
1906
  http = inject(HttpClient);
1757
1907
  transloco = inject(TranslocoService);
@@ -1789,42 +1939,20 @@ class WorkCenterProcessPreview {
1789
1939
  preview = signal(null, ...(ngDevMode ? [{ debugName: "preview" }] : /* istanbul ignore next */ []));
1790
1940
  canRenderPreview = computed(() => (this.requestId() ?? 0) > 0, ...(ngDevMode ? [{ debugName: "canRenderPreview" }] : /* istanbul ignore next */ []));
1791
1941
  approvalRows = computed(() => {
1792
- const steps = this.preview()?.steps ?? [];
1793
- const rows = [];
1794
- const seenGroupKeys = new Set();
1795
- for (const step of steps) {
1796
- const groupName = this.resolveGroupName(step.target);
1797
- if (groupName) {
1798
- const dedupKey = `${step.stepSchemaId ?? ''}|${step.target?.groupKey ?? ''}`;
1799
- if (seenGroupKeys.has(dedupKey))
1800
- continue;
1801
- seenGroupKeys.add(dedupKey);
1802
- // Prefer a sibling that has been actioned so we can surface real
1803
- // action date/status; otherwise fall back to the current step.
1804
- const siblings = steps.filter((s) => (s.stepSchemaId ?? '') === (step.stepSchemaId ?? '') &&
1805
- (s.target?.groupKey ?? '') === (step.target?.groupKey ?? ''));
1806
- const repr = siblings.find((s) => s.actionUserInfo) ?? step;
1807
- rows.push({
1808
- stepName: this.resolveDisplayName(repr.stepName) ||
1809
- String(repr.stepId ?? '--'),
1810
- status: buildEntity('Status', 'Status', repr.status),
1811
- user: buildEntity('User', 'Text', groupName),
1812
- createdAt: buildEntity('Initiation Date', 'DateTime', repr.createdAt ?? ''),
1813
- actionDate: buildEntity('Action Date', 'DateTime', repr.actionDate ?? ''),
1814
- });
1815
- }
1816
- else {
1817
- rows.push({
1818
- stepName: this.resolveDisplayName(step.stepName) ||
1819
- String(step.stepId ?? '--'),
1820
- status: buildEntity('Status', 'Status', step.status),
1821
- user: buildEntity('User', 'User', (step.actionUserInfo ?? step.targetUser ?? null)),
1822
- createdAt: buildEntity('Initiation Date', 'DateTime', step.createdAt ?? ''),
1823
- actionDate: buildEntity('Action Date', 'DateTime', step.actionDate ?? ''),
1824
- });
1825
- }
1826
- }
1827
- return rows;
1942
+ const preview = this.preview();
1943
+ const rows = buildApprovalRows(preview?.steps ?? [], {
1944
+ currentStepIds: preview?.currentStepIds,
1945
+ resolveDisplayName: (value) => this.resolveDisplayName(value),
1946
+ });
1947
+ return rows.map((row) => ({
1948
+ stepName: row.stepName,
1949
+ status: buildEntity('Status', 'Status', row.status),
1950
+ user: row.user.kind === 'text'
1951
+ ? buildEntity('User', 'Text', row.user.value)
1952
+ : buildEntity('User', 'User', row.user.value),
1953
+ createdAt: buildEntity('Initiation Date', 'DateTime', row.createdAt),
1954
+ actionDate: buildEntity('Action Date', 'DateTime', row.actionDate),
1955
+ }));
1828
1956
  }, ...(ngDevMode ? [{ debugName: "approvalRows" }] : /* istanbul ignore next */ []));
1829
1957
  hasApprovals = computed(() => this.approvalRows().length > 0, ...(ngDevMode ? [{ debugName: "hasApprovals" }] : /* istanbul ignore next */ []));
1830
1958
  schemaNodes = computed(() => {
@@ -1923,13 +2051,6 @@ class WorkCenterProcessPreview {
1923
2051
  ngOnDestroy() {
1924
2052
  this.loadSub?.unsubscribe();
1925
2053
  }
1926
- resolveGroupName(target) {
1927
- if (!target || target.type !== 'Group' || !target.group) {
1928
- return '';
1929
- }
1930
- return (this.resolveDisplayName(target.displayName) ||
1931
- this.resolveDisplayName(target.group.name));
1932
- }
1933
2054
  resolveDisplayName(name) {
1934
2055
  if (typeof name === 'string') {
1935
2056
  return name;