@haiiro/docuflow 0.0.1 → 0.0.3

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 (136) hide show
  1. package/{.twenty/output/manifest.json → manifest.json} +9 -9
  2. package/package.json +15 -3
  3. package/{.twenty/output/src → src}/front-components/docflow-documents.front-component.mjs +24 -24
  4. package/src/front-components/docflow-documents.front-component.tsx +21 -6
  5. package/{.twenty/output/src → src}/front-components/docflow-processes.front-component.mjs +18 -18
  6. package/{.twenty/output/src → src}/front-components/docflow-tasks.front-component.mjs +18 -18
  7. package/{.twenty/output/src → src}/front-components/process-steps.front-component.mjs +19 -19
  8. package/src/front-components/process-steps.front-component.tsx +6 -1
  9. package/{.twenty/output/src → src}/front-components/task-form.front-component.mjs +24 -24
  10. package/{.twenty/output/src → src}/logic-functions/launch-process.logic-function.mjs +14 -0
  11. package/src/logic-functions/launch-process.logic-function.ts +5 -0
  12. package/{.twenty/output/src → src}/logic-functions/save-launch-fields.logic-function.mjs +14 -0
  13. package/src/logic-functions/save-launch-fields.logic-function.ts +5 -0
  14. package/.gitignore +0 -43
  15. package/.nvmrc +0 -1
  16. package/.oxlintrc.json +0 -37
  17. package/.twenty/output/README.md +0 -128
  18. package/.twenty/output/package.json +0 -49
  19. package/.twenty/output/public/cover.svg +0 -15
  20. package/.twenty/output/public/logo.svg +0 -9
  21. package/.twenty/output/src/front-components/docflow-documents.front-component.tsx +0 -516
  22. package/.twenty/output/src/front-components/docflow-processes.front-component.tsx +0 -266
  23. package/.twenty/output/src/front-components/docflow-tasks.front-component.tsx +0 -303
  24. package/.twenty/output/src/front-components/process-steps.front-component.tsx +0 -286
  25. package/.twenty/output/src/front-components/task-form.front-component.tsx +0 -29
  26. package/.twenty/output/src/logic-functions/bitrix-app.logic-function.ts +0 -143
  27. package/.twenty/output/src/logic-functions/bitrix-event.logic-function.ts +0 -54
  28. package/.twenty/output/src/logic-functions/bitrix-install.logic-function.ts +0 -81
  29. package/.twenty/output/src/logic-functions/capture-file.logic-function.ts +0 -32
  30. package/.twenty/output/src/logic-functions/complete-task.logic-function.ts +0 -46
  31. package/.twenty/output/src/logic-functions/hand-out-form-step.logic-function.ts +0 -30
  32. package/.twenty/output/src/logic-functions/health-check.ts +0 -31
  33. package/.twenty/output/src/logic-functions/launch-parameters-step.logic-function.ts +0 -33
  34. package/.twenty/output/src/logic-functions/launch-process.logic-function.ts +0 -96
  35. package/.twenty/output/src/logic-functions/notify-bitrix-assignee.logic-function.ts +0 -85
  36. package/.twenty/output/src/logic-functions/on-attachment-created.logic-function.ts +0 -67
  37. package/.twenty/output/src/logic-functions/on-task-updated.logic-function.ts +0 -71
  38. package/.twenty/output/src/logic-functions/on-workflow-run-updated.logic-function.ts +0 -70
  39. package/.twenty/output/src/logic-functions/save-launch-fields.logic-function.ts +0 -73
  40. package/.twenty/output/src/logic-functions/save-step-settings.logic-function.ts +0 -90
  41. package/.twenty/output/src/logic-functions/save-task-form.logic-function.ts +0 -62
  42. package/.twenty/output/yarn.lock +0 -3292
  43. package/.yarn/install-state.gz +0 -0
  44. package/.yarnrc.yml +0 -1
  45. package/dist/tsconfig.spec.tsbuildinfo +0 -1
  46. package/src/__tests__/api-and-bitrix.test.ts +0 -89
  47. package/src/__tests__/form-fields.test.ts +0 -73
  48. package/src/__tests__/launch-parameters.test.ts +0 -75
  49. package/src/__tests__/step-settings.test.ts +0 -39
  50. package/src/__tests__/workflow-run.test.ts +0 -113
  51. package/src/application-config.ts +0 -58
  52. package/src/fields/company-documents.field.ts +0 -29
  53. package/src/fields/document-author.field.ts +0 -32
  54. package/src/fields/document-counterparty.field.ts +0 -32
  55. package/src/fields/document-folder-child-folders.field.ts +0 -23
  56. package/src/fields/document-folder-documents.field.ts +0 -24
  57. package/src/fields/document-folder-parent-folder.field.ts +0 -31
  58. package/src/fields/document-folder.field.ts +0 -32
  59. package/src/fields/document-responsible.field.ts +0 -32
  60. package/src/fields/member-authored-documents.field.ts +0 -29
  61. package/src/fields/member-responsible-documents.field.ts +0 -29
  62. package/src/fields/task-form-schema.field.ts +0 -21
  63. package/src/fields/task-form-values.field.ts +0 -21
  64. package/src/fields/task-instruction.field.ts +0 -21
  65. package/src/fields/task-section.field.ts +0 -36
  66. package/src/fields/task-workflow-run-id.field.ts +0 -21
  67. package/src/fields/task-workflow-step-id.field.ts +0 -21
  68. package/src/fields/task-workflow-thread-id.field.ts +0 -21
  69. package/src/fields/workflow-launch-fields.field.ts +0 -21
  70. package/src/fields/workflow-section.field.ts +0 -36
  71. package/src/front/api.ts +0 -56
  72. package/src/front/format.ts +0 -60
  73. package/src/front/hooks/use-async.ts +0 -54
  74. package/src/front/hooks/use-workspace-members.ts +0 -22
  75. package/src/front/types.ts +0 -48
  76. package/src/front/ui/components.tsx +0 -436
  77. package/src/front/ui/form-field-inputs.tsx +0 -187
  78. package/src/front/ui/form-fields-builder.tsx +0 -183
  79. package/src/front/ui/task-work-panel.tsx +0 -218
  80. package/src/navigation-menu-items/docflow.navigation-menu-item.ts +0 -15
  81. package/src/objects/document-folder.object.ts +0 -80
  82. package/src/objects/document.object.ts +0 -312
  83. package/src/page-layouts/docflow.page-layout.ts +0 -86
  84. package/src/page-layouts/task-docflow.page-layout-tab.ts +0 -36
  85. package/src/page-layouts/workflow-steps.page-layout-tab.ts +0 -37
  86. package/src/roles/default-role.ts +0 -21
  87. package/src/roles/docflow-member.role.ts +0 -23
  88. package/src/server/bitrix-page.ts +0 -52
  89. package/src/server/bitrix-portal.ts +0 -67
  90. package/src/server/document-capture.ts +0 -102
  91. package/src/server/http-response.ts +0 -18
  92. package/src/server/launch-step.ts +0 -166
  93. package/src/server/process-tasks.ts +0 -116
  94. package/src/server/records.ts +0 -132
  95. package/src/server/task-completion.ts +0 -100
  96. package/src/server/workflow-engine.ts +0 -105
  97. package/src/shared/api/core-graphql.util.ts +0 -98
  98. package/src/shared/api/object-summaries.ts +0 -79
  99. package/src/shared/api/rest-filter.util.ts +0 -37
  100. package/src/shared/api/rest-response.util.ts +0 -43
  101. package/src/shared/bitrix/bitrix-client.ts +0 -114
  102. package/src/shared/bitrix/bitrix.type.ts +0 -25
  103. package/src/shared/bitrix/bitrix.util.ts +0 -139
  104. package/src/shared/docflow.constants.ts +0 -31
  105. package/src/shared/documents/attachment-target.util.ts +0 -64
  106. package/src/shared/engine.ts +0 -11
  107. package/src/shared/form/form-field-builder.util.ts +0 -122
  108. package/src/shared/form/form-field.type.ts +0 -23
  109. package/src/shared/form/form-fields.util.ts +0 -138
  110. package/src/shared/form/step-settings.util.ts +0 -104
  111. package/src/shared/workflow/launch-step.util.ts +0 -56
  112. package/src/shared/workflow/run-target-record.util.ts +0 -70
  113. package/src/shared/workflow/workflow-run.type.ts +0 -35
  114. package/src/shared/workflow/workflow-run.util.ts +0 -174
  115. package/src/universal-identifiers.ts +0 -199
  116. package/src/views/all-document-folders.view.ts +0 -50
  117. package/src/views/all-documents.view.ts +0 -91
  118. package/src/views/documents-by-status.view.ts +0 -76
  119. package/tsconfig.json +0 -37
  120. package/tsconfig.spec.json +0 -9
  121. package/vitest.unit.config.ts +0 -14
  122. /package/{.twenty/output/front-component-shared-dependencies.mjs → front-component-shared-dependencies.mjs} +0 -0
  123. /package/{.twenty/output/src → src}/logic-functions/bitrix-app.logic-function.mjs +0 -0
  124. /package/{.twenty/output/src → src}/logic-functions/bitrix-event.logic-function.mjs +0 -0
  125. /package/{.twenty/output/src → src}/logic-functions/bitrix-install.logic-function.mjs +0 -0
  126. /package/{.twenty/output/src → src}/logic-functions/capture-file.logic-function.mjs +0 -0
  127. /package/{.twenty/output/src → src}/logic-functions/complete-task.logic-function.mjs +0 -0
  128. /package/{.twenty/output/src → src}/logic-functions/hand-out-form-step.logic-function.mjs +0 -0
  129. /package/{.twenty/output/src → src}/logic-functions/health-check.mjs +0 -0
  130. /package/{.twenty/output/src → src}/logic-functions/launch-parameters-step.logic-function.mjs +0 -0
  131. /package/{.twenty/output/src → src}/logic-functions/notify-bitrix-assignee.logic-function.mjs +0 -0
  132. /package/{.twenty/output/src → src}/logic-functions/on-attachment-created.logic-function.mjs +0 -0
  133. /package/{.twenty/output/src → src}/logic-functions/on-task-updated.logic-function.mjs +0 -0
  134. /package/{.twenty/output/src → src}/logic-functions/on-workflow-run-updated.logic-function.mjs +0 -0
  135. /package/{.twenty/output/src → src}/logic-functions/save-step-settings.logic-function.mjs +0 -0
  136. /package/{.twenty/output/src → src}/logic-functions/save-task-form.logic-function.mjs +0 -0
@@ -1,30 +0,0 @@
1
- import { defineLogicFunction } from 'twenty-sdk/define';
2
-
3
- import { handOutFormStep } from 'src/server/process-tasks';
4
- import { HAND_OUT_FORM_STEP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
5
-
6
- const handler = async (payload: {
7
- workflowRunId?: string;
8
- stepId?: string;
9
- threadId?: string;
10
- }) => {
11
- const { workflowRunId, stepId, threadId } = payload;
12
-
13
- if (
14
- typeof workflowRunId !== 'string' ||
15
- typeof stepId !== 'string' ||
16
- typeof threadId !== 'string'
17
- ) {
18
- return { status: 'INVALID_PAYLOAD' };
19
- }
20
-
21
- return { status: await handOutFormStep({ workflowRunId, stepId, threadId }) };
22
- };
23
-
24
- export default defineLogicFunction({
25
- universalIdentifier: HAND_OUT_FORM_STEP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
26
- name: 'docflow-hand-out-form-step',
27
- description: 'Создаёт задачу CRM по шагу «Человеческий ввод»',
28
- timeoutSeconds: 30,
29
- handler,
30
- });
@@ -1,31 +0,0 @@
1
- import { defineHealthCheck } from 'twenty-sdk/define';
2
-
3
- import { isBitrixConfigured } from 'src/shared/bitrix/bitrix-client';
4
- import { getDocflowEngine } from 'src/shared/engine';
5
- import { HEALTH_CHECK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
6
-
7
- export default defineHealthCheck({
8
- universalIdentifier: HEALTH_CHECK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
9
- name: 'docflow-health-check',
10
- handler: async () => {
11
- if (getDocflowEngine() === 'FORK') {
12
- return {
13
- status: 'INFO',
14
- title: 'Серверную работу выполняет форк NorDiv',
15
- description:
16
- 'Выдача заданий, параметры запуска и сбор документов выключены в приложении (DOCFLOW_ENGINE = FORK).',
17
- };
18
- }
19
-
20
- if (!isBitrixConfigured()) {
21
- return {
22
- status: 'INFO',
23
- title: 'Битрикс24 не подключён',
24
- description:
25
- 'Укажите BITRIX_CLIENT_ID и BITRIX_CLIENT_SECRET локального приложения портала, если нужен Битрикс24.',
26
- };
27
- }
28
-
29
- return { status: 'OK' };
30
- },
31
- });
@@ -1,33 +0,0 @@
1
- import { defineLogicFunction } from 'twenty-sdk/define';
2
-
3
- import { LAUNCH_PARAMETERS_STEP_NAME } from 'src/shared/docflow.constants';
4
- import { readLaunchParameters } from 'src/shared/workflow/launch-step.util';
5
- import { LAUNCH_PARAMETERS_STEP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
6
-
7
- // The "Launch with parameters" step of a process: it gets the parameters the
8
- // person filled before the launch and returns them, so every later step reads
9
- // them as variables of this step. The tab of the process puts it first and
10
- // writes its output schema from the parameter fields.
11
- const handler = (payload: { parameters?: unknown }) =>
12
- readLaunchParameters(payload.parameters);
13
-
14
- export default defineLogicFunction({
15
- universalIdentifier: LAUNCH_PARAMETERS_STEP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
16
- name: 'docflow-launch-parameters',
17
- description: 'Отдаёт следующим шагам параметры, заполненные перед запуском процесса',
18
- timeoutSeconds: 10,
19
- handler,
20
- workflowActionTriggerSettings: {
21
- label: LAUNCH_PARAMETERS_STEP_NAME,
22
- icon: 'IconPlayerPlay',
23
- inputSchema: [
24
- {
25
- type: 'object',
26
- properties: {
27
- parameters: { type: 'string', label: 'Параметры запуска (подставляются сами)' },
28
- },
29
- },
30
- ],
31
- outputSchema: [{ type: 'object', properties: {} }],
32
- },
33
- });
@@ -1,96 +0,0 @@
1
- import { defineLogicFunction } from 'twenty-sdk/define';
2
- import {
3
- type LogicFunctionExecutionContext,
4
- type RoutePayload,
5
- } from 'twenty-sdk/logic-function';
6
-
7
- import { CoreGraphqlError } from 'src/shared/api/core-graphql.util';
8
- import { DOCFLOW_SECTION } from 'src/shared/docflow.constants';
9
- import { findMissingRequiredFields } from 'src/shared/form/form-fields.util';
10
- import {
11
- buildLaunchParameters,
12
- buildLaunchPayload,
13
- parseLaunchFields,
14
- } from 'src/shared/workflow/launch-step.util';
15
- import { readJsonBody, jsonResponse } from 'src/server/http-response';
16
- import { getWorkflow, getWorkflowVersion } from 'src/server/records';
17
- import { runWorkflowVersion } from 'src/server/workflow-engine';
18
- import { LAUNCH_PROCESS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
19
-
20
- // Launch with parameters: they are checked here and ride in the run payload,
21
- // so they stay in the run as its trigger output and reach later steps through
22
- // the "Launch with parameters" step.
23
- const handler = async (
24
- event: RoutePayload,
25
- context: LogicFunctionExecutionContext,
26
- ) => {
27
- if (context.workspaceMemberId === null) {
28
- return jsonResponse(401, { message: 'Запускать процессы может только сотрудник' });
29
- }
30
-
31
- const body = readJsonBody(event.body);
32
- const workflowVersionId = body.workflowVersionId;
33
- const values = readJsonBody(body.values);
34
-
35
- if (typeof workflowVersionId !== 'string' || workflowVersionId.length === 0) {
36
- return jsonResponse(400, { message: 'Не указан процесс' });
37
- }
38
-
39
- const version = await getWorkflowVersion('person', workflowVersionId);
40
-
41
- if (version === undefined || version.status !== 'ACTIVE') {
42
- return jsonResponse(404, { message: 'Процесс не найден или не опубликован' });
43
- }
44
-
45
- const workflow =
46
- typeof version.workflowId === 'string'
47
- ? await getWorkflow('person', version.workflowId)
48
- : undefined;
49
-
50
- if (workflow?.smbSection !== DOCFLOW_SECTION) {
51
- return jsonResponse(403, { message: 'Процесс не относится к документообороту' });
52
- }
53
-
54
- const fields = parseLaunchFields(workflow.smbLaunchFields);
55
- const parameters = buildLaunchParameters({ fields, values });
56
- const missingFields = findMissingRequiredFields({ fields, values: parameters });
57
-
58
- if (missingFields.length > 0) {
59
- return jsonResponse(422, {
60
- message: `Заполните: ${missingFields.map(({ label }) => label).join(', ')}`,
61
- missingFieldNames: missingFields.map(({ name }) => name),
62
- });
63
- }
64
-
65
- const workflowRunId = crypto.randomUUID();
66
-
67
- try {
68
- await runWorkflowVersion({
69
- workflowVersionId,
70
- workflowRunId,
71
- payload: fields.length > 0 ? buildLaunchPayload(parameters) : {},
72
- });
73
- } catch (error) {
74
- return jsonResponse(403, {
75
- message:
76
- error instanceof CoreGraphqlError && error.userMessage !== undefined
77
- ? error.userMessage
78
- : 'Не удалось запустить процесс',
79
- });
80
- }
81
-
82
- return { workflowRunId };
83
- };
84
-
85
- export default defineLogicFunction({
86
- universalIdentifier: LAUNCH_PROCESS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
87
- name: 'docflow-launch-process',
88
- description: 'Запускает процесс документооборота с параметрами',
89
- timeoutSeconds: 30,
90
- handler,
91
- httpRouteTriggerSettings: {
92
- path: '/docflow/processes/launch',
93
- httpMethod: 'POST',
94
- isAuthRequired: true,
95
- },
96
- });
@@ -1,85 +0,0 @@
1
- import { defineLogicFunction } from 'twenty-sdk/define';
2
- import {
3
- type DatabaseEventPayload,
4
- type ObjectRecordUpdateEvent,
5
- } from 'twenty-sdk/logic-function';
6
-
7
- import { isBitrixConfigured, callBitrix } from 'src/shared/bitrix/bitrix-client';
8
- import { DOCFLOW_SECTION } from 'src/shared/docflow.constants';
9
- import { getLinkedBitrixPortal } from 'src/server/bitrix-portal';
10
- import { readRestRecord } from 'src/shared/api/rest-response.util';
11
- import { restClient, type DocflowTaskRecord } from 'src/server/records';
12
- import { NOTIFY_BITRIX_ASSIGNEE_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
13
-
14
- // A docflow task given to someone shows up as a Bitrix24 notification of
15
- // that person, found by e-mail, with a link into the CRM.
16
- const handler = async (
17
- event: DatabaseEventPayload<ObjectRecordUpdateEvent<DocflowTaskRecord>>,
18
- ) => {
19
- const task = event.properties.after;
20
- const previousAssigneeId = event.properties.before?.assigneeId;
21
-
22
- if (
23
- !isBitrixConfigured() ||
24
- task?.smbSection !== DOCFLOW_SECTION ||
25
- typeof task.assigneeId !== 'string' ||
26
- task.assigneeId === previousAssigneeId
27
- ) {
28
- return { skipped: true };
29
- }
30
-
31
- const portal = await getLinkedBitrixPortal();
32
-
33
- if (portal === null) {
34
- return { skipped: 'NO_PORTAL' };
35
- }
36
-
37
- const member = readRestRecord<{ userEmail?: string | null }>(
38
- await restClient('application').get(`/rest/workspaceMembers/${task.assigneeId}`),
39
- );
40
-
41
- if (typeof member?.userEmail !== 'string') {
42
- return { skipped: 'NO_EMAIL' };
43
- }
44
-
45
- const [bitrixUser] = await callBitrix<{ ID: string }[]>({
46
- restEndpoint: portal.restEndpoint,
47
- accessToken: portal.accessToken,
48
- method: 'user.get',
49
- params: { FILTER: { EMAIL: member.userEmail } },
50
- });
51
-
52
- if (bitrixUser === undefined) {
53
- return { skipped: 'NO_BITRIX_USER' };
54
- }
55
-
56
- const crmUrl = process.env.CRM_PUBLIC_URL?.trim().replace(/\/$/, '');
57
- const title = task.title?.trim() || 'Без названия';
58
-
59
- await callBitrix({
60
- restEndpoint: portal.restEndpoint,
61
- accessToken: portal.accessToken,
62
- method: 'im.notify.system.add',
63
- params: {
64
- USER_ID: bitrixUser.ID,
65
- MESSAGE:
66
- crmUrl !== undefined && crmUrl.length > 0
67
- ? `Новое задание документооборота: [URL=${crmUrl}/object/task/${event.recordId}]${title}[/URL]`
68
- : `Новое задание документооборота: ${title}`,
69
- },
70
- });
71
-
72
- return { status: 'NOTIFIED' };
73
- };
74
-
75
- export default defineLogicFunction({
76
- universalIdentifier: NOTIFY_BITRIX_ASSIGNEE_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
77
- name: 'docflow-notify-bitrix-assignee',
78
- description: 'Уведомляет исполнителя задания в Битрикс24',
79
- timeoutSeconds: 30,
80
- handler,
81
- databaseEventTriggerSettings: {
82
- eventName: 'task.*',
83
- updatedFields: ['assigneeId', 'smbSection'],
84
- },
85
- });
@@ -1,67 +0,0 @@
1
- import { defineLogicFunction } from 'twenty-sdk/define';
2
- import {
3
- enqueueJobs,
4
- type DatabaseEventBatchPayload,
5
- type ObjectRecordCreateEvent,
6
- } from 'twenty-sdk/logic-function';
7
-
8
- import { getAttachmentTarget, readFileItems } from 'src/shared/documents/attachment-target.util';
9
- import { isAppEngine } from 'src/shared/engine';
10
- import {
11
- CAPTURE_FILE_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
12
- ON_ATTACHMENT_CREATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
13
- } from 'src/universal-identifiers';
14
-
15
- // A file attached to any record (company, deal, task of a process, a custom
16
- // object) goes to documents. Copying is slow, so each file is a queued job
17
- // named after the file: a repeated event does not copy it twice.
18
- const handler = async (
19
- batch: DatabaseEventBatchPayload<ObjectRecordCreateEvent<Record<string, unknown>>>,
20
- ) => {
21
- if (!isAppEngine()) {
22
- return { skipped: 'FORK_ENGINE' };
23
- }
24
-
25
- const jobs = batch.events.flatMap((event) => {
26
- const attachment = event.properties.after ?? {};
27
- const target = getAttachmentTarget(attachment);
28
-
29
- if (target === undefined) {
30
- return [];
31
- }
32
-
33
- return readFileItems(attachment.file).map((file) => ({
34
- payload: {
35
- attachmentId: event.recordId,
36
- fileId: file.fileId,
37
- label: file.label,
38
- sourceObjectName: target.objectNameSingular,
39
- sourceRecordId: target.recordId,
40
- workspaceMemberId: event.workspaceMemberId ?? null,
41
- },
42
- jobId: `capture-${file.fileId}`,
43
- }));
44
- });
45
-
46
- if (jobs.length > 0) {
47
- await enqueueJobs({
48
- logicFunctionUniversalIdentifier: CAPTURE_FILE_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
49
- jobs,
50
- retryLimit: 3,
51
- });
52
- }
53
-
54
- return { queuedFileCount: jobs.length };
55
- };
56
-
57
- export default defineLogicFunction({
58
- universalIdentifier: ON_ATTACHMENT_CREATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
59
- name: 'docflow-on-attachment-created',
60
- description: 'Ставит в очередь сбор вложенных файлов в документы',
61
- timeoutSeconds: 15,
62
- handler,
63
- databaseEventTriggerSettings: {
64
- eventName: 'attachment.created',
65
- batchMode: true,
66
- },
67
- });
@@ -1,71 +0,0 @@
1
- import { defineLogicFunction } from 'twenty-sdk/define';
2
- import {
3
- type DatabaseEventPayload,
4
- type ObjectRecordUpdateEvent,
5
- } from 'twenty-sdk/logic-function';
6
-
7
- import { TASK_DONE_STATUS, TASK_TODO_STATUS } from 'src/shared/docflow.constants';
8
- import { isAppEngine } from 'src/shared/engine';
9
- import {
10
- findMissingRequiredFields,
11
- parseFormFields,
12
- parseFormValues,
13
- } from 'src/shared/form/form-fields.util';
14
- import { type DocflowTaskRecord, updateTask } from 'src/server/records';
15
- import { answerStepOfTask } from 'src/server/task-completion';
16
- import { ON_TASK_UPDATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
17
-
18
- // A task closed outside the docflow page (the board, the list, the API).
19
- // Twenty gives apps no way to refuse a save, so a close that breaks the
20
- // rules is undone right after it: required fields empty, or the process did
21
- // not accept the answer.
22
- const handler = async (
23
- event: DatabaseEventPayload<ObjectRecordUpdateEvent<DocflowTaskRecord>>,
24
- ) => {
25
- if (!isAppEngine()) {
26
- return { skipped: 'FORK_ENGINE' };
27
- }
28
-
29
- const { before, after } = event.properties;
30
-
31
- if (after?.status !== TASK_DONE_STATUS || before?.status === TASK_DONE_STATUS) {
32
- return { skipped: 'NOT_COMPLETED' };
33
- }
34
-
35
- const task = { ...after, id: event.recordId };
36
- const values = parseFormValues(task.smbFormValues);
37
- const missingFields = findMissingRequiredFields({
38
- fields: parseFormFields(task.smbFormSchema),
39
- values,
40
- });
41
- const stepResult =
42
- missingFields.length > 0 ? undefined : await answerStepOfTask(task, values);
43
-
44
- if (missingFields.length === 0 && stepResult === undefined) {
45
- return { status: 'ACCEPTED' };
46
- }
47
-
48
- await updateTask('application', task.id, {
49
- status: before?.status ?? TASK_TODO_STATUS,
50
- });
51
-
52
- return {
53
- status: 'REVERTED',
54
- reason:
55
- missingFields.length > 0
56
- ? `Не заполнены: ${missingFields.map(({ label }) => label).join(', ')}`
57
- : stepResult?.status,
58
- };
59
- };
60
-
61
- export default defineLogicFunction({
62
- universalIdentifier: ON_TASK_UPDATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
63
- name: 'docflow-on-task-updated',
64
- description: 'Проверяет закрытие задачи и продолжает процесс',
65
- timeoutSeconds: 30,
66
- handler,
67
- databaseEventTriggerSettings: {
68
- eventName: 'task.updated',
69
- updatedFields: ['status'],
70
- },
71
- });
@@ -1,70 +0,0 @@
1
- import { defineLogicFunction } from 'twenty-sdk/define';
2
- import {
3
- enqueueJobs,
4
- type DatabaseEventPayload,
5
- type ObjectRecordUpdateEvent,
6
- } from 'twenty-sdk/logic-function';
7
-
8
- import { isAppEngine } from 'src/shared/engine';
9
- import { getFormStepTaskRequests } from 'src/shared/workflow/workflow-run.util';
10
- import { closeTasksOfAnsweredSteps } from 'src/server/process-tasks';
11
- import { type WorkflowRunRecord } from 'src/server/records';
12
- import {
13
- HAND_OUT_FORM_STEP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
14
- ON_WORKFLOW_RUN_UPDATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
15
- } from 'src/universal-identifiers';
16
-
17
- // Process tasks follow the run: a FORM step the run waits on becomes a CRM
18
- // task, and a step answered elsewhere closes its task. Handing out is queued
19
- // under the step's thread id, so repeated run updates create one task.
20
- const handler = async (
21
- event: DatabaseEventPayload<ObjectRecordUpdateEvent<WorkflowRunRecord>>,
22
- ) => {
23
- if (!isAppEngine()) {
24
- return { skipped: 'FORK_ENGINE' };
25
- }
26
-
27
- const run = { ...event.properties.after, id: event.recordId };
28
-
29
- if (run.status !== 'RUNNING' || run.state === undefined || run.state === null) {
30
- return { skipped: 'NOT_RUNNING' };
31
- }
32
-
33
- const requests = getFormStepTaskRequests({
34
- state: run.state,
35
- runStarterWorkspaceMemberId: run.createdBy?.workspaceMemberId,
36
- now: new Date(),
37
- });
38
-
39
- if (requests.length > 0) {
40
- await enqueueJobs({
41
- logicFunctionUniversalIdentifier:
42
- HAND_OUT_FORM_STEP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
43
- jobs: requests.map((request) => ({
44
- payload: {
45
- workflowRunId: run.id,
46
- stepId: request.stepId,
47
- threadId: request.threadId,
48
- },
49
- jobId: `hand-out-${request.threadId}`,
50
- })),
51
- retryLimit: 2,
52
- });
53
- }
54
-
55
- const closedTaskCount = await closeTasksOfAnsweredSteps(run);
56
-
57
- return { queuedTaskCount: requests.length, closedTaskCount };
58
- };
59
-
60
- export default defineLogicFunction({
61
- universalIdentifier: ON_WORKFLOW_RUN_UPDATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
62
- name: 'docflow-on-workflow-run-updated',
63
- description: 'Выдаёт задачи по шагам процессов и закрывает отвеченные',
64
- timeoutSeconds: 30,
65
- handler,
66
- databaseEventTriggerSettings: {
67
- eventName: 'workflowRun.updated',
68
- updatedFields: ['state', 'status'],
69
- },
70
- });
@@ -1,73 +0,0 @@
1
- import { defineLogicFunction } from 'twenty-sdk/define';
2
- import { type RoutePayload } from 'twenty-sdk/logic-function';
3
-
4
- import { CoreGraphqlError } from 'src/shared/api/core-graphql.util';
5
- import { readRestRecord } from 'src/shared/api/rest-response.util';
6
- import { getFormFieldsError } from 'src/shared/form/form-fields.util';
7
- import { parseLaunchFields } from 'src/shared/workflow/launch-step.util';
8
- import { syncLaunchStep } from 'src/server/launch-step';
9
- import { jsonResponse, readJsonBody } from 'src/server/http-response';
10
- import { restClient } from 'src/server/records';
11
- import { SAVE_LAUNCH_FIELDS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
12
-
13
- // Saves the parameters of a process and keeps its draft in step: the
14
- // "Launch with parameters" step first, its variables matching the fields.
15
- const handler = async (event: RoutePayload) => {
16
- const body = readJsonBody(event.body);
17
- const workflowId = body.workflowId;
18
- const fields = parseLaunchFields(body.fields);
19
-
20
- if (typeof workflowId !== 'string' || workflowId.length === 0 || !Array.isArray(body.fields)) {
21
- return jsonResponse(400, { message: 'Не указан процесс или поля' });
22
- }
23
-
24
- const fieldsError = getFormFieldsError(fields);
25
-
26
- if (fieldsError !== undefined) {
27
- return jsonResponse(422, { message: fieldsError });
28
- }
29
-
30
- // Written as the person: who may not change the process gets the refusal
31
- // of the API, not a half-saved draft.
32
- const workflow = readRestRecord<{ id: string }>(
33
- await restClient('person').patch(`/rest/workflows/${workflowId}`, {
34
- smbLaunchFields: fields.length > 0 ? fields : null,
35
- }),
36
- );
37
-
38
- if (workflow === undefined) {
39
- return jsonResponse(404, { message: 'Процесс не найден' });
40
- }
41
-
42
- try {
43
- const result = await syncLaunchStep({ workflowId, fields });
44
-
45
- if (result?.status === 'NOT_MANUAL') {
46
- return jsonResponse(409, {
47
- message: 'Параметры сохранены, но шаг не добавлен: у процесса не ручной запуск',
48
- });
49
- }
50
-
51
- return { status: 'SAVED', stepId: result?.status === 'SAVED' ? result.stepId : null };
52
- } catch (error) {
53
- return jsonResponse(502, {
54
- message:
55
- error instanceof CoreGraphqlError && error.userMessage !== undefined
56
- ? `Параметры сохранены, шаг процесса не обновлён: ${error.userMessage}`
57
- : `Параметры сохранены, шаг процесса не обновлён: ${error instanceof Error ? error.message : 'ошибка редактора процессов'}`,
58
- });
59
- }
60
- };
61
-
62
- export default defineLogicFunction({
63
- universalIdentifier: SAVE_LAUNCH_FIELDS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
64
- name: 'docflow-save-launch-fields',
65
- description: 'Сохраняет параметры запуска процесса и ставит шаг «Запуск с параметрами»',
66
- timeoutSeconds: 30,
67
- handler,
68
- httpRouteTriggerSettings: {
69
- path: '/docflow/processes/launch-fields',
70
- httpMethod: 'POST',
71
- isAuthRequired: true,
72
- },
73
- });
@@ -1,90 +0,0 @@
1
- import { defineLogicFunction } from 'twenty-sdk/define';
2
- import { type RoutePayload } from 'twenty-sdk/logic-function';
3
-
4
- import { jsonResponse, readJsonBody } from 'src/server/http-response';
5
- import { restClient, type WorkflowVersionRecord } from 'src/server/records';
6
- import { callCoreGraphql, UPDATE_WORKFLOW_VERSION_STEP_MUTATION } from 'src/shared/api/core-graphql.util';
7
- import { readRestRecord } from 'src/shared/api/rest-response.util';
8
- import { applyStepSettings, type StepSettings } from 'src/shared/form/step-settings.util';
9
- import { parseFormFields } from 'src/shared/form/form-fields.util';
10
- import { isFormStep } from 'src/shared/workflow/workflow-run.util';
11
- import { SAVE_STEP_SETTINGS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
12
-
13
- const readSettings = (value: unknown): StepSettings | undefined => {
14
- const body = readJsonBody(value);
15
- const { assignee, dueInDays, requiredFieldIds } = body;
16
-
17
- if (
18
- (assignee !== null && typeof assignee !== 'string') ||
19
- (dueInDays !== null && typeof dueInDays !== 'number') ||
20
- !Array.isArray(requiredFieldIds)
21
- ) {
22
- return undefined;
23
- }
24
-
25
- return {
26
- assignee: assignee === '' ? null : assignee,
27
- dueInDays: dueInDays === 0 ? null : dueInDays,
28
- requiredFieldIds: requiredFieldIds.filter((id): id is string => typeof id === 'string'),
29
- };
30
- };
31
-
32
- // Who fills a "Human input" step, by when, and which of its fields are
33
- // required: the editor of Twenty has no place for it, the step keeps it in the
34
- // settings of its fields.
35
- const handler = async (event: RoutePayload) => {
36
- const body = readJsonBody(event.body);
37
- const settings = readSettings(body.settings);
38
-
39
- if (
40
- typeof body.workflowVersionId !== 'string' ||
41
- typeof body.stepId !== 'string' ||
42
- settings === undefined
43
- ) {
44
- return jsonResponse(400, { message: 'Не указан шаг или настройки' });
45
- }
46
-
47
- const version = readRestRecord<WorkflowVersionRecord>(
48
- await restClient('person').get(`/rest/workflowVersions/${body.workflowVersionId}`),
49
- );
50
- const step = version?.steps?.find(({ id }) => id === body.stepId);
51
-
52
- if (version === undefined || step === undefined || !isFormStep(step)) {
53
- return jsonResponse(404, { message: 'Шаг «Человеческий ввод» не найден' });
54
- }
55
-
56
- if (version.status !== 'DRAFT') {
57
- return jsonResponse(409, { message: 'Менять можно только черновик процесса' });
58
- }
59
-
60
- const fields = applyStepSettings({
61
- fields: parseFormFields(step.settings.input),
62
- settings,
63
- createId: () => crypto.randomUUID(),
64
- });
65
-
66
- await callCoreGraphql({
67
- query: UPDATE_WORKFLOW_VERSION_STEP_MUTATION,
68
- variables: {
69
- input: {
70
- workflowVersionId: version.id,
71
- step: { ...step, settings: { ...step.settings, input: fields } },
72
- },
73
- },
74
- });
75
-
76
- return { status: 'SAVED' };
77
- };
78
-
79
- export default defineLogicFunction({
80
- universalIdentifier: SAVE_STEP_SETTINGS_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
81
- name: 'docflow-save-step-settings',
82
- description: 'Сохраняет исполнителя, срок и обязательные поля шага процесса',
83
- timeoutSeconds: 15,
84
- handler,
85
- httpRouteTriggerSettings: {
86
- path: '/docflow/processes/step-settings',
87
- httpMethod: 'POST',
88
- isAuthRequired: true,
89
- },
90
- });