@haiiro/docuflow 0.0.11 → 0.0.13

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.
Files changed (33) hide show
  1. package/README.md +45 -9
  2. package/manifest.json +117 -16
  3. package/package.json +6 -3
  4. package/public/app/docflow-app.css +2 -0
  5. package/public/app/docflow-app.js +94 -0
  6. package/src/front-components/docflow-documents.front-component.mjs +42 -42
  7. package/src/front-components/docflow-documents.front-component.tsx +336 -128
  8. package/src/front-components/docflow-processes.front-component.mjs +35 -35
  9. package/src/front-components/docflow-processes.front-component.tsx +527 -131
  10. package/src/front-components/docflow-tasks.front-component.mjs +46 -41
  11. package/src/front-components/docflow-tasks.front-component.tsx +489 -132
  12. package/src/front-components/launch-process.front-component.mjs +75 -0
  13. package/src/front-components/launch-process.front-component.tsx +270 -0
  14. package/src/front-components/process-steps.front-component.mjs +25 -25
  15. package/src/front-components/process-steps.front-component.tsx +55 -10
  16. package/src/front-components/task-form.front-component.mjs +46 -41
  17. package/src/logic-functions/bitrix-app.logic-function.mjs +424 -198
  18. package/src/logic-functions/bitrix-app.logic-function.ts +54 -82
  19. package/src/logic-functions/bitrix-install.logic-function.mjs +0 -11
  20. package/src/logic-functions/bitrix-install.logic-function.ts +0 -3
  21. package/src/logic-functions/capture-file.logic-function.mjs +2 -1
  22. package/src/logic-functions/docflow-app-session.logic-function.mjs +60043 -0
  23. package/src/logic-functions/docflow-app-session.logic-function.ts +54 -0
  24. package/src/logic-functions/docflow-app.logic-function.mjs +60122 -0
  25. package/src/logic-functions/docflow-app.logic-function.ts +37 -0
  26. package/src/logic-functions/hand-out-form-step.logic-function.mjs +2 -1
  27. package/src/logic-functions/launch-process.logic-function.mjs +113 -29
  28. package/src/logic-functions/launch-process.logic-function.ts +83 -22
  29. package/src/logic-functions/on-workflow-run-updated.logic-function.mjs +2 -1
  30. package/src/logic-functions/publish-process.logic-function.mjs +119519 -0
  31. package/src/logic-functions/publish-process.logic-function.ts +91 -0
  32. package/src/logic-functions/save-launch-fields.logic-function.mjs +178 -10
  33. package/src/logic-functions/save-launch-fields.logic-function.ts +24 -6
@@ -0,0 +1,37 @@
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 { DOCFLOW_APP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
11
+
12
+ // The standalone docflow page: tasks, processes and documents without the
13
+ // rest of the CRM. It needs no sign-in to load: the page itself signs in with
14
+ // the CRM session (see /docflow/app/session). `?connect=1` is the popup that
15
+ // signs in the page framed in Bitrix24.
16
+ const handler = async (event: RoutePayload, context: LogicFunctionExecutionContext) =>
17
+ renderDocflowAppPage({
18
+ workspaceId: context.workspaceId,
19
+ config: {
20
+ mode: event.queryStringParameters.connect === '1' ? 'connect' : 'crm',
21
+ crmUrl: getCrmUrl() ?? '',
22
+ section: readDocflowAppSection(event.queryStringParameters.section),
23
+ },
24
+ });
25
+
26
+ export default defineLogicFunction({
27
+ universalIdentifier: DOCFLOW_APP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
28
+ name: 'docflow-app',
29
+ description: 'Отдельная страница документооборота без остальной CRM',
30
+ timeoutSeconds: 10,
31
+ handler,
32
+ httpRouteTriggerSettings: {
33
+ path: '/docflow/app',
34
+ httpMethod: 'GET',
35
+ isAuthRequired: false,
36
+ },
37
+ });
@@ -59551,7 +59551,8 @@ var readRestList = (response) => {
59551
59551
  const value = readDataValue(response);
59552
59552
  return {
59553
59553
  records: Array.isArray(value) ? value : [],
59554
- pageInfo: response?.pageInfo ?? {}
59554
+ pageInfo: response?.pageInfo ?? {},
59555
+ totalCount: response?.totalCount
59555
59556
  };
59556
59557
  };
59557
59558
  var readRestRecord = (response) => {
@@ -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
@@ -119427,19 +119487,6 @@ var jsonResponse = (status, body) => new AL(JSON.stringify(body), {
119427
119487
  });
119428
119488
  var readJsonBody = (body) => typeof body === "object" && body !== null && !Array.isArray(body) ? body : {};
119429
119489
 
119430
- // src/shared/api/rest-response.util.ts
119431
- var readDataValue = (response) => {
119432
- const data = response?.data;
119433
- if (typeof data !== "object" || data === null) {
119434
- return void 0;
119435
- }
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
- };
119442
-
119443
119490
  // src/server/records.ts
119444
119491
  var clients = {
119445
119492
  person: new u2(),
@@ -119485,6 +119532,7 @@ var logDocflowError = (event, error, details) => console.error(PREFIX, event, de
119485
119532
  var LAUNCH_PROCESS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER = "de04e9c8-7ebe-4ac2-b4b3-4ca256eb94db";
119486
119533
 
119487
119534
  // src/logic-functions/launch-process.logic-function.ts
119535
+ var readRecordIds = (value) => Array.isArray(value) ? [...new Set(value.filter((id3) => typeof id3 === "string" && id3.length > 0))] : [];
119488
119536
  var handler = async (event, context) => {
119489
119537
  if (context.workspaceMemberId === null) {
119490
119538
  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 +119540,7 @@ var handler = async (event, context) => {
119492
119540
  const body = readJsonBody(event.body);
119493
119541
  const workflowVersionId = body.workflowVersionId;
119494
119542
  const values = readJsonBody(body.values);
119543
+ const recordIds = readRecordIds(body.recordIds);
119495
119544
  if (typeof workflowVersionId !== "string" || workflowVersionId.length === 0) {
119496
119545
  return jsonResponse(400, { message: "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D \u043F\u0440\u043E\u0446\u0435\u0441\u0441" });
119497
119546
  }
@@ -119512,21 +119561,56 @@ var handler = async (event, context) => {
119512
119561
  missingFieldNames: missingFields.map(({ name }) => name)
119513
119562
  });
119514
119563
  }
119515
- const workflowRunId = crypto.randomUUID();
119516
- try {
119517
- await runWorkflowVersion({
119518
- workflowVersionId,
119519
- workflowRunId,
119520
- payload: fields.length > 0 ? buildLaunchPayload(parameters) : {}
119521
- });
119522
- } catch (error) {
119523
- logDocflowError("launch: run failed", error, { workflowVersionId });
119524
- return jsonResponse(403, {
119525
- 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"
119526
- });
119564
+ const launchObjectName = parseLaunchObject(workflow.smbLaunchObject);
119565
+ const isLaunchedOnRecords = launchObjectName !== void 0 && isLaunchTrigger(version.trigger);
119566
+ let records = [void 0];
119567
+ if (isLaunchedOnRecords) {
119568
+ if (recordIds.length === 0) {
119569
+ 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" });
119570
+ }
119571
+ if (recordIds.length > LAUNCH_RECORDS_LIMIT) {
119572
+ 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` });
119573
+ }
119574
+ const namePlural = (await getObjectSummaries()).get(launchObjectName)?.namePlural;
119575
+ if (namePlural === void 0) {
119576
+ 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" });
119577
+ }
119578
+ records = await Promise.all(
119579
+ recordIds.map(async (recordId) => {
119580
+ try {
119581
+ return readRestRecord(
119582
+ await restClient("person").get(`/rest/${namePlural}/${recordId}`, { query: { depth: 0 } })
119583
+ );
119584
+ } catch (error) {
119585
+ logDocflowError("launch: record not read", error, { namePlural, recordId });
119586
+ return void 0;
119587
+ }
119588
+ })
119589
+ );
119590
+ if (records.some((record) => record === void 0)) {
119591
+ return jsonResponse(404, { message: "\u0417\u0430\u043F\u0438\u0441\u044C \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430" });
119592
+ }
119593
+ }
119594
+ const workflowRunIds = [];
119595
+ for (const record of records) {
119596
+ const workflowRunId = crypto.randomUUID();
119597
+ try {
119598
+ await runWorkflowVersion({
119599
+ workflowVersionId,
119600
+ workflowRunId,
119601
+ payload: fields.length > 0 || record !== void 0 ? buildLaunchPayload(parameters, record) : {}
119602
+ });
119603
+ } catch (error) {
119604
+ logDocflowError("launch: run failed", error, { workflowVersionId, recordId: record?.id });
119605
+ return jsonResponse(403, {
119606
+ 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",
119607
+ workflowRunIds
119608
+ });
119609
+ }
119610
+ workflowRunIds.push(workflowRunId);
119527
119611
  }
119528
- logDocflow("launch: started", { workflowVersionId, workflowRunId, parameters });
119529
- return { workflowRunId };
119612
+ logDocflow("launch: started", { workflowVersionId, workflowRunIds, parameters, recordIds });
119613
+ return { workflowRunId: workflowRunIds[0], workflowRunIds };
119530
119614
  };
119531
119615
  var launch_process_logic_function_default = defineLogicFunction({
119532
119616
  universalIdentifier: LAUNCH_PROCESS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
@@ -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 { DOCFLOW_SECTION } from 'src/shared/docflow.constants';
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
19
  import { readJsonBody, jsonResponse } from 'src/server/http-response';
16
- import { getWorkflow, getWorkflowVersion } from 'src/server/records';
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 and reach later steps through
23
- // the "Launch with parameters" step.
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 workflowRunId = crypto.randomUUID();
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
- try {
69
- await runWorkflowVersion({
70
- workflowVersionId,
71
- workflowRunId,
72
- payload: fields.length > 0 ? buildLaunchPayload(parameters) : {},
73
- });
74
- } catch (error) {
75
- logDocflowError('launch: run failed', error, { workflowVersionId });
76
-
77
- return jsonResponse(403, {
78
- message:
79
- error instanceof CoreGraphqlError && error.userMessage !== undefined
80
- ? error.userMessage
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, workflowRunId, parameters });
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({
@@ -119461,7 +119461,8 @@ var readRestList = (response) => {
119461
119461
  const value = readDataValue(response);
119462
119462
  return {
119463
119463
  records: Array.isArray(value) ? value : [],
119464
- pageInfo: response?.pageInfo ?? {}
119464
+ pageInfo: response?.pageInfo ?? {},
119465
+ totalCount: response?.totalCount
119465
119466
  };
119466
119467
  };
119467
119468
  var readRestRecord = (response) => {