@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.
- package/{.twenty/output/manifest.json → manifest.json} +9 -9
- package/package.json +15 -3
- package/{.twenty/output/src → src}/front-components/docflow-documents.front-component.mjs +24 -24
- package/src/front-components/docflow-documents.front-component.tsx +21 -6
- package/{.twenty/output/src → src}/front-components/docflow-processes.front-component.mjs +18 -18
- package/{.twenty/output/src → src}/front-components/docflow-tasks.front-component.mjs +18 -18
- package/{.twenty/output/src → src}/front-components/process-steps.front-component.mjs +19 -19
- package/src/front-components/process-steps.front-component.tsx +6 -1
- package/{.twenty/output/src → src}/front-components/task-form.front-component.mjs +24 -24
- package/{.twenty/output/src → src}/logic-functions/launch-process.logic-function.mjs +14 -0
- package/src/logic-functions/launch-process.logic-function.ts +5 -0
- package/{.twenty/output/src → src}/logic-functions/save-launch-fields.logic-function.mjs +14 -0
- package/src/logic-functions/save-launch-fields.logic-function.ts +5 -0
- package/.gitignore +0 -43
- package/.nvmrc +0 -1
- package/.oxlintrc.json +0 -37
- package/.twenty/output/README.md +0 -128
- package/.twenty/output/package.json +0 -49
- package/.twenty/output/public/cover.svg +0 -15
- package/.twenty/output/public/logo.svg +0 -9
- package/.twenty/output/src/front-components/docflow-documents.front-component.tsx +0 -516
- package/.twenty/output/src/front-components/docflow-processes.front-component.tsx +0 -266
- package/.twenty/output/src/front-components/docflow-tasks.front-component.tsx +0 -303
- package/.twenty/output/src/front-components/process-steps.front-component.tsx +0 -286
- package/.twenty/output/src/front-components/task-form.front-component.tsx +0 -29
- package/.twenty/output/src/logic-functions/bitrix-app.logic-function.ts +0 -143
- package/.twenty/output/src/logic-functions/bitrix-event.logic-function.ts +0 -54
- package/.twenty/output/src/logic-functions/bitrix-install.logic-function.ts +0 -81
- package/.twenty/output/src/logic-functions/capture-file.logic-function.ts +0 -32
- package/.twenty/output/src/logic-functions/complete-task.logic-function.ts +0 -46
- package/.twenty/output/src/logic-functions/hand-out-form-step.logic-function.ts +0 -30
- package/.twenty/output/src/logic-functions/health-check.ts +0 -31
- package/.twenty/output/src/logic-functions/launch-parameters-step.logic-function.ts +0 -33
- package/.twenty/output/src/logic-functions/launch-process.logic-function.ts +0 -96
- package/.twenty/output/src/logic-functions/notify-bitrix-assignee.logic-function.ts +0 -85
- package/.twenty/output/src/logic-functions/on-attachment-created.logic-function.ts +0 -67
- package/.twenty/output/src/logic-functions/on-task-updated.logic-function.ts +0 -71
- package/.twenty/output/src/logic-functions/on-workflow-run-updated.logic-function.ts +0 -70
- package/.twenty/output/src/logic-functions/save-launch-fields.logic-function.ts +0 -73
- package/.twenty/output/src/logic-functions/save-step-settings.logic-function.ts +0 -90
- package/.twenty/output/src/logic-functions/save-task-form.logic-function.ts +0 -62
- package/.twenty/output/yarn.lock +0 -3292
- package/.yarn/install-state.gz +0 -0
- package/.yarnrc.yml +0 -1
- package/dist/tsconfig.spec.tsbuildinfo +0 -1
- package/src/__tests__/api-and-bitrix.test.ts +0 -89
- package/src/__tests__/form-fields.test.ts +0 -73
- package/src/__tests__/launch-parameters.test.ts +0 -75
- package/src/__tests__/step-settings.test.ts +0 -39
- package/src/__tests__/workflow-run.test.ts +0 -113
- package/src/application-config.ts +0 -58
- package/src/fields/company-documents.field.ts +0 -29
- package/src/fields/document-author.field.ts +0 -32
- package/src/fields/document-counterparty.field.ts +0 -32
- package/src/fields/document-folder-child-folders.field.ts +0 -23
- package/src/fields/document-folder-documents.field.ts +0 -24
- package/src/fields/document-folder-parent-folder.field.ts +0 -31
- package/src/fields/document-folder.field.ts +0 -32
- package/src/fields/document-responsible.field.ts +0 -32
- package/src/fields/member-authored-documents.field.ts +0 -29
- package/src/fields/member-responsible-documents.field.ts +0 -29
- package/src/fields/task-form-schema.field.ts +0 -21
- package/src/fields/task-form-values.field.ts +0 -21
- package/src/fields/task-instruction.field.ts +0 -21
- package/src/fields/task-section.field.ts +0 -36
- package/src/fields/task-workflow-run-id.field.ts +0 -21
- package/src/fields/task-workflow-step-id.field.ts +0 -21
- package/src/fields/task-workflow-thread-id.field.ts +0 -21
- package/src/fields/workflow-launch-fields.field.ts +0 -21
- package/src/fields/workflow-section.field.ts +0 -36
- package/src/front/api.ts +0 -56
- package/src/front/format.ts +0 -60
- package/src/front/hooks/use-async.ts +0 -54
- package/src/front/hooks/use-workspace-members.ts +0 -22
- package/src/front/types.ts +0 -48
- package/src/front/ui/components.tsx +0 -436
- package/src/front/ui/form-field-inputs.tsx +0 -187
- package/src/front/ui/form-fields-builder.tsx +0 -183
- package/src/front/ui/task-work-panel.tsx +0 -218
- package/src/navigation-menu-items/docflow.navigation-menu-item.ts +0 -15
- package/src/objects/document-folder.object.ts +0 -80
- package/src/objects/document.object.ts +0 -312
- package/src/page-layouts/docflow.page-layout.ts +0 -86
- package/src/page-layouts/task-docflow.page-layout-tab.ts +0 -36
- package/src/page-layouts/workflow-steps.page-layout-tab.ts +0 -37
- package/src/roles/default-role.ts +0 -21
- package/src/roles/docflow-member.role.ts +0 -23
- package/src/server/bitrix-page.ts +0 -52
- package/src/server/bitrix-portal.ts +0 -67
- package/src/server/document-capture.ts +0 -102
- package/src/server/http-response.ts +0 -18
- package/src/server/launch-step.ts +0 -166
- package/src/server/process-tasks.ts +0 -116
- package/src/server/records.ts +0 -132
- package/src/server/task-completion.ts +0 -100
- package/src/server/workflow-engine.ts +0 -105
- package/src/shared/api/core-graphql.util.ts +0 -98
- package/src/shared/api/object-summaries.ts +0 -79
- package/src/shared/api/rest-filter.util.ts +0 -37
- package/src/shared/api/rest-response.util.ts +0 -43
- package/src/shared/bitrix/bitrix-client.ts +0 -114
- package/src/shared/bitrix/bitrix.type.ts +0 -25
- package/src/shared/bitrix/bitrix.util.ts +0 -139
- package/src/shared/docflow.constants.ts +0 -31
- package/src/shared/documents/attachment-target.util.ts +0 -64
- package/src/shared/engine.ts +0 -11
- package/src/shared/form/form-field-builder.util.ts +0 -122
- package/src/shared/form/form-field.type.ts +0 -23
- package/src/shared/form/form-fields.util.ts +0 -138
- package/src/shared/form/step-settings.util.ts +0 -104
- package/src/shared/workflow/launch-step.util.ts +0 -56
- package/src/shared/workflow/run-target-record.util.ts +0 -70
- package/src/shared/workflow/workflow-run.type.ts +0 -35
- package/src/shared/workflow/workflow-run.util.ts +0 -174
- package/src/universal-identifiers.ts +0 -199
- package/src/views/all-document-folders.view.ts +0 -50
- package/src/views/all-documents.view.ts +0 -91
- package/src/views/documents-by-status.view.ts +0 -76
- package/tsconfig.json +0 -37
- package/tsconfig.spec.json +0 -9
- package/vitest.unit.config.ts +0 -14
- /package/{.twenty/output/front-component-shared-dependencies.mjs → front-component-shared-dependencies.mjs} +0 -0
- /package/{.twenty/output/src → src}/logic-functions/bitrix-app.logic-function.mjs +0 -0
- /package/{.twenty/output/src → src}/logic-functions/bitrix-event.logic-function.mjs +0 -0
- /package/{.twenty/output/src → src}/logic-functions/bitrix-install.logic-function.mjs +0 -0
- /package/{.twenty/output/src → src}/logic-functions/capture-file.logic-function.mjs +0 -0
- /package/{.twenty/output/src → src}/logic-functions/complete-task.logic-function.mjs +0 -0
- /package/{.twenty/output/src → src}/logic-functions/hand-out-form-step.logic-function.mjs +0 -0
- /package/{.twenty/output/src → src}/logic-functions/health-check.mjs +0 -0
- /package/{.twenty/output/src → src}/logic-functions/launch-parameters-step.logic-function.mjs +0 -0
- /package/{.twenty/output/src → src}/logic-functions/notify-bitrix-assignee.logic-function.mjs +0 -0
- /package/{.twenty/output/src → src}/logic-functions/on-attachment-created.logic-function.mjs +0 -0
- /package/{.twenty/output/src → src}/logic-functions/on-task-updated.logic-function.mjs +0 -0
- /package/{.twenty/output/src → src}/logic-functions/on-workflow-run-updated.logic-function.mjs +0 -0
- /package/{.twenty/output/src → src}/logic-functions/save-step-settings.logic-function.mjs +0 -0
- /package/{.twenty/output/src → src}/logic-functions/save-task-form.logic-function.mjs +0 -0
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
|
2
|
-
|
|
3
|
-
import { restEq } from 'src/shared/api/rest-filter.util';
|
|
4
|
-
import { readRestList, readRestRecord } from 'src/shared/api/rest-response.util';
|
|
5
|
-
import {
|
|
6
|
-
formatRecordLabel,
|
|
7
|
-
getDocumentNameFromFile,
|
|
8
|
-
readFileItems,
|
|
9
|
-
} from 'src/shared/documents/attachment-target.util';
|
|
10
|
-
import { getObjectSummaries } from 'src/shared/api/object-summaries';
|
|
11
|
-
import { getTask, restClient } from 'src/server/records';
|
|
12
|
-
import { DOCUMENT_FILE_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
13
|
-
|
|
14
|
-
export type FileCapture = {
|
|
15
|
-
attachmentId: string;
|
|
16
|
-
fileId: string;
|
|
17
|
-
label: string;
|
|
18
|
-
sourceObjectName: string;
|
|
19
|
-
sourceRecordId: string;
|
|
20
|
-
workspaceMemberId: string | null;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export type CaptureResult = 'CREATED' | 'ALREADY_CAPTURED' | 'FILE_GONE';
|
|
24
|
-
|
|
25
|
-
const findSourceRecordName = async (
|
|
26
|
-
objectName: string,
|
|
27
|
-
recordId: string,
|
|
28
|
-
): Promise<string | null> => {
|
|
29
|
-
const summary = (await getObjectSummaries()).get(objectName);
|
|
30
|
-
|
|
31
|
-
if (summary === undefined || summary.labelFieldName === undefined) {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const record = readRestRecord<Record<string, unknown>>(
|
|
36
|
-
await restClient('application')
|
|
37
|
-
.get(`/rest/${summary.namePlural}/${recordId}`)
|
|
38
|
-
.catch(() => null),
|
|
39
|
-
);
|
|
40
|
-
const label = formatRecordLabel(record?.[summary.labelFieldName]);
|
|
41
|
-
|
|
42
|
-
return label === undefined ? summary.labelSingular : `${summary.labelSingular}: ${label}`;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// The document keeps its own copy of the file: a file belongs to one FILES
|
|
46
|
-
// field, and the archive must outlive the record it came from.
|
|
47
|
-
export const captureFile = async (capture: FileCapture): Promise<CaptureResult> => {
|
|
48
|
-
const existing = readRestList<{ id: string }>(
|
|
49
|
-
await restClient('application').get('/rest/smbDocuments', {
|
|
50
|
-
query: { filter: restEq('sourceFileId', capture.fileId), limit: 1 },
|
|
51
|
-
}),
|
|
52
|
-
).records;
|
|
53
|
-
|
|
54
|
-
if (existing.length > 0) {
|
|
55
|
-
return 'ALREADY_CAPTURED';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const attachment = readRestRecord<Record<string, unknown>>(
|
|
59
|
-
await restClient('application').get(`/rest/attachments/${capture.attachmentId}`),
|
|
60
|
-
);
|
|
61
|
-
const file = readFileItems(attachment?.file).find(
|
|
62
|
-
(item) => item.fileId === capture.fileId,
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
if (file?.url === undefined) {
|
|
66
|
-
return 'FILE_GONE';
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const download = await fetch(file.url);
|
|
70
|
-
|
|
71
|
-
if (!download.ok) {
|
|
72
|
-
throw new Error(`The file of attachment ${capture.attachmentId} could not be read (${download.status})`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const uploaded = await new MetadataApiClient().uploadFile({
|
|
76
|
-
fileBuffer: Buffer.from(await download.arrayBuffer()),
|
|
77
|
-
filename: capture.label,
|
|
78
|
-
fieldMetadataUniversalIdentifier: DOCUMENT_FILE_UNIVERSAL_IDENTIFIER,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const workflowRunId =
|
|
82
|
-
capture.sourceObjectName === 'task'
|
|
83
|
-
? ((await getTask('application', capture.sourceRecordId))?.smbWorkflowRunId ?? null)
|
|
84
|
-
: null;
|
|
85
|
-
|
|
86
|
-
await restClient('application').post('/rest/smbDocuments', {
|
|
87
|
-
name: getDocumentNameFromFile(capture.label),
|
|
88
|
-
file: [{ fileId: uploaded.id, label: capture.label }],
|
|
89
|
-
origin: workflowRunId !== null ? 'PROCESS' : 'RECORD',
|
|
90
|
-
sourceObjectName: capture.sourceObjectName,
|
|
91
|
-
sourceRecordId: capture.sourceRecordId,
|
|
92
|
-
sourceRecordName: await findSourceRecordName(
|
|
93
|
-
capture.sourceObjectName,
|
|
94
|
-
capture.sourceRecordId,
|
|
95
|
-
),
|
|
96
|
-
sourceFileId: capture.fileId,
|
|
97
|
-
workflowRunId,
|
|
98
|
-
authorId: capture.workspaceMemberId,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
return 'CREATED';
|
|
102
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Response } from 'twenty-sdk/logic-function';
|
|
2
|
-
|
|
3
|
-
export const jsonResponse = (status: number, body: Record<string, unknown>) =>
|
|
4
|
-
new Response(JSON.stringify(body), {
|
|
5
|
-
status,
|
|
6
|
-
headers: { 'content-type': 'application/json; charset=utf-8' },
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
export const htmlResponse = (status: number, html: string) =>
|
|
10
|
-
new Response(html, {
|
|
11
|
-
status,
|
|
12
|
-
headers: { 'content-type': 'text/html; charset=utf-8' },
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
export const readJsonBody = (body: unknown): Record<string, unknown> =>
|
|
16
|
-
typeof body === 'object' && body !== null && !Array.isArray(body)
|
|
17
|
-
? (body as Record<string, unknown>)
|
|
18
|
-
: {};
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
callCoreGraphql,
|
|
5
|
-
CREATE_DRAFT_FROM_WORKFLOW_VERSION_MUTATION,
|
|
6
|
-
CREATE_WORKFLOW_VERSION_STEP_MUTATION,
|
|
7
|
-
DELETE_WORKFLOW_VERSION_STEP_MUTATION,
|
|
8
|
-
UPDATE_WORKFLOW_VERSION_STEP_MUTATION,
|
|
9
|
-
UPDATE_WORKFLOW_VERSION_TRIGGER_MUTATION,
|
|
10
|
-
} from 'src/shared/api/core-graphql.util';
|
|
11
|
-
import {
|
|
12
|
-
LAUNCH_PARAMETERS_STEP_INPUT,
|
|
13
|
-
LAUNCH_PARAMETERS_STEP_NAME,
|
|
14
|
-
TRIGGER_STEP_ID,
|
|
15
|
-
} from 'src/shared/docflow.constants';
|
|
16
|
-
import { type FormField } from 'src/shared/form/form-field.type';
|
|
17
|
-
import { buildFieldsOutputSchema } from 'src/shared/form/form-field-builder.util';
|
|
18
|
-
import { findLaunchParametersStep } from 'src/shared/workflow/launch-step.util';
|
|
19
|
-
import { listWorkflowVersions, type WorkflowVersionRecord } from 'src/server/records';
|
|
20
|
-
import { LAUNCH_PARAMETERS_STEP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
21
|
-
|
|
22
|
-
export type LaunchStepResult =
|
|
23
|
-
| { status: 'SAVED'; workflowVersionId: string; stepId: string | null }
|
|
24
|
-
| { status: 'NOT_MANUAL' };
|
|
25
|
-
|
|
26
|
-
const MANUAL_TRIGGER = {
|
|
27
|
-
name: 'Запуск вручную',
|
|
28
|
-
type: 'MANUAL',
|
|
29
|
-
position: { x: 0, y: 0 },
|
|
30
|
-
settings: {
|
|
31
|
-
outputSchema: {},
|
|
32
|
-
availability: { type: 'GLOBAL' },
|
|
33
|
-
icon: 'IconPlayerPlay',
|
|
34
|
-
isPinned: false,
|
|
35
|
-
},
|
|
36
|
-
nextStepIds: [],
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// The step of the workflow editor is created with the function's id in this
|
|
40
|
-
// workspace; the app knows the function by its universal identifier.
|
|
41
|
-
const findLaunchStepLogicFunctionId = async (): Promise<string> => {
|
|
42
|
-
const { findManyLogicFunctions } = await new MetadataApiClient().query({
|
|
43
|
-
findManyLogicFunctions: { id: true, universalIdentifier: true },
|
|
44
|
-
});
|
|
45
|
-
const logicFunction = findManyLogicFunctions.find(
|
|
46
|
-
({ universalIdentifier }) =>
|
|
47
|
-
universalIdentifier === LAUNCH_PARAMETERS_STEP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
if (logicFunction === undefined) {
|
|
51
|
-
throw new Error('The "Launch with parameters" function is not installed');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return logicFunction.id;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// Only a draft can change: an existing one, or a new one copied from the
|
|
58
|
-
// latest version, as the editor does when a published process is edited.
|
|
59
|
-
const findOrCreateDraft = async (workflowId: string): Promise<WorkflowVersionRecord | undefined> => {
|
|
60
|
-
const versions = await listWorkflowVersions('person', workflowId);
|
|
61
|
-
const draft = versions.find(({ status }) => status === 'DRAFT');
|
|
62
|
-
|
|
63
|
-
if (draft !== undefined || versions.length === 0) {
|
|
64
|
-
return draft;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
await callCoreGraphql({
|
|
68
|
-
query: CREATE_DRAFT_FROM_WORKFLOW_VERSION_MUTATION,
|
|
69
|
-
variables: { input: { workflowId, workflowVersionIdToCopy: versions[0].id } },
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
return (await listWorkflowVersions('person', workflowId)).find(({ status }) => status === 'DRAFT');
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// Puts the "Launch with parameters" step right after the manual trigger of the
|
|
76
|
-
// draft and describes its output with the parameter fields, so the editor
|
|
77
|
-
// offers them as variables of later steps. No fields, no step.
|
|
78
|
-
export const syncLaunchStep = async ({
|
|
79
|
-
workflowId,
|
|
80
|
-
fields,
|
|
81
|
-
}: {
|
|
82
|
-
workflowId: string;
|
|
83
|
-
fields: FormField[];
|
|
84
|
-
}): Promise<LaunchStepResult | undefined> => {
|
|
85
|
-
let draft = await findOrCreateDraft(workflowId);
|
|
86
|
-
|
|
87
|
-
if (draft === undefined) {
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (draft.trigger === null || draft.trigger === undefined) {
|
|
92
|
-
await callCoreGraphql({
|
|
93
|
-
query: UPDATE_WORKFLOW_VERSION_TRIGGER_MUTATION,
|
|
94
|
-
variables: { input: { workflowVersionId: draft.id, trigger: MANUAL_TRIGGER } },
|
|
95
|
-
});
|
|
96
|
-
draft = { ...draft, trigger: MANUAL_TRIGGER };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (draft.trigger?.type !== 'MANUAL') {
|
|
100
|
-
return { status: 'NOT_MANUAL' };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const existingStep = findLaunchParametersStep(draft.steps);
|
|
104
|
-
|
|
105
|
-
if (fields.length === 0) {
|
|
106
|
-
if (existingStep !== undefined) {
|
|
107
|
-
await callCoreGraphql({
|
|
108
|
-
query: DELETE_WORKFLOW_VERSION_STEP_MUTATION,
|
|
109
|
-
variables: { input: { workflowVersionId: draft.id, stepId: existingStep.id } },
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return { status: 'SAVED', workflowVersionId: draft.id, stepId: null };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
let step = existingStep;
|
|
117
|
-
|
|
118
|
-
if (step === undefined) {
|
|
119
|
-
const stepId = crypto.randomUUID();
|
|
120
|
-
|
|
121
|
-
await callCoreGraphql({
|
|
122
|
-
query: CREATE_WORKFLOW_VERSION_STEP_MUTATION,
|
|
123
|
-
variables: {
|
|
124
|
-
input: {
|
|
125
|
-
workflowVersionId: draft.id,
|
|
126
|
-
id: stepId,
|
|
127
|
-
stepType: 'LOGIC_FUNCTION',
|
|
128
|
-
parentStepId: TRIGGER_STEP_ID,
|
|
129
|
-
nextStepId: draft.trigger.nextStepIds?.[0],
|
|
130
|
-
defaultSettings: {
|
|
131
|
-
input: {
|
|
132
|
-
logicFunctionId: await findLaunchStepLogicFunctionId(),
|
|
133
|
-
logicFunctionInput: { parameters: LAUNCH_PARAMETERS_STEP_INPUT },
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
const versions = await listWorkflowVersions('person', workflowId);
|
|
141
|
-
|
|
142
|
-
step = versions
|
|
143
|
-
.find(({ id }) => id === draft?.id)
|
|
144
|
-
?.steps?.find(({ id }) => id === stepId);
|
|
145
|
-
|
|
146
|
-
if (step === undefined) {
|
|
147
|
-
throw new Error(`The launch step ${stepId} was not created`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
await callCoreGraphql({
|
|
152
|
-
query: UPDATE_WORKFLOW_VERSION_STEP_MUTATION,
|
|
153
|
-
variables: {
|
|
154
|
-
input: {
|
|
155
|
-
workflowVersionId: draft.id,
|
|
156
|
-
step: {
|
|
157
|
-
...step,
|
|
158
|
-
name: LAUNCH_PARAMETERS_STEP_NAME,
|
|
159
|
-
settings: { ...step.settings, outputSchema: buildFieldsOutputSchema(fields) },
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
return { status: 'SAVED', workflowVersionId: draft.id, stepId: step.id };
|
|
166
|
-
};
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { DOCFLOW_SECTION, CRM_SECTION, TASK_DONE_STATUS, TASK_TODO_STATUS } from 'src/shared/docflow.constants';
|
|
2
|
-
import { parseFormFields } from 'src/shared/form/form-fields.util';
|
|
3
|
-
import { getRunTargetRecord } from 'src/shared/workflow/run-target-record.util';
|
|
4
|
-
import { getFormStepTaskRequests } from 'src/shared/workflow/workflow-run.util';
|
|
5
|
-
import {
|
|
6
|
-
createTask,
|
|
7
|
-
createTaskTarget,
|
|
8
|
-
findOpenTasksOfRun,
|
|
9
|
-
findTaskByThreadId,
|
|
10
|
-
getWorkflow,
|
|
11
|
-
getWorkflowRun,
|
|
12
|
-
updateTask,
|
|
13
|
-
type WorkflowRunRecord,
|
|
14
|
-
} from 'src/server/records';
|
|
15
|
-
|
|
16
|
-
export type HandOutResult = 'CREATED' | 'ALREADY_HANDED_OUT' | 'NOT_WAITING';
|
|
17
|
-
|
|
18
|
-
// Creates the CRM task of one waiting FORM step. The run is read again: the
|
|
19
|
-
// job may run after the step was answered or the task created.
|
|
20
|
-
export const handOutFormStep = async ({
|
|
21
|
-
workflowRunId,
|
|
22
|
-
stepId,
|
|
23
|
-
threadId,
|
|
24
|
-
}: {
|
|
25
|
-
workflowRunId: string;
|
|
26
|
-
stepId: string;
|
|
27
|
-
threadId: string;
|
|
28
|
-
}): Promise<HandOutResult> => {
|
|
29
|
-
if ((await findTaskByThreadId(threadId)) !== undefined) {
|
|
30
|
-
return 'ALREADY_HANDED_OUT';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const run = await getWorkflowRun('application', workflowRunId);
|
|
34
|
-
const request = getFormStepTaskRequests({
|
|
35
|
-
state: run?.state,
|
|
36
|
-
runStarterWorkspaceMemberId: run?.createdBy?.workspaceMemberId,
|
|
37
|
-
now: new Date(),
|
|
38
|
-
}).find(
|
|
39
|
-
(candidate) => candidate.stepId === stepId && candidate.threadId === threadId,
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
if (run === undefined || request === undefined) {
|
|
43
|
-
return 'NOT_WAITING';
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const workflow =
|
|
47
|
-
typeof run.workflowId === 'string'
|
|
48
|
-
? await getWorkflow('application', run.workflowId)
|
|
49
|
-
: undefined;
|
|
50
|
-
|
|
51
|
-
const task = await createTask('application', {
|
|
52
|
-
title: request.title,
|
|
53
|
-
assigneeId: request.assigneeWorkspaceMemberId,
|
|
54
|
-
dueAt: request.dueAt ?? null,
|
|
55
|
-
status: TASK_TODO_STATUS,
|
|
56
|
-
smbSection:
|
|
57
|
-
workflow?.smbSection === DOCFLOW_SECTION ? DOCFLOW_SECTION : CRM_SECTION,
|
|
58
|
-
smbFormSchema: request.fields,
|
|
59
|
-
smbFormValues: {},
|
|
60
|
-
smbWorkflowRunId: run.id,
|
|
61
|
-
smbWorkflowStepId: request.stepId,
|
|
62
|
-
smbWorkflowThreadId: request.threadId,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const target = getRunTargetRecord(run.state);
|
|
66
|
-
|
|
67
|
-
// The task stands without the link: a record deleted meanwhile must not
|
|
68
|
-
// cancel handing it out.
|
|
69
|
-
if (task !== undefined && target !== undefined) {
|
|
70
|
-
await createTaskTarget({
|
|
71
|
-
taskId: task.id,
|
|
72
|
-
[target.targetFieldName]: target.recordId,
|
|
73
|
-
}).catch(() => undefined);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return 'CREATED';
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// A step answered on the run page or by a launch closes its open task with
|
|
80
|
-
// the answer, so the task does not stay open.
|
|
81
|
-
export const closeTasksOfAnsweredSteps = async (
|
|
82
|
-
run: WorkflowRunRecord,
|
|
83
|
-
): Promise<number> => {
|
|
84
|
-
const tasks = await findOpenTasksOfRun(run.id);
|
|
85
|
-
let closedCount = 0;
|
|
86
|
-
|
|
87
|
-
for (const task of tasks) {
|
|
88
|
-
const stepInfo =
|
|
89
|
-
typeof task.smbWorkflowStepId === 'string'
|
|
90
|
-
? run.state?.stepInfos?.[task.smbWorkflowStepId]
|
|
91
|
-
: undefined;
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
stepInfo?.status !== 'SUCCESS' ||
|
|
95
|
-
stepInfo.threadId !== task.smbWorkflowThreadId
|
|
96
|
-
) {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const answer =
|
|
101
|
-
typeof stepInfo.result === 'object' && stepInfo.result !== null
|
|
102
|
-
? (stepInfo.result as Record<string, unknown>)
|
|
103
|
-
: {};
|
|
104
|
-
const fieldNames = parseFormFields(task.smbFormSchema).map(({ name }) => name);
|
|
105
|
-
|
|
106
|
-
await updateTask('application', task.id, {
|
|
107
|
-
smbFormValues: Object.fromEntries(
|
|
108
|
-
fieldNames.filter((name) => name in answer).map((name) => [name, answer[name]]),
|
|
109
|
-
),
|
|
110
|
-
status: TASK_DONE_STATUS,
|
|
111
|
-
});
|
|
112
|
-
closedCount++;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return closedCount;
|
|
116
|
-
};
|
package/src/server/records.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { RestApiClient } from 'twenty-client-sdk/rest';
|
|
2
|
-
|
|
3
|
-
import { restAnd, restEq, restNeq, restIsNull, restOr } from 'src/shared/api/rest-filter.util';
|
|
4
|
-
import { readRestList, readRestRecord } from 'src/shared/api/rest-response.util';
|
|
5
|
-
import { TASK_DONE_STATUS } from 'src/shared/docflow.constants';
|
|
6
|
-
import {
|
|
7
|
-
type WorkflowRunState,
|
|
8
|
-
type WorkflowStep,
|
|
9
|
-
type WorkflowTrigger,
|
|
10
|
-
} from 'src/shared/workflow/workflow-run.type';
|
|
11
|
-
|
|
12
|
-
// Every record the server functions read or write goes through here, over
|
|
13
|
-
// the REST API: it is the most stable public surface of Twenty, so a new core
|
|
14
|
-
// version changes at most this file.
|
|
15
|
-
export type RunAs = 'person' | 'application';
|
|
16
|
-
|
|
17
|
-
const clients: Record<RunAs, RestApiClient> = {
|
|
18
|
-
person: new RestApiClient(),
|
|
19
|
-
application: new RestApiClient({ runAs: 'application' }),
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const restClient = (runAs: RunAs): RestApiClient => clients[runAs];
|
|
23
|
-
|
|
24
|
-
export type DocflowTaskRecord = {
|
|
25
|
-
id: string;
|
|
26
|
-
title?: string | null;
|
|
27
|
-
status?: string | null;
|
|
28
|
-
assigneeId?: string | null;
|
|
29
|
-
smbSection?: string | null;
|
|
30
|
-
smbFormSchema?: unknown;
|
|
31
|
-
smbFormValues?: unknown;
|
|
32
|
-
smbWorkflowRunId?: string | null;
|
|
33
|
-
smbWorkflowStepId?: string | null;
|
|
34
|
-
smbWorkflowThreadId?: string | null;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export type WorkflowRunRecord = {
|
|
38
|
-
id: string;
|
|
39
|
-
name?: string | null;
|
|
40
|
-
status?: string | null;
|
|
41
|
-
workflowId?: string | null;
|
|
42
|
-
state?: WorkflowRunState | null;
|
|
43
|
-
createdBy?: { workspaceMemberId?: string | null } | null;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export type WorkflowRecord = {
|
|
47
|
-
id: string;
|
|
48
|
-
smbSection?: string | null;
|
|
49
|
-
smbLaunchFields?: unknown;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export type WorkflowVersionRecord = {
|
|
53
|
-
id: string;
|
|
54
|
-
status?: string | null;
|
|
55
|
-
workflowId?: string | null;
|
|
56
|
-
trigger?: WorkflowTrigger | null;
|
|
57
|
-
steps?: WorkflowStep[] | null;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export const getTask = async (runAs: RunAs, taskId: string) =>
|
|
61
|
-
readRestRecord<DocflowTaskRecord>(
|
|
62
|
-
await restClient(runAs).get(`/rest/tasks/${taskId}`),
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
export const updateTask = async (
|
|
66
|
-
runAs: RunAs,
|
|
67
|
-
taskId: string,
|
|
68
|
-
data: Partial<Omit<DocflowTaskRecord, 'id'>> & Record<string, unknown>,
|
|
69
|
-
) =>
|
|
70
|
-
readRestRecord<DocflowTaskRecord>(
|
|
71
|
-
await restClient(runAs).patch(`/rest/tasks/${taskId}`, data),
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
export const createTask = async (
|
|
75
|
-
runAs: RunAs,
|
|
76
|
-
data: Record<string, unknown>,
|
|
77
|
-
) =>
|
|
78
|
-
readRestRecord<DocflowTaskRecord>(
|
|
79
|
-
await restClient(runAs).post('/rest/tasks', data),
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
export const findTaskByThreadId = async (threadId: string) =>
|
|
83
|
-
readRestList<DocflowTaskRecord>(
|
|
84
|
-
await restClient('application').get('/rest/tasks', {
|
|
85
|
-
query: { filter: restEq('smbWorkflowThreadId', threadId), limit: 1 },
|
|
86
|
-
}),
|
|
87
|
-
).records[0];
|
|
88
|
-
|
|
89
|
-
export const findOpenTasksOfRun = async (workflowRunId: string) =>
|
|
90
|
-
readRestList<DocflowTaskRecord>(
|
|
91
|
-
await restClient('application').get('/rest/tasks', {
|
|
92
|
-
query: {
|
|
93
|
-
filter: restAnd(
|
|
94
|
-
restEq('smbWorkflowRunId', workflowRunId),
|
|
95
|
-
restOr(restNeq('status', TASK_DONE_STATUS), restIsNull('status')),
|
|
96
|
-
),
|
|
97
|
-
limit: 200,
|
|
98
|
-
},
|
|
99
|
-
}),
|
|
100
|
-
).records;
|
|
101
|
-
|
|
102
|
-
export const getWorkflowRun = async (runAs: RunAs, workflowRunId: string) =>
|
|
103
|
-
readRestRecord<WorkflowRunRecord>(
|
|
104
|
-
await restClient(runAs).get(`/rest/workflowRuns/${workflowRunId}`),
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
export const getWorkflow = async (runAs: RunAs, workflowId: string) =>
|
|
108
|
-
readRestRecord<WorkflowRecord>(
|
|
109
|
-
await restClient(runAs).get(`/rest/workflows/${workflowId}`),
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
export const getWorkflowVersion = async (
|
|
113
|
-
runAs: RunAs,
|
|
114
|
-
workflowVersionId: string,
|
|
115
|
-
) =>
|
|
116
|
-
readRestRecord<WorkflowVersionRecord>(
|
|
117
|
-
await restClient(runAs).get(`/rest/workflowVersions/${workflowVersionId}`),
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
export const listWorkflowVersions = async (runAs: RunAs, workflowId: string) =>
|
|
121
|
-
readRestList<WorkflowVersionRecord>(
|
|
122
|
-
await restClient(runAs).get('/rest/workflowVersions', {
|
|
123
|
-
query: {
|
|
124
|
-
filter: restEq('workflowId', workflowId),
|
|
125
|
-
order_by: 'createdAt[DescNullsLast]',
|
|
126
|
-
limit: 50,
|
|
127
|
-
},
|
|
128
|
-
}),
|
|
129
|
-
).records;
|
|
130
|
-
|
|
131
|
-
export const createTaskTarget = async (data: Record<string, unknown>) =>
|
|
132
|
-
restClient('application').post('/rest/taskTargets', data);
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { CoreGraphqlError } from 'src/shared/api/core-graphql.util';
|
|
2
|
-
import { TASK_DONE_STATUS } from 'src/shared/docflow.constants';
|
|
3
|
-
import { isAppEngine } from 'src/shared/engine';
|
|
4
|
-
import { type FormValues } from 'src/shared/form/form-field.type';
|
|
5
|
-
import {
|
|
6
|
-
findMissingRequiredFields,
|
|
7
|
-
parseFormFields,
|
|
8
|
-
parseFormValues,
|
|
9
|
-
} from 'src/shared/form/form-fields.util';
|
|
10
|
-
import { getTask, getWorkflowRun, updateTask, type DocflowTaskRecord } from 'src/server/records';
|
|
11
|
-
import { answerFormStep } from 'src/server/workflow-engine';
|
|
12
|
-
|
|
13
|
-
export type CompleteTaskResult =
|
|
14
|
-
| { status: 'DONE' }
|
|
15
|
-
| { status: 'NOT_FOUND' }
|
|
16
|
-
| { status: 'MISSING_REQUIRED'; labels: string[] }
|
|
17
|
-
| { status: 'FAILED'; message: string };
|
|
18
|
-
|
|
19
|
-
export const isProcessTask = (task: DocflowTaskRecord): boolean =>
|
|
20
|
-
typeof task.smbWorkflowThreadId === 'string' &&
|
|
21
|
-
task.smbWorkflowThreadId.length > 0 &&
|
|
22
|
-
typeof task.smbWorkflowRunId === 'string' &&
|
|
23
|
-
typeof task.smbWorkflowStepId === 'string';
|
|
24
|
-
|
|
25
|
-
// Answers the process step of a task as the person completing it. A step
|
|
26
|
-
// that is no longer waiting (answered elsewhere, run stopped) is not an error.
|
|
27
|
-
export const answerStepOfTask = async (
|
|
28
|
-
task: DocflowTaskRecord,
|
|
29
|
-
values: FormValues,
|
|
30
|
-
): Promise<CompleteTaskResult | undefined> => {
|
|
31
|
-
if (!isProcessTask(task) || !isAppEngine()) {
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const run = await getWorkflowRun('person', task.smbWorkflowRunId as string);
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
const result = await answerFormStep({
|
|
39
|
-
state: run?.state,
|
|
40
|
-
stepId: task.smbWorkflowStepId as string,
|
|
41
|
-
threadId: task.smbWorkflowThreadId as string,
|
|
42
|
-
values,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
return result.status === 'MISSING_REQUIRED'
|
|
46
|
-
? { status: 'MISSING_REQUIRED', labels: result.labels }
|
|
47
|
-
: undefined;
|
|
48
|
-
} catch (error) {
|
|
49
|
-
const refreshedRun = await getWorkflowRun('person', task.smbWorkflowRunId as string);
|
|
50
|
-
|
|
51
|
-
if (refreshedRun?.state?.stepInfos?.[task.smbWorkflowStepId as string]?.status === 'SUCCESS') {
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
status: 'FAILED',
|
|
57
|
-
message:
|
|
58
|
-
error instanceof CoreGraphqlError && error.userMessage !== undefined
|
|
59
|
-
? error.userMessage
|
|
60
|
-
: 'Процесс не принял ответ. Для ответа на шаги процессов нужно право «Рабочие процессы».',
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export const completeTask = async ({
|
|
66
|
-
taskId,
|
|
67
|
-
values,
|
|
68
|
-
}: {
|
|
69
|
-
taskId: string;
|
|
70
|
-
values: FormValues | undefined;
|
|
71
|
-
}): Promise<CompleteTaskResult> => {
|
|
72
|
-
const task = await getTask('person', taskId);
|
|
73
|
-
|
|
74
|
-
if (task === undefined) {
|
|
75
|
-
return { status: 'NOT_FOUND' };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const fields = parseFormFields(task.smbFormSchema);
|
|
79
|
-
const currentValues = values ?? parseFormValues(task.smbFormValues);
|
|
80
|
-
|
|
81
|
-
if (values !== undefined) {
|
|
82
|
-
await updateTask('person', taskId, { smbFormValues: values });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const missingFields = findMissingRequiredFields({ fields, values: currentValues });
|
|
86
|
-
|
|
87
|
-
if (missingFields.length > 0) {
|
|
88
|
-
return { status: 'MISSING_REQUIRED', labels: missingFields.map(({ label }) => label) };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const stepResult = await answerStepOfTask(task, currentValues);
|
|
92
|
-
|
|
93
|
-
if (stepResult !== undefined) {
|
|
94
|
-
return stepResult;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
await updateTask('person', taskId, { status: TASK_DONE_STATUS });
|
|
98
|
-
|
|
99
|
-
return { status: 'DONE' };
|
|
100
|
-
};
|