@haiiro/docuflow 0.0.12 → 0.0.15
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/README.md +41 -9
- package/bin/install.mjs +69 -3
- package/manifest.json +114 -18
- package/package.json +6 -3
- package/public/app/docflow-app.css +2 -0
- package/public/app/docflow-app.js +95 -0
- package/src/front-components/docflow-documents.front-component.mjs +41 -41
- package/src/front-components/docflow-documents.front-component.tsx +49 -32
- package/src/front-components/docflow-processes.front-component.mjs +34 -34
- package/src/front-components/docflow-processes.front-component.tsx +24 -11
- package/src/front-components/docflow-tasks.front-component.mjs +40 -39
- package/src/front-components/docflow-tasks.front-component.tsx +19 -14
- package/src/front-components/launch-process.front-component.mjs +75 -0
- package/src/front-components/launch-process.front-component.tsx +270 -0
- package/src/front-components/process-steps.front-component.mjs +26 -26
- package/src/front-components/process-steps.front-component.tsx +56 -10
- package/src/front-components/task-form.front-component.mjs +45 -44
- package/src/front-components/task-form.front-component.tsx +12 -1
- package/src/logic-functions/bitrix-app.logic-function.mjs +186 -88
- package/src/logic-functions/bitrix-app.logic-function.ts +24 -61
- package/src/logic-functions/bitrix-event.logic-function.mjs +22 -1
- package/src/logic-functions/bitrix-event.logic-function.ts +2 -2
- package/src/logic-functions/bitrix-install.logic-function.mjs +26 -12
- package/src/logic-functions/bitrix-install.logic-function.ts +2 -5
- package/src/logic-functions/complete-task.logic-function.mjs +22 -1
- package/src/logic-functions/complete-task.logic-function.ts +2 -2
- package/src/logic-functions/docflow-app-session.logic-function.mjs +60064 -0
- package/src/logic-functions/docflow-app-session.logic-function.ts +54 -0
- package/src/logic-functions/docflow-app.logic-function.mjs +60149 -0
- package/src/logic-functions/docflow-app.logic-function.ts +38 -0
- package/src/logic-functions/launch-process.logic-function.mjs +134 -40
- package/src/logic-functions/launch-process.logic-function.ts +85 -24
- package/src/logic-functions/publish-process.logic-function.mjs +32 -11
- package/src/logic-functions/publish-process.logic-function.ts +8 -4
- package/src/logic-functions/save-launch-fields.logic-function.mjs +194 -17
- package/src/logic-functions/save-launch-fields.logic-function.ts +26 -8
- package/src/logic-functions/save-step-settings.logic-function.mjs +24 -2
- package/src/logic-functions/save-step-settings.logic-function.ts +2 -2
- package/src/logic-functions/save-task-form.logic-function.mjs +22 -1
- package/src/logic-functions/save-task-form.logic-function.ts +2 -2
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defineLogicFunction } from 'twenty-sdk/define';
|
|
2
|
+
import {
|
|
3
|
+
type LogicFunctionExecutionContext,
|
|
4
|
+
type RoutePayload,
|
|
5
|
+
} from 'twenty-sdk/logic-function';
|
|
6
|
+
|
|
7
|
+
import { readDocflowAppSection } from 'src/shared/app/docflow-app.util';
|
|
8
|
+
import { getCrmUrl } from 'src/server/crm-url';
|
|
9
|
+
import { renderDocflowAppPage } from 'src/server/docflow-app-page';
|
|
10
|
+
import { withRouteErrors } from 'src/server/http-response';
|
|
11
|
+
import { DOCFLOW_APP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
12
|
+
|
|
13
|
+
// The standalone docflow page: tasks, processes and documents without the
|
|
14
|
+
// rest of the CRM. It needs no sign-in to load: the page itself signs in with
|
|
15
|
+
// the CRM session (see /docflow/app/session). `?connect=1` is the popup that
|
|
16
|
+
// signs in the page framed in Bitrix24.
|
|
17
|
+
const handler = async (event: RoutePayload, context: LogicFunctionExecutionContext) =>
|
|
18
|
+
renderDocflowAppPage({
|
|
19
|
+
workspaceId: context.workspaceId,
|
|
20
|
+
config: {
|
|
21
|
+
mode: event.queryStringParameters.connect === '1' ? 'connect' : 'crm',
|
|
22
|
+
crmUrl: getCrmUrl() ?? '',
|
|
23
|
+
section: readDocflowAppSection(event.queryStringParameters.section),
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export default defineLogicFunction({
|
|
28
|
+
universalIdentifier: DOCFLOW_APP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
|
29
|
+
name: 'docflow-app',
|
|
30
|
+
description: 'Отдельная страница документооборота без остальной CRM',
|
|
31
|
+
timeoutSeconds: 10,
|
|
32
|
+
handler: withRouteErrors('docflow-app', handler),
|
|
33
|
+
httpRouteTriggerSettings: {
|
|
34
|
+
path: '/docflow/app',
|
|
35
|
+
httpMethod: 'GET',
|
|
36
|
+
isAuthRequired: false,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -1725,10 +1725,67 @@ var RUN_WORKFLOW_VERSION_MUTATION = `mutation RunWorkflowVersion($input: RunWork
|
|
|
1725
1725
|
runWorkflowVersion(input: $input) { workflowRunId }
|
|
1726
1726
|
}`;
|
|
1727
1727
|
|
|
1728
|
+
// src/shared/api/object-summaries.ts
|
|
1729
|
+
import { MetadataApiClient } from "twenty-client-sdk/metadata";
|
|
1730
|
+
var OBJECTS_PAGE_SIZE = 100;
|
|
1731
|
+
var getObjectSummaries = async () => {
|
|
1732
|
+
const client = new MetadataApiClient();
|
|
1733
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
1734
|
+
let after;
|
|
1735
|
+
do {
|
|
1736
|
+
const { objects } = await client.query({
|
|
1737
|
+
objects: {
|
|
1738
|
+
__args: {
|
|
1739
|
+
paging: { first: OBJECTS_PAGE_SIZE, ...after !== void 0 ? { after } : {} },
|
|
1740
|
+
filter: {}
|
|
1741
|
+
},
|
|
1742
|
+
pageInfo: { hasNextPage: true, endCursor: true },
|
|
1743
|
+
edges: {
|
|
1744
|
+
node: {
|
|
1745
|
+
nameSingular: true,
|
|
1746
|
+
namePlural: true,
|
|
1747
|
+
labelSingular: true,
|
|
1748
|
+
labelIdentifierFieldMetadataId: true,
|
|
1749
|
+
fieldsList: { id: true, name: true }
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
});
|
|
1754
|
+
for (const { node } of objects.edges) {
|
|
1755
|
+
summaries.set(node.nameSingular, {
|
|
1756
|
+
nameSingular: node.nameSingular,
|
|
1757
|
+
namePlural: node.namePlural,
|
|
1758
|
+
labelSingular: node.labelSingular,
|
|
1759
|
+
labelFieldName: node.fieldsList?.find(
|
|
1760
|
+
(field) => field.id === node.labelIdentifierFieldMetadataId
|
|
1761
|
+
)?.name
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
after = objects.pageInfo.hasNextPage === true ? objects.pageInfo.endCursor ?? void 0 : void 0;
|
|
1765
|
+
} while (after !== void 0);
|
|
1766
|
+
return summaries;
|
|
1767
|
+
};
|
|
1768
|
+
|
|
1769
|
+
// src/shared/api/rest-response.util.ts
|
|
1770
|
+
var readDataValue = (response) => {
|
|
1771
|
+
const data = response?.data;
|
|
1772
|
+
if (typeof data !== "object" || data === null) {
|
|
1773
|
+
return void 0;
|
|
1774
|
+
}
|
|
1775
|
+
return Object.values(data)[0];
|
|
1776
|
+
};
|
|
1777
|
+
var readRestRecord = (response) => {
|
|
1778
|
+
const value = readDataValue(response);
|
|
1779
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1780
|
+
};
|
|
1781
|
+
|
|
1728
1782
|
// src/shared/docflow.constants.ts
|
|
1729
1783
|
var DOCFLOW_SECTION = "DOCFLOW";
|
|
1730
1784
|
var DOCFLOW_LAUNCH_PARAMETERS_KEY = "smbDocflowParameters";
|
|
1731
1785
|
var LAUNCH_PARAMETERS_STEP_INPUT = `{{trigger.${DOCFLOW_LAUNCH_PARAMETERS_KEY}}}`;
|
|
1786
|
+
var LAUNCH_TRIGGER_NAME = "\u0417\u0430\u043F\u0443\u0441\u043A \u0441 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u0430\u043C\u0438";
|
|
1787
|
+
var LAUNCH_RECORD_KEY = "record";
|
|
1788
|
+
var LAUNCH_RECORDS_LIMIT = 50;
|
|
1732
1789
|
var FORM_FIELD_REQUIRED_SETTING = "smbIsRequired";
|
|
1733
1790
|
var FORM_FIELD_ROLE_SETTING = "smbRole";
|
|
1734
1791
|
|
|
@@ -1766,7 +1823,9 @@ var parseFormFields = (value) => Array.isArray(value) ? value.filter(
|
|
|
1766
1823
|
) : [];
|
|
1767
1824
|
|
|
1768
1825
|
// src/shared/workflow/launch-step.util.ts
|
|
1826
|
+
var isLaunchTrigger = (trigger) => trigger?.type === "WEBHOOK" && trigger.name === LAUNCH_TRIGGER_NAME;
|
|
1769
1827
|
var parseLaunchFields = (value) => getFillableFormFields(parseFormFields(value));
|
|
1828
|
+
var parseLaunchObject = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
1770
1829
|
var buildLaunchParameters = ({
|
|
1771
1830
|
fields,
|
|
1772
1831
|
values
|
|
@@ -1776,9 +1835,10 @@ var buildLaunchParameters = ({
|
|
|
1776
1835
|
return [field.name, value === void 0 || value === "" ? null : value];
|
|
1777
1836
|
})
|
|
1778
1837
|
);
|
|
1779
|
-
var buildLaunchPayload = (parameters) => ({
|
|
1838
|
+
var buildLaunchPayload = (parameters, record) => ({
|
|
1780
1839
|
...parameters,
|
|
1781
|
-
[DOCFLOW_LAUNCH_PARAMETERS_KEY]: parameters
|
|
1840
|
+
[DOCFLOW_LAUNCH_PARAMETERS_KEY]: parameters,
|
|
1841
|
+
...record !== void 0 ? { [LAUNCH_RECORD_KEY]: record } : {}
|
|
1782
1842
|
});
|
|
1783
1843
|
|
|
1784
1844
|
// node_modules/twenty-sdk/dist/logic-function/isDefined-Dtu5EYqP-CMNeFg4z.mjs
|
|
@@ -119420,24 +119480,33 @@ var AL = class {
|
|
|
119420
119480
|
}
|
|
119421
119481
|
};
|
|
119422
119482
|
|
|
119483
|
+
// src/shared/log.util.ts
|
|
119484
|
+
var PREFIX = "[docflow]";
|
|
119485
|
+
var describeError = (error) => {
|
|
119486
|
+
if (error instanceof Error) {
|
|
119487
|
+
const { status, body } = error;
|
|
119488
|
+
return { name: error.name, message: error.message, status, body };
|
|
119489
|
+
}
|
|
119490
|
+
return error;
|
|
119491
|
+
};
|
|
119492
|
+
var logDocflow = (event, details) => console.info(PREFIX, event, details ?? "");
|
|
119493
|
+
var logDocflowError = (event, error, details) => console.error(PREFIX, event, describeError(error), details ?? "");
|
|
119494
|
+
|
|
119423
119495
|
// src/server/http-response.ts
|
|
119424
119496
|
var jsonResponse = (status, body) => new AL(JSON.stringify(body), {
|
|
119425
119497
|
status,
|
|
119426
119498
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
119427
119499
|
});
|
|
119428
119500
|
var readJsonBody = (body) => typeof body === "object" && body !== null && !Array.isArray(body) ? body : {};
|
|
119429
|
-
|
|
119430
|
-
|
|
119431
|
-
|
|
119432
|
-
|
|
119433
|
-
|
|
119434
|
-
return
|
|
119501
|
+
var withRouteErrors = (name, handler2) => async (...args) => {
|
|
119502
|
+
try {
|
|
119503
|
+
return await handler2(...args);
|
|
119504
|
+
} catch (error) {
|
|
119505
|
+
logDocflowError(`${name}: unexpected error`, error);
|
|
119506
|
+
return jsonResponse(500, {
|
|
119507
|
+
message: `\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442\u0430: ${error instanceof Error ? error.message : String(error)}`
|
|
119508
|
+
});
|
|
119435
119509
|
}
|
|
119436
|
-
return Object.values(data)[0];
|
|
119437
|
-
};
|
|
119438
|
-
var readRestRecord = (response) => {
|
|
119439
|
-
const value = readDataValue(response);
|
|
119440
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
119441
119510
|
};
|
|
119442
119511
|
|
|
119443
119512
|
// src/server/records.ts
|
|
@@ -119469,22 +119538,11 @@ var runWorkflowVersion = async ({
|
|
|
119469
119538
|
return data.runWorkflowVersion.workflowRunId;
|
|
119470
119539
|
};
|
|
119471
119540
|
|
|
119472
|
-
// src/shared/log.util.ts
|
|
119473
|
-
var PREFIX = "[docflow]";
|
|
119474
|
-
var describeError = (error) => {
|
|
119475
|
-
if (error instanceof Error) {
|
|
119476
|
-
const { status, body } = error;
|
|
119477
|
-
return { name: error.name, message: error.message, status, body };
|
|
119478
|
-
}
|
|
119479
|
-
return error;
|
|
119480
|
-
};
|
|
119481
|
-
var logDocflow = (event, details) => console.info(PREFIX, event, details ?? "");
|
|
119482
|
-
var logDocflowError = (event, error, details) => console.error(PREFIX, event, describeError(error), details ?? "");
|
|
119483
|
-
|
|
119484
119541
|
// src/universal-identifiers.ts
|
|
119485
119542
|
var LAUNCH_PROCESS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER = "de04e9c8-7ebe-4ac2-b4b3-4ca256eb94db";
|
|
119486
119543
|
|
|
119487
119544
|
// src/logic-functions/launch-process.logic-function.ts
|
|
119545
|
+
var readRecordIds = (value) => Array.isArray(value) ? [...new Set(value.filter((id3) => typeof id3 === "string" && id3.length > 0))] : [];
|
|
119488
119546
|
var handler = async (event, context) => {
|
|
119489
119547
|
if (context.workspaceMemberId === null) {
|
|
119490
119548
|
return jsonResponse(401, { message: "\u0417\u0430\u043F\u0443\u0441\u043A\u0430\u0442\u044C \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u044B \u043C\u043E\u0436\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A" });
|
|
@@ -119492,6 +119550,7 @@ var handler = async (event, context) => {
|
|
|
119492
119550
|
const body = readJsonBody(event.body);
|
|
119493
119551
|
const workflowVersionId = body.workflowVersionId;
|
|
119494
119552
|
const values = readJsonBody(body.values);
|
|
119553
|
+
const recordIds = readRecordIds(body.recordIds);
|
|
119495
119554
|
if (typeof workflowVersionId !== "string" || workflowVersionId.length === 0) {
|
|
119496
119555
|
return jsonResponse(400, { message: "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D \u043F\u0440\u043E\u0446\u0435\u0441\u0441" });
|
|
119497
119556
|
}
|
|
@@ -119512,28 +119571,63 @@ var handler = async (event, context) => {
|
|
|
119512
119571
|
missingFieldNames: missingFields.map(({ name }) => name)
|
|
119513
119572
|
});
|
|
119514
119573
|
}
|
|
119515
|
-
const
|
|
119516
|
-
|
|
119517
|
-
|
|
119518
|
-
|
|
119519
|
-
|
|
119520
|
-
|
|
119521
|
-
}
|
|
119522
|
-
|
|
119523
|
-
|
|
119524
|
-
|
|
119525
|
-
|
|
119526
|
-
|
|
119574
|
+
const launchObjectName = parseLaunchObject(workflow.smbLaunchObject);
|
|
119575
|
+
const isLaunchedOnRecords = launchObjectName !== void 0 && isLaunchTrigger(version.trigger);
|
|
119576
|
+
let records = [void 0];
|
|
119577
|
+
if (isLaunchedOnRecords) {
|
|
119578
|
+
if (recordIds.length === 0) {
|
|
119579
|
+
return jsonResponse(422, { message: "\u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F \u043D\u0430 \u0437\u0430\u043F\u0438\u0441\u0438: \u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0437\u0430\u043F\u0438\u0441\u044C" });
|
|
119580
|
+
}
|
|
119581
|
+
if (recordIds.length > LAUNCH_RECORDS_LIMIT) {
|
|
119582
|
+
return jsonResponse(422, { message: `\u0417\u0430 \u043E\u0434\u0438\u043D \u0440\u0430\u0437 \u2014 \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${LAUNCH_RECORDS_LIMIT} \u0437\u0430\u043F\u0438\u0441\u0435\u0439` });
|
|
119583
|
+
}
|
|
119584
|
+
const namePlural = (await getObjectSummaries()).get(launchObjectName)?.namePlural;
|
|
119585
|
+
if (namePlural === void 0) {
|
|
119586
|
+
return jsonResponse(404, { message: "\u041E\u0431\u044A\u0435\u043A\u0442, \u043D\u0430 \u043A\u043E\u0442\u043E\u0440\u043E\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u043E\u0446\u0435\u0441\u0441, \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D" });
|
|
119587
|
+
}
|
|
119588
|
+
records = await Promise.all(
|
|
119589
|
+
recordIds.map(async (recordId) => {
|
|
119590
|
+
try {
|
|
119591
|
+
return readRestRecord(
|
|
119592
|
+
await restClient("person").get(`/rest/${namePlural}/${recordId}`, { query: { depth: 0 } })
|
|
119593
|
+
);
|
|
119594
|
+
} catch (error) {
|
|
119595
|
+
logDocflowError("launch: record not read", error, { namePlural, recordId });
|
|
119596
|
+
return void 0;
|
|
119597
|
+
}
|
|
119598
|
+
})
|
|
119599
|
+
);
|
|
119600
|
+
if (records.some((record) => record === void 0)) {
|
|
119601
|
+
return jsonResponse(404, { message: "\u0417\u0430\u043F\u0438\u0441\u044C \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430" });
|
|
119602
|
+
}
|
|
119603
|
+
}
|
|
119604
|
+
const workflowRunIds = [];
|
|
119605
|
+
for (const record of records) {
|
|
119606
|
+
const workflowRunId = crypto.randomUUID();
|
|
119607
|
+
try {
|
|
119608
|
+
await runWorkflowVersion({
|
|
119609
|
+
workflowVersionId,
|
|
119610
|
+
workflowRunId,
|
|
119611
|
+
payload: fields.length > 0 || record !== void 0 ? buildLaunchPayload(parameters, record) : {}
|
|
119612
|
+
});
|
|
119613
|
+
} catch (error) {
|
|
119614
|
+
logDocflowError("launch: run failed", error, { workflowVersionId, recordId: record?.id });
|
|
119615
|
+
return jsonResponse(403, {
|
|
119616
|
+
message: error instanceof CoreGraphqlError && error.userMessage !== void 0 ? error.userMessage : "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u043F\u0440\u043E\u0446\u0435\u0441\u0441",
|
|
119617
|
+
workflowRunIds
|
|
119618
|
+
});
|
|
119619
|
+
}
|
|
119620
|
+
workflowRunIds.push(workflowRunId);
|
|
119527
119621
|
}
|
|
119528
|
-
logDocflow("launch: started", { workflowVersionId,
|
|
119529
|
-
return { workflowRunId };
|
|
119622
|
+
logDocflow("launch: started", { workflowVersionId, workflowRunIds, parameters, recordIds });
|
|
119623
|
+
return { workflowRunId: workflowRunIds[0], workflowRunIds };
|
|
119530
119624
|
};
|
|
119531
119625
|
var launch_process_logic_function_default = defineLogicFunction({
|
|
119532
119626
|
universalIdentifier: LAUNCH_PROCESS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
|
119533
119627
|
name: "docflow-launch-process",
|
|
119534
119628
|
description: "\u0417\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442\u0430 \u0441 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u0430\u043C\u0438",
|
|
119535
119629
|
timeoutSeconds: 30,
|
|
119536
|
-
handler,
|
|
119630
|
+
handler: withRouteErrors("launch-process", handler),
|
|
119537
119631
|
httpRouteTriggerSettings: {
|
|
119538
119632
|
path: "/docflow/processes/launch",
|
|
119539
119633
|
httpMethod: "POST",
|
|
@@ -5,22 +5,33 @@ import {
|
|
|
5
5
|
} from 'twenty-sdk/logic-function';
|
|
6
6
|
|
|
7
7
|
import { CoreGraphqlError } from 'src/shared/api/core-graphql.util';
|
|
8
|
-
import {
|
|
8
|
+
import { getObjectSummaries } from 'src/shared/api/object-summaries';
|
|
9
|
+
import { readRestRecord } from 'src/shared/api/rest-response.util';
|
|
10
|
+
import { DOCFLOW_SECTION, LAUNCH_RECORDS_LIMIT } from 'src/shared/docflow.constants';
|
|
9
11
|
import { findMissingRequiredFields } from 'src/shared/form/form-fields.util';
|
|
10
12
|
import {
|
|
11
13
|
buildLaunchParameters,
|
|
12
14
|
buildLaunchPayload,
|
|
15
|
+
isLaunchTrigger,
|
|
13
16
|
parseLaunchFields,
|
|
17
|
+
parseLaunchObject,
|
|
14
18
|
} from 'src/shared/workflow/launch-step.util';
|
|
15
|
-
import { readJsonBody, jsonResponse } from 'src/server/http-response';
|
|
16
|
-
import { getWorkflow, getWorkflowVersion } from 'src/server/records';
|
|
19
|
+
import { readJsonBody, jsonResponse, withRouteErrors } from 'src/server/http-response';
|
|
20
|
+
import { getWorkflow, getWorkflowVersion, restClient } from 'src/server/records';
|
|
17
21
|
import { runWorkflowVersion } from 'src/server/workflow-engine';
|
|
18
22
|
import { logDocflow, logDocflowError } from 'src/shared/log.util';
|
|
19
23
|
import { LAUNCH_PROCESS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
20
24
|
|
|
25
|
+
const readRecordIds = (value: unknown): string[] =>
|
|
26
|
+
Array.isArray(value)
|
|
27
|
+
? [...new Set(value.filter((id): id is string => typeof id === 'string' && id.length > 0))]
|
|
28
|
+
: [];
|
|
29
|
+
|
|
21
30
|
// Launch with parameters: they are checked here and ride in the run payload,
|
|
22
|
-
// so they stay in the run as its trigger output
|
|
23
|
-
// the
|
|
31
|
+
// so they stay in the run as its trigger output ({{trigger.<field>}}). A
|
|
32
|
+
// process launched on records runs once per record, the record read as the
|
|
33
|
+
// person and put into its run as {{trigger.record}}, like the standard manual
|
|
34
|
+
// launch on a selection does.
|
|
24
35
|
const handler = async (
|
|
25
36
|
event: RoutePayload,
|
|
26
37
|
context: LogicFunctionExecutionContext,
|
|
@@ -32,6 +43,7 @@ const handler = async (
|
|
|
32
43
|
const body = readJsonBody(event.body);
|
|
33
44
|
const workflowVersionId = body.workflowVersionId;
|
|
34
45
|
const values = readJsonBody(body.values);
|
|
46
|
+
const recordIds = readRecordIds(body.recordIds);
|
|
35
47
|
|
|
36
48
|
if (typeof workflowVersionId !== 'string' || workflowVersionId.length === 0) {
|
|
37
49
|
return jsonResponse(400, { message: 'Не указан процесс' });
|
|
@@ -63,28 +75,77 @@ const handler = async (
|
|
|
63
75
|
});
|
|
64
76
|
}
|
|
65
77
|
|
|
66
|
-
const
|
|
78
|
+
const launchObjectName = parseLaunchObject(workflow.smbLaunchObject);
|
|
79
|
+
// The record's object comes from the published trigger: the field may
|
|
80
|
+
// already hold the object of a draft not published yet.
|
|
81
|
+
const isLaunchedOnRecords = launchObjectName !== undefined && isLaunchTrigger(version.trigger);
|
|
82
|
+
let records: (Record<string, unknown> | undefined)[] = [undefined];
|
|
83
|
+
|
|
84
|
+
if (isLaunchedOnRecords) {
|
|
85
|
+
if (recordIds.length === 0) {
|
|
86
|
+
return jsonResponse(422, { message: 'Процесс запускается на записи: выберите запись' });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (recordIds.length > LAUNCH_RECORDS_LIMIT) {
|
|
90
|
+
return jsonResponse(422, { message: `За один раз — не больше ${LAUNCH_RECORDS_LIMIT} записей` });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const namePlural = (await getObjectSummaries()).get(launchObjectName)?.namePlural;
|
|
94
|
+
|
|
95
|
+
if (namePlural === undefined) {
|
|
96
|
+
return jsonResponse(404, { message: 'Объект, на котором запускается процесс, не найден' });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Read as the person: a record they cannot see does not start the process
|
|
100
|
+
records = await Promise.all(
|
|
101
|
+
recordIds.map(async (recordId) => {
|
|
102
|
+
try {
|
|
103
|
+
return readRestRecord<Record<string, unknown>>(
|
|
104
|
+
await restClient('person').get(`/rest/${namePlural}/${recordId}`, { query: { depth: 0 } }),
|
|
105
|
+
);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
logDocflowError('launch: record not read', error, { namePlural, recordId });
|
|
108
|
+
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (records.some((record) => record === undefined)) {
|
|
115
|
+
return jsonResponse(404, { message: 'Запись не найдена' });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
67
118
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
119
|
+
const workflowRunIds: string[] = [];
|
|
120
|
+
|
|
121
|
+
for (const record of records) {
|
|
122
|
+
const workflowRunId = crypto.randomUUID();
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await runWorkflowVersion({
|
|
126
|
+
workflowVersionId,
|
|
127
|
+
workflowRunId,
|
|
128
|
+
payload:
|
|
129
|
+
fields.length > 0 || record !== undefined ? buildLaunchPayload(parameters, record) : {},
|
|
130
|
+
});
|
|
131
|
+
} catch (error) {
|
|
132
|
+
logDocflowError('launch: run failed', error, { workflowVersionId, recordId: record?.id });
|
|
133
|
+
|
|
134
|
+
return jsonResponse(403, {
|
|
135
|
+
message:
|
|
136
|
+
error instanceof CoreGraphqlError && error.userMessage !== undefined
|
|
137
|
+
? error.userMessage
|
|
138
|
+
: 'Не удалось запустить процесс',
|
|
139
|
+
workflowRunIds,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
workflowRunIds.push(workflowRunId);
|
|
83
144
|
}
|
|
84
145
|
|
|
85
|
-
logDocflow('launch: started', { workflowVersionId,
|
|
146
|
+
logDocflow('launch: started', { workflowVersionId, workflowRunIds, parameters, recordIds });
|
|
86
147
|
|
|
87
|
-
return { workflowRunId };
|
|
148
|
+
return { workflowRunId: workflowRunIds[0], workflowRunIds };
|
|
88
149
|
};
|
|
89
150
|
|
|
90
151
|
export default defineLogicFunction({
|
|
@@ -92,7 +153,7 @@ export default defineLogicFunction({
|
|
|
92
153
|
name: 'docflow-launch-process',
|
|
93
154
|
description: 'Запускает процесс документооборота с параметрами',
|
|
94
155
|
timeoutSeconds: 30,
|
|
95
|
-
handler,
|
|
156
|
+
handler: withRouteErrors('launch-process', handler),
|
|
96
157
|
httpRouteTriggerSettings: {
|
|
97
158
|
path: '/docflow/processes/launch',
|
|
98
159
|
httpMethod: 'POST',
|
|
@@ -119372,12 +119372,34 @@ var AL = class {
|
|
|
119372
119372
|
}
|
|
119373
119373
|
};
|
|
119374
119374
|
|
|
119375
|
+
// src/shared/log.util.ts
|
|
119376
|
+
var PREFIX = "[docflow]";
|
|
119377
|
+
var describeError = (error) => {
|
|
119378
|
+
if (error instanceof Error) {
|
|
119379
|
+
const { status, body } = error;
|
|
119380
|
+
return { name: error.name, message: error.message, status, body };
|
|
119381
|
+
}
|
|
119382
|
+
return error;
|
|
119383
|
+
};
|
|
119384
|
+
var logDocflow = (event, details) => console.info(PREFIX, event, details ?? "");
|
|
119385
|
+
var logDocflowError = (event, error, details) => console.error(PREFIX, event, describeError(error), details ?? "");
|
|
119386
|
+
|
|
119375
119387
|
// src/server/http-response.ts
|
|
119376
119388
|
var jsonResponse = (status, body) => new AL(JSON.stringify(body), {
|
|
119377
119389
|
status,
|
|
119378
119390
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
119379
119391
|
});
|
|
119380
119392
|
var readJsonBody = (body) => typeof body === "object" && body !== null && !Array.isArray(body) ? body : {};
|
|
119393
|
+
var withRouteErrors = (name, handler2) => async (...args) => {
|
|
119394
|
+
try {
|
|
119395
|
+
return await handler2(...args);
|
|
119396
|
+
} catch (error) {
|
|
119397
|
+
logDocflowError(`${name}: unexpected error`, error);
|
|
119398
|
+
return jsonResponse(500, {
|
|
119399
|
+
message: `\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442\u0430: ${error instanceof Error ? error.message : String(error)}`
|
|
119400
|
+
});
|
|
119401
|
+
}
|
|
119402
|
+
};
|
|
119381
119403
|
|
|
119382
119404
|
// src/shared/api/rest-filter.util.ts
|
|
119383
119405
|
var UNSAFE_FILTER_CHARACTERS = /[()[\]'",]/g;
|
|
@@ -119425,17 +119447,16 @@ var listWorkflowVersions = async (runAs, workflowId) => readRestList(
|
|
|
119425
119447
|
})
|
|
119426
119448
|
).records;
|
|
119427
119449
|
|
|
119428
|
-
// src/shared/
|
|
119429
|
-
var
|
|
119430
|
-
var
|
|
119431
|
-
|
|
119432
|
-
|
|
119433
|
-
return
|
|
119450
|
+
// src/shared/workflow/publish-error.util.ts
|
|
119451
|
+
var INVALID_STEP_PATTERN = /Step "([^"]+)" configuration is invalid/g;
|
|
119452
|
+
var describeUnfinishedSteps = (message) => {
|
|
119453
|
+
const stepNames = [...new Set([...message.matchAll(INVALID_STEP_PATTERN)].map(([, name]) => name))];
|
|
119454
|
+
if (stepNames.length === 0) {
|
|
119455
|
+
return void 0;
|
|
119434
119456
|
}
|
|
119435
|
-
|
|
119457
|
+
const steps = stepNames.map((name) => `\xAB${name}\xBB`).join(", ");
|
|
119458
|
+
return stepNames.length === 1 ? `\u0428\u0430\u0433 ${steps} \u043D\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D \u0434\u043E \u043A\u043E\u043D\u0446\u0430. \u041E\u0442\u043A\u0440\u043E\u0439\u0442\u0435 \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u0432 \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440\u0435 \u0438 \u0437\u0430\u043F\u043E\u043B\u043D\u0438\u0442\u0435 \u0435\u0433\u043E.` : `\u0428\u0430\u0433\u0438 ${steps} \u043D\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D\u044B \u0434\u043E \u043A\u043E\u043D\u0446\u0430. \u041E\u0442\u043A\u0440\u043E\u0439\u0442\u0435 \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u0432 \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440\u0435 \u0438 \u0437\u0430\u043F\u043E\u043B\u043D\u0438\u0442\u0435 \u0438\u0445.`;
|
|
119436
119459
|
};
|
|
119437
|
-
var logDocflow = (event, details) => console.info(PREFIX, event, details ?? "");
|
|
119438
|
-
var logDocflowError = (event, error, details) => console.error(PREFIX, event, describeError(error), details ?? "");
|
|
119439
119460
|
|
|
119440
119461
|
// src/universal-identifiers.ts
|
|
119441
119462
|
var PUBLISH_PROCESS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER = "c164c184-3b24-4d26-832b-219f3f02102a";
|
|
@@ -119471,7 +119492,7 @@ var handler = async (event, context) => {
|
|
|
119471
119492
|
} catch (error) {
|
|
119472
119493
|
logDocflowError("publish: engine refused", error, { workflowId, isPublish });
|
|
119473
119494
|
return jsonResponse(422, {
|
|
119474
|
-
message: error instanceof CoreGraphqlError && error.userMessage !== void 0 ? error.userMessage : isPublish ? "\u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u043D\u0435 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D: \u043F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u0435\u0433\u043E \u0448\u0430\u0433\u0438 \u0432 \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440\u0435" : "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043D\u044F\u0442\u044C \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u0441 \u043F\u0443\u0431\u043B\u0438\u043A\u0430\u0446\u0438\u0438"
|
|
119495
|
+
message: (isPublish && error instanceof CoreGraphqlError ? describeUnfinishedSteps(`${error.message} ${error.userMessage ?? ""}`) : void 0) ?? (error instanceof CoreGraphqlError && error.userMessage !== void 0 ? error.userMessage : isPublish ? "\u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u043D\u0435 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D: \u043F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u0435\u0433\u043E \u0448\u0430\u0433\u0438 \u0432 \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440\u0435" : "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043D\u044F\u0442\u044C \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u0441 \u043F\u0443\u0431\u043B\u0438\u043A\u0430\u0446\u0438\u0438")
|
|
119475
119496
|
});
|
|
119476
119497
|
}
|
|
119477
119498
|
logDocflow("publish: done", { workflowId, workflowVersionId: version.id, isPublish });
|
|
@@ -119482,7 +119503,7 @@ var publish_process_logic_function_default = defineLogicFunction({
|
|
|
119482
119503
|
name: "docflow-publish-process",
|
|
119483
119504
|
description: "\u041F\u0443\u0431\u043B\u0438\u043A\u0443\u0435\u0442 \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442\u0430 \u0438\u043B\u0438 \u0441\u043D\u0438\u043C\u0430\u0435\u0442 \u0435\u0433\u043E \u0441 \u043F\u0443\u0431\u043B\u0438\u043A\u0430\u0446\u0438\u0438",
|
|
119484
119505
|
timeoutSeconds: 30,
|
|
119485
|
-
handler,
|
|
119506
|
+
handler: withRouteErrors("publish-process", handler),
|
|
119486
119507
|
httpRouteTriggerSettings: {
|
|
119487
119508
|
path: "/docflow/processes/publish",
|
|
119488
119509
|
httpMethod: "POST",
|
|
@@ -11,9 +11,10 @@ import {
|
|
|
11
11
|
DEACTIVATE_WORKFLOW_VERSION_MUTATION,
|
|
12
12
|
} from 'src/shared/api/core-graphql.util';
|
|
13
13
|
import { DOCFLOW_SECTION } from 'src/shared/docflow.constants';
|
|
14
|
-
import { jsonResponse, readJsonBody } from 'src/server/http-response';
|
|
14
|
+
import { jsonResponse, readJsonBody, withRouteErrors } from 'src/server/http-response';
|
|
15
15
|
import { getWorkflow, listWorkflowVersions } from 'src/server/records';
|
|
16
16
|
import { logDocflow, logDocflowError } from 'src/shared/log.util';
|
|
17
|
+
import { describeUnfinishedSteps } from 'src/shared/workflow/publish-error.util';
|
|
17
18
|
import { PUBLISH_PROCESS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
18
19
|
|
|
19
20
|
// "Опубликовать" and "Снять с публикации" from the processes page, as the
|
|
@@ -64,11 +65,14 @@ const handler = async (event: RoutePayload, context: LogicFunctionExecutionConte
|
|
|
64
65
|
|
|
65
66
|
return jsonResponse(422, {
|
|
66
67
|
message:
|
|
67
|
-
|
|
68
|
+
(isPublish && error instanceof CoreGraphqlError
|
|
69
|
+
? describeUnfinishedSteps(`${error.message} ${error.userMessage ?? ''}`)
|
|
70
|
+
: undefined) ??
|
|
71
|
+
(error instanceof CoreGraphqlError && error.userMessage !== undefined
|
|
68
72
|
? error.userMessage
|
|
69
73
|
: isPublish
|
|
70
74
|
? 'Процесс не опубликован: проверьте его шаги в редакторе'
|
|
71
|
-
: 'Не удалось снять процесс с публикации',
|
|
75
|
+
: 'Не удалось снять процесс с публикации'),
|
|
72
76
|
});
|
|
73
77
|
}
|
|
74
78
|
|
|
@@ -82,7 +86,7 @@ export default defineLogicFunction({
|
|
|
82
86
|
name: 'docflow-publish-process',
|
|
83
87
|
description: 'Публикует процесс документооборота или снимает его с публикации',
|
|
84
88
|
timeoutSeconds: 30,
|
|
85
|
-
handler,
|
|
89
|
+
handler: withRouteErrors('publish-process', handler),
|
|
86
90
|
httpRouteTriggerSettings: {
|
|
87
91
|
path: '/docflow/processes/publish',
|
|
88
92
|
httpMethod: 'POST',
|