@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
|
|
1793
|
-
const rows = []
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
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;
|