@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,286 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
import { defineFrontComponent } from 'twenty-sdk/define';
|
|
3
|
-
import { enqueueSnackbar, useSelectedRecordIds } from 'twenty-sdk/front-component';
|
|
4
|
-
import { Button } from 'twenty-ui/primitives/input';
|
|
5
|
-
import { useTheme } from 'twenty-ui/theme';
|
|
6
|
-
|
|
7
|
-
import { callAppRoute, getErrorMessage, getRecord, listRecords } from 'src/front/api';
|
|
8
|
-
import { formatMemberName } from 'src/front/format';
|
|
9
|
-
import { useAsync } from 'src/front/hooks/use-async';
|
|
10
|
-
import { useWorkspaceMembers } from 'src/front/hooks/use-workspace-members';
|
|
11
|
-
import {
|
|
12
|
-
CheckboxField,
|
|
13
|
-
DocflowRoot,
|
|
14
|
-
Message,
|
|
15
|
-
SectionTitle,
|
|
16
|
-
SelectField,
|
|
17
|
-
TextField,
|
|
18
|
-
VariableChip,
|
|
19
|
-
} from 'src/front/ui/components';
|
|
20
|
-
import { FormFieldsBuilder } from 'src/front/ui/form-fields-builder';
|
|
21
|
-
import { restAnd, restEq } from 'src/shared/api/rest-filter.util';
|
|
22
|
-
import { LAUNCH_PARAMETERS_STEP_NAME } from 'src/shared/docflow.constants';
|
|
23
|
-
import { type FormField } from 'src/shared/form/form-field.type';
|
|
24
|
-
import { getFillableFormFields, getFormFieldsError, parseFormFields } from 'src/shared/form/form-fields.util';
|
|
25
|
-
import { readStepSettings, type StepSettings } from 'src/shared/form/step-settings.util';
|
|
26
|
-
import { findLaunchParametersStep, parseLaunchFields } from 'src/shared/workflow/launch-step.util';
|
|
27
|
-
import { type WorkflowStep } from 'src/shared/workflow/workflow-run.type';
|
|
28
|
-
import { getFormStepFields, isFormStep } from 'src/shared/workflow/workflow-run.util';
|
|
29
|
-
import { PROCESS_STEPS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
30
|
-
|
|
31
|
-
const NO_ASSIGNEE = '';
|
|
32
|
-
|
|
33
|
-
type DraftVersion = { id: string; steps?: WorkflowStep[] | null };
|
|
34
|
-
type ProcessRecord = { id: string; smbLaunchFields?: unknown };
|
|
35
|
-
|
|
36
|
-
const sectionStyle = { display: 'flex', flexDirection: 'column' as const };
|
|
37
|
-
|
|
38
|
-
const LaunchParametersEditor = ({
|
|
39
|
-
workflowId,
|
|
40
|
-
initialFields,
|
|
41
|
-
launchStepId,
|
|
42
|
-
onSaved,
|
|
43
|
-
}: {
|
|
44
|
-
workflowId: string;
|
|
45
|
-
initialFields: FormField[];
|
|
46
|
-
launchStepId: string | undefined;
|
|
47
|
-
onSaved: () => void;
|
|
48
|
-
}) => {
|
|
49
|
-
const theme = useTheme();
|
|
50
|
-
const [fields, setFields] = useState(initialFields);
|
|
51
|
-
const [isSaving, setIsSaving] = useState(false);
|
|
52
|
-
const [errorMessage, setErrorMessage] = useState<string | undefined>();
|
|
53
|
-
|
|
54
|
-
const save = async () => {
|
|
55
|
-
const fieldsError = getFormFieldsError(fields);
|
|
56
|
-
|
|
57
|
-
if (fieldsError !== undefined) {
|
|
58
|
-
setErrorMessage(fieldsError);
|
|
59
|
-
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
setIsSaving(true);
|
|
64
|
-
setErrorMessage(undefined);
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
await callAppRoute('/docflow/processes/launch-fields', { workflowId, fields });
|
|
68
|
-
await enqueueSnackbar({
|
|
69
|
-
message:
|
|
70
|
-
fields.length > 0
|
|
71
|
-
? `Параметры сохранены, шаг «${LAUNCH_PARAMETERS_STEP_NAME}» стоит в черновике первым`
|
|
72
|
-
: 'Параметры убраны',
|
|
73
|
-
variant: 'success',
|
|
74
|
-
});
|
|
75
|
-
onSaved();
|
|
76
|
-
} catch (error) {
|
|
77
|
-
setErrorMessage(getErrorMessage(error, 'Не удалось сохранить параметры'));
|
|
78
|
-
} finally {
|
|
79
|
-
setIsSaving(false);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<section style={{ ...sectionStyle, gap: theme.spacing[2] }} aria-label={LAUNCH_PARAMETERS_STEP_NAME}>
|
|
85
|
-
<SectionTitle>{LAUNCH_PARAMETERS_STEP_NAME}</SectionTitle>
|
|
86
|
-
<Message tone="hint">
|
|
87
|
-
Что человек заполняет перед запуском. Значения хранятся в запуске, а следующие шаги читают их
|
|
88
|
-
переменными шага «{LAUNCH_PARAMETERS_STEP_NAME}»: шаг ставится в черновик сам при сохранении.
|
|
89
|
-
</Message>
|
|
90
|
-
<FormFieldsBuilder
|
|
91
|
-
fields={fields}
|
|
92
|
-
onChange={setFields}
|
|
93
|
-
variablePrefix={launchStepId}
|
|
94
|
-
disabled={isSaving}
|
|
95
|
-
/>
|
|
96
|
-
{errorMessage !== undefined && <Message tone="danger">{errorMessage}</Message>}
|
|
97
|
-
<div>
|
|
98
|
-
<Button variant="solid" size="sm" loading={isSaving} onClick={() => void save()}>
|
|
99
|
-
Сохранить параметры
|
|
100
|
-
</Button>
|
|
101
|
-
</div>
|
|
102
|
-
</section>
|
|
103
|
-
);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const StepSettingsEditor = ({
|
|
107
|
-
workflowVersionId,
|
|
108
|
-
step,
|
|
109
|
-
}: {
|
|
110
|
-
workflowVersionId: string;
|
|
111
|
-
step: WorkflowStep;
|
|
112
|
-
}) => {
|
|
113
|
-
const theme = useTheme();
|
|
114
|
-
const { members } = useWorkspaceMembers();
|
|
115
|
-
const fields = parseFormFields(getFormStepFields(step));
|
|
116
|
-
const [settings, setSettings] = useState<StepSettings>(readStepSettings(fields));
|
|
117
|
-
const [isSaving, setIsSaving] = useState(false);
|
|
118
|
-
|
|
119
|
-
const save = async () => {
|
|
120
|
-
setIsSaving(true);
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
await callAppRoute('/docflow/processes/step-settings', {
|
|
124
|
-
workflowVersionId,
|
|
125
|
-
stepId: step.id,
|
|
126
|
-
settings,
|
|
127
|
-
});
|
|
128
|
-
await enqueueSnackbar({ message: 'Настройки шага сохранены', variant: 'success' });
|
|
129
|
-
} catch (error) {
|
|
130
|
-
await enqueueSnackbar({
|
|
131
|
-
message: getErrorMessage(error, 'Не удалось сохранить настройки шага'),
|
|
132
|
-
variant: 'error',
|
|
133
|
-
});
|
|
134
|
-
} finally {
|
|
135
|
-
setIsSaving(false);
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
const toggleRequired = (fieldId: string, isRequired: boolean) =>
|
|
140
|
-
setSettings((current) => ({
|
|
141
|
-
...current,
|
|
142
|
-
requiredFieldIds: isRequired
|
|
143
|
-
? [...current.requiredFieldIds, fieldId]
|
|
144
|
-
: current.requiredFieldIds.filter((id) => id !== fieldId),
|
|
145
|
-
}));
|
|
146
|
-
|
|
147
|
-
return (
|
|
148
|
-
<div
|
|
149
|
-
style={{
|
|
150
|
-
background: theme.background.transparent.lighter,
|
|
151
|
-
borderRadius: theme.border.radius.md,
|
|
152
|
-
display: 'flex',
|
|
153
|
-
flexDirection: 'column',
|
|
154
|
-
gap: theme.spacing[2],
|
|
155
|
-
padding: theme.spacing[3],
|
|
156
|
-
}}
|
|
157
|
-
>
|
|
158
|
-
<strong>{step.name}</strong>
|
|
159
|
-
<div style={{ display: 'flex', flexWrap: 'wrap', gap: theme.spacing[2] }}>
|
|
160
|
-
<div style={{ flex: '2 1 200px' }}>
|
|
161
|
-
<SelectField
|
|
162
|
-
label="Кто выполняет задание"
|
|
163
|
-
value={settings.assignee ?? NO_ASSIGNEE}
|
|
164
|
-
options={[
|
|
165
|
-
{ value: NO_ASSIGNEE, label: 'Запустивший процесс' },
|
|
166
|
-
...members.map((member) => ({ value: member.id, label: formatMemberName(member) || member.id })),
|
|
167
|
-
]}
|
|
168
|
-
onChange={(assignee) =>
|
|
169
|
-
setSettings((current) => ({ ...current, assignee: assignee === NO_ASSIGNEE ? null : assignee }))
|
|
170
|
-
}
|
|
171
|
-
/>
|
|
172
|
-
</div>
|
|
173
|
-
<div style={{ flex: '1 1 120px' }}>
|
|
174
|
-
<TextField
|
|
175
|
-
label="Срок в днях"
|
|
176
|
-
type="number"
|
|
177
|
-
value={settings.dueInDays === null ? '' : String(settings.dueInDays)}
|
|
178
|
-
onChange={(value) =>
|
|
179
|
-
setSettings((current) => ({ ...current, dueInDays: value === '' ? null : Number(value) }))
|
|
180
|
-
}
|
|
181
|
-
/>
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
{getFillableFormFields(fields).map((field) => (
|
|
185
|
-
<div key={field.id} style={{ alignItems: 'center', display: 'flex', flexWrap: 'wrap', gap: theme.spacing[2] }}>
|
|
186
|
-
<CheckboxField
|
|
187
|
-
label={`${field.label} обязательное`}
|
|
188
|
-
checked={settings.requiredFieldIds.includes(field.id)}
|
|
189
|
-
onChange={(isRequired) => toggleRequired(field.id, isRequired)}
|
|
190
|
-
/>
|
|
191
|
-
<VariableChip>{`{{${step.id}.${field.name}}}`}</VariableChip>
|
|
192
|
-
</div>
|
|
193
|
-
))}
|
|
194
|
-
<div>
|
|
195
|
-
<Button variant="outline" size="sm" loading={isSaving} onClick={() => void save()}>
|
|
196
|
-
Сохранить шаг
|
|
197
|
-
</Button>
|
|
198
|
-
</div>
|
|
199
|
-
</div>
|
|
200
|
-
);
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
// Tab of a workflow's record page: the parameters asked before the launch and
|
|
204
|
-
// who fills each "Human input" step of the draft, by when, which fields are
|
|
205
|
-
// required. The standard editor has no place for either.
|
|
206
|
-
const ProcessSteps = () => {
|
|
207
|
-
const theme = useTheme();
|
|
208
|
-
const [workflowId] = useSelectedRecordIds();
|
|
209
|
-
const [launchEditorKey, setLaunchEditorKey] = useState(0);
|
|
210
|
-
const { data, error, loading, reload } = useAsync(async () => {
|
|
211
|
-
if (workflowId === undefined) {
|
|
212
|
-
return undefined;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const [process, drafts] = await Promise.all([
|
|
216
|
-
getRecord<ProcessRecord>('workflows', workflowId),
|
|
217
|
-
listRecords<DraftVersion>('workflowVersions', {
|
|
218
|
-
filter: restAnd(restEq('workflowId', workflowId), restEq('status', 'DRAFT')),
|
|
219
|
-
limit: 1,
|
|
220
|
-
}),
|
|
221
|
-
]);
|
|
222
|
-
|
|
223
|
-
return { process, draft: drafts.records[0] };
|
|
224
|
-
}, [workflowId]);
|
|
225
|
-
const draft = data?.draft;
|
|
226
|
-
const formSteps = draft?.steps?.filter(isFormStep) ?? [];
|
|
227
|
-
|
|
228
|
-
// The steps are edited in the diagram next to this tab, so what was loaded
|
|
229
|
-
// goes stale; read the draft again whenever the tab gets the focus back.
|
|
230
|
-
useEffect(() => {
|
|
231
|
-
const handleVisible = () => {
|
|
232
|
-
if (document.visibilityState === 'visible') {
|
|
233
|
-
void reload();
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
window.addEventListener('focus', handleVisible);
|
|
238
|
-
document.addEventListener('visibilitychange', handleVisible);
|
|
239
|
-
|
|
240
|
-
return () => {
|
|
241
|
-
window.removeEventListener('focus', handleVisible);
|
|
242
|
-
document.removeEventListener('visibilitychange', handleVisible);
|
|
243
|
-
};
|
|
244
|
-
}, [reload]);
|
|
245
|
-
|
|
246
|
-
return (
|
|
247
|
-
<DocflowRoot>
|
|
248
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: theme.spacing[6], padding: theme.spacing[3] }}>
|
|
249
|
-
{loading && data === undefined && <Message>Загрузка…</Message>}
|
|
250
|
-
{error !== undefined && (
|
|
251
|
-
<Message tone="danger">{getErrorMessage(error, 'Не удалось прочитать процесс')}</Message>
|
|
252
|
-
)}
|
|
253
|
-
{workflowId !== undefined && data?.process !== undefined && (
|
|
254
|
-
<LaunchParametersEditor
|
|
255
|
-
key={`${workflowId}:${launchEditorKey}`}
|
|
256
|
-
workflowId={workflowId}
|
|
257
|
-
initialFields={parseLaunchFields(data.process.smbLaunchFields)}
|
|
258
|
-
launchStepId={findLaunchParametersStep(draft?.steps)?.id}
|
|
259
|
-
onSaved={() => {
|
|
260
|
-
void reload().then(() => setLaunchEditorKey((current) => current + 1));
|
|
261
|
-
}}
|
|
262
|
-
/>
|
|
263
|
-
)}
|
|
264
|
-
{draft !== undefined && formSteps.length > 0 && (
|
|
265
|
-
<section style={{ ...sectionStyle, gap: theme.spacing[2] }} aria-label="Задания шагов">
|
|
266
|
-
<SectionTitle>Задания шагов «Человеческий ввод»</SectionTitle>
|
|
267
|
-
<Message tone="hint">
|
|
268
|
-
Каждый такой шаг становится заданием. Поля шага задаются в редакторе процесса; заполненные
|
|
269
|
-
значения следующие шаги читают переменными, показанными рядом с полем.
|
|
270
|
-
</Message>
|
|
271
|
-
{formSteps.map((step) => (
|
|
272
|
-
<StepSettingsEditor key={`${draft.id}:${step.id}`} workflowVersionId={draft.id} step={step} />
|
|
273
|
-
))}
|
|
274
|
-
</section>
|
|
275
|
-
)}
|
|
276
|
-
</div>
|
|
277
|
-
</DocflowRoot>
|
|
278
|
-
);
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
export default defineFrontComponent({
|
|
282
|
-
universalIdentifier: PROCESS_STEPS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
|
283
|
-
name: 'docflow-process-steps',
|
|
284
|
-
description: 'Параметры запуска процесса и задания его шагов',
|
|
285
|
-
component: ProcessSteps,
|
|
286
|
-
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { defineFrontComponent } from 'twenty-sdk/define';
|
|
2
|
-
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
|
|
3
|
-
import { useTheme } from 'twenty-ui/theme';
|
|
4
|
-
|
|
5
|
-
import { DocflowRoot, Message } from 'src/front/ui/components';
|
|
6
|
-
import { TaskWorkPanel } from 'src/front/ui/task-work-panel';
|
|
7
|
-
import { TASK_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
8
|
-
|
|
9
|
-
// The same task panel as on the docflow page, on the task's own record page:
|
|
10
|
-
// one task, wherever it is opened.
|
|
11
|
-
const TaskForm = () => {
|
|
12
|
-
const theme = useTheme();
|
|
13
|
-
const [taskId] = useSelectedRecordIds();
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<DocflowRoot>
|
|
17
|
-
<div style={{ padding: theme.spacing[3] }}>
|
|
18
|
-
{taskId === undefined ? <Message>Откройте задачу.</Message> : <TaskWorkPanel taskId={taskId} />}
|
|
19
|
-
</div>
|
|
20
|
-
</DocflowRoot>
|
|
21
|
-
);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export default defineFrontComponent({
|
|
25
|
-
universalIdentifier: TASK_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
|
26
|
-
name: 'docflow-task-form',
|
|
27
|
-
description: 'Инструкция, поля задания и выполнение в карточке задачи',
|
|
28
|
-
component: TaskForm,
|
|
29
|
-
});
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { defineLogicFunction } from 'twenty-sdk/define';
|
|
2
|
-
import { type RoutePayload } from 'twenty-sdk/logic-function';
|
|
3
|
-
|
|
4
|
-
import { restAnd, restEq, restIsNull, restNeq, restOr } from 'src/shared/api/rest-filter.util';
|
|
5
|
-
import { readRestList } from 'src/shared/api/rest-response.util';
|
|
6
|
-
import { BitrixError, callBitrix, isBitrixConfigured } from 'src/shared/bitrix/bitrix-client';
|
|
7
|
-
import { parseBitrixRequest } from 'src/shared/bitrix/bitrix.util';
|
|
8
|
-
import { DOCFLOW_SECTION, TASK_DONE_STATUS } from 'src/shared/docflow.constants';
|
|
9
|
-
import { proveAndLinkBitrixPortal } from 'src/server/bitrix-portal';
|
|
10
|
-
import { renderBitrixPage, type BitrixPageTask } from 'src/server/bitrix-page';
|
|
11
|
-
import { htmlResponse } from 'src/server/http-response';
|
|
12
|
-
import { restClient } from 'src/server/records';
|
|
13
|
-
import { BITRIX_APP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
14
|
-
|
|
15
|
-
const TASKS_SHOWN_IN_PORTAL = 20;
|
|
16
|
-
|
|
17
|
-
const getCrmUrl = (): string | undefined => {
|
|
18
|
-
const value = process.env.CRM_PUBLIC_URL?.trim();
|
|
19
|
-
|
|
20
|
-
return value !== undefined && /^https?:\/\//.test(value)
|
|
21
|
-
? value.replace(/\/$/, '')
|
|
22
|
-
: undefined;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// The person is found by the e-mail of their Bitrix24 account; their open
|
|
26
|
-
// docflow tasks are listed with links into the CRM.
|
|
27
|
-
const findMyTasks = async (
|
|
28
|
-
email: string,
|
|
29
|
-
crmUrl: string | undefined,
|
|
30
|
-
): Promise<BitrixPageTask[] | undefined> => {
|
|
31
|
-
const [member] = readRestList<{ id: string }>(
|
|
32
|
-
await restClient('application').get('/rest/workspaceMembers', {
|
|
33
|
-
query: { filter: restEq('userEmail', email), limit: 1 },
|
|
34
|
-
}),
|
|
35
|
-
).records;
|
|
36
|
-
|
|
37
|
-
if (member === undefined) {
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return readRestList<{ id: string; title?: string | null; dueAt?: string | null }>(
|
|
42
|
-
await restClient('application').get('/rest/tasks', {
|
|
43
|
-
query: {
|
|
44
|
-
filter: restAnd(
|
|
45
|
-
restEq('smbSection', DOCFLOW_SECTION),
|
|
46
|
-
restEq('assigneeId', member.id),
|
|
47
|
-
restOr(restNeq('status', TASK_DONE_STATUS), restIsNull('status')),
|
|
48
|
-
),
|
|
49
|
-
order_by: 'dueAt[AscNullsLast]',
|
|
50
|
-
limit: TASKS_SHOWN_IN_PORTAL,
|
|
51
|
-
},
|
|
52
|
-
}),
|
|
53
|
-
).records.map((task) => ({
|
|
54
|
-
title: task.title?.trim() || 'Без названия',
|
|
55
|
-
url: crmUrl !== undefined ? `${crmUrl}/object/task/${task.id}` : undefined,
|
|
56
|
-
dueAt:
|
|
57
|
-
typeof task.dueAt === 'string'
|
|
58
|
-
? new Date(task.dueAt).toLocaleDateString('ru-RU')
|
|
59
|
-
: undefined,
|
|
60
|
-
}));
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// "Путь вашего обработчика" of the local Bitrix24 application: the page a
|
|
64
|
-
// person sees when they open the app in the portal.
|
|
65
|
-
const handler = async (event: RoutePayload) => {
|
|
66
|
-
const crmUrl = getCrmUrl();
|
|
67
|
-
|
|
68
|
-
if (!isBitrixConfigured()) {
|
|
69
|
-
return htmlResponse(
|
|
70
|
-
503,
|
|
71
|
-
renderBitrixPage({
|
|
72
|
-
title: 'Документооборот',
|
|
73
|
-
message: 'В настройках приложения в CRM не указаны BITRIX_CLIENT_ID и BITRIX_CLIENT_SECRET.',
|
|
74
|
-
crmUrl,
|
|
75
|
-
tasks: [],
|
|
76
|
-
isInstall: false,
|
|
77
|
-
}),
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const request = parseBitrixRequest({
|
|
82
|
-
rawBody: event.rawBody,
|
|
83
|
-
body: event.body,
|
|
84
|
-
query: event.queryStringParameters,
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const portal = await proveAndLinkBitrixPortal(request);
|
|
89
|
-
const user = await callBitrix<{ EMAIL?: string }>({
|
|
90
|
-
restEndpoint: portal.restEndpoint,
|
|
91
|
-
accessToken: request.authId ?? portal.accessToken,
|
|
92
|
-
method: 'user.current',
|
|
93
|
-
params: {},
|
|
94
|
-
});
|
|
95
|
-
const tasks =
|
|
96
|
-
typeof user.EMAIL === 'string' && user.EMAIL.length > 0
|
|
97
|
-
? await findMyTasks(user.EMAIL, crmUrl)
|
|
98
|
-
: undefined;
|
|
99
|
-
|
|
100
|
-
return htmlResponse(
|
|
101
|
-
200,
|
|
102
|
-
renderBitrixPage({
|
|
103
|
-
title: 'Документооборот',
|
|
104
|
-
message:
|
|
105
|
-
tasks === undefined
|
|
106
|
-
? 'В CRM нет сотрудника с почтой вашего профиля Битрикс24.'
|
|
107
|
-
: tasks.length === 0
|
|
108
|
-
? 'Открытых заданий нет.'
|
|
109
|
-
: 'Ваши открытые задания:',
|
|
110
|
-
crmUrl,
|
|
111
|
-
tasks: tasks ?? [],
|
|
112
|
-
isInstall: false,
|
|
113
|
-
}),
|
|
114
|
-
);
|
|
115
|
-
} catch (error) {
|
|
116
|
-
return htmlResponse(
|
|
117
|
-
403,
|
|
118
|
-
renderBitrixPage({
|
|
119
|
-
title: 'Документооборот',
|
|
120
|
-
message:
|
|
121
|
-
error instanceof BitrixError
|
|
122
|
-
? `Запрос портала не подтверждён: ${error.message}`
|
|
123
|
-
: 'Запрос портала не подтверждён.',
|
|
124
|
-
crmUrl,
|
|
125
|
-
tasks: [],
|
|
126
|
-
isInstall: false,
|
|
127
|
-
}),
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
export default defineLogicFunction({
|
|
133
|
-
universalIdentifier: BITRIX_APP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
|
134
|
-
name: 'docflow-bitrix-app',
|
|
135
|
-
description: 'Страница документооборота в портале Битрикс24',
|
|
136
|
-
timeoutSeconds: 30,
|
|
137
|
-
handler,
|
|
138
|
-
httpRouteTriggerSettings: {
|
|
139
|
-
path: '/bitrix/app',
|
|
140
|
-
httpMethod: 'POST',
|
|
141
|
-
isAuthRequired: false,
|
|
142
|
-
},
|
|
143
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { defineLogicFunction } from 'twenty-sdk/define';
|
|
2
|
-
import { type RoutePayload } from 'twenty-sdk/logic-function';
|
|
3
|
-
|
|
4
|
-
import { parseBitrixRequest } from 'src/shared/bitrix/bitrix.util';
|
|
5
|
-
import { forgetBitrixPortal, loadBitrixPortal, saveBitrixPortal } from 'src/server/bitrix-portal';
|
|
6
|
-
import { jsonResponse } from 'src/server/http-response';
|
|
7
|
-
import { BITRIX_EVENT_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
8
|
-
|
|
9
|
-
// Events of the linked portal. An event must name the linked portal; the
|
|
10
|
-
// application token of the first event is kept, and every later event must
|
|
11
|
-
// carry the same one.
|
|
12
|
-
const handler = async (event: RoutePayload) => {
|
|
13
|
-
const request = parseBitrixRequest({
|
|
14
|
-
rawBody: event.rawBody,
|
|
15
|
-
body: event.body,
|
|
16
|
-
query: event.queryStringParameters,
|
|
17
|
-
});
|
|
18
|
-
const portal = await loadBitrixPortal();
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
portal === null ||
|
|
22
|
-
request.memberId !== portal.memberId ||
|
|
23
|
-
request.applicationToken === undefined ||
|
|
24
|
-
(portal.applicationToken !== undefined &&
|
|
25
|
-
portal.applicationToken !== request.applicationToken)
|
|
26
|
-
) {
|
|
27
|
-
return jsonResponse(403, { message: 'Unknown portal' });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (request.event === 'ONAPPUNINSTALL') {
|
|
31
|
-
await forgetBitrixPortal();
|
|
32
|
-
|
|
33
|
-
return { status: 'UNLINKED' };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (portal.applicationToken === undefined) {
|
|
37
|
-
await saveBitrixPortal({ ...portal, applicationToken: request.applicationToken });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return { status: 'IGNORED' };
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export default defineLogicFunction({
|
|
44
|
-
universalIdentifier: BITRIX_EVENT_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
|
45
|
-
name: 'docflow-bitrix-event',
|
|
46
|
-
description: 'События портала Битрикс24 (удаление приложения)',
|
|
47
|
-
timeoutSeconds: 15,
|
|
48
|
-
handler,
|
|
49
|
-
httpRouteTriggerSettings: {
|
|
50
|
-
path: '/bitrix/event',
|
|
51
|
-
httpMethod: 'POST',
|
|
52
|
-
isAuthRequired: false,
|
|
53
|
-
},
|
|
54
|
-
});
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { defineLogicFunction } from 'twenty-sdk/define';
|
|
2
|
-
import { type RoutePayload } from 'twenty-sdk/logic-function';
|
|
3
|
-
import { RestApiClient } from 'twenty-client-sdk/rest';
|
|
4
|
-
|
|
5
|
-
import { BitrixError, callBitrix, isBitrixConfigured } from 'src/shared/bitrix/bitrix-client';
|
|
6
|
-
import { parseBitrixRequest } from 'src/shared/bitrix/bitrix.util';
|
|
7
|
-
import { proveAndLinkBitrixPortal } from 'src/server/bitrix-portal';
|
|
8
|
-
import { renderBitrixPage } from 'src/server/bitrix-page';
|
|
9
|
-
import { htmlResponse } from 'src/server/http-response';
|
|
10
|
-
import { BITRIX_INSTALL_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
11
|
-
|
|
12
|
-
// "Путь для первоначальной установки" of the local Bitrix24 application.
|
|
13
|
-
const handler = async (event: RoutePayload) => {
|
|
14
|
-
if (!isBitrixConfigured()) {
|
|
15
|
-
return htmlResponse(
|
|
16
|
-
503,
|
|
17
|
-
renderBitrixPage({
|
|
18
|
-
title: 'Документооборот',
|
|
19
|
-
message: 'В настройках приложения в CRM не указаны BITRIX_CLIENT_ID и BITRIX_CLIENT_SECRET.',
|
|
20
|
-
tasks: [],
|
|
21
|
-
isInstall: false,
|
|
22
|
-
}),
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const portal = await proveAndLinkBitrixPortal(
|
|
28
|
-
parseBitrixRequest({
|
|
29
|
-
rawBody: event.rawBody,
|
|
30
|
-
body: event.body,
|
|
31
|
-
query: event.queryStringParameters,
|
|
32
|
-
}),
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
await callBitrix({
|
|
36
|
-
restEndpoint: portal.restEndpoint,
|
|
37
|
-
accessToken: portal.accessToken,
|
|
38
|
-
method: 'event.bind',
|
|
39
|
-
params: {
|
|
40
|
-
event: 'ONAPPUNINSTALL',
|
|
41
|
-
handler: new RestApiClient().resolveUrl('/s/bitrix/event'),
|
|
42
|
-
},
|
|
43
|
-
}).catch(() => undefined);
|
|
44
|
-
|
|
45
|
-
return htmlResponse(
|
|
46
|
-
200,
|
|
47
|
-
renderBitrixPage({
|
|
48
|
-
title: 'Документооборот подключён',
|
|
49
|
-
message: `Портал ${portal.portalDomain} связан с CRM.`,
|
|
50
|
-
tasks: [],
|
|
51
|
-
isInstall: true,
|
|
52
|
-
}),
|
|
53
|
-
);
|
|
54
|
-
} catch (error) {
|
|
55
|
-
return htmlResponse(
|
|
56
|
-
403,
|
|
57
|
-
renderBitrixPage({
|
|
58
|
-
title: 'Документооборот',
|
|
59
|
-
message:
|
|
60
|
-
error instanceof BitrixError
|
|
61
|
-
? `Портал не подтверждён: ${error.message}`
|
|
62
|
-
: 'Портал не подтверждён.',
|
|
63
|
-
tasks: [],
|
|
64
|
-
isInstall: false,
|
|
65
|
-
}),
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export default defineLogicFunction({
|
|
71
|
-
universalIdentifier: BITRIX_INSTALL_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
|
72
|
-
name: 'docflow-bitrix-install',
|
|
73
|
-
description: 'Установка приложения на портале Битрикс24',
|
|
74
|
-
timeoutSeconds: 30,
|
|
75
|
-
handler,
|
|
76
|
-
httpRouteTriggerSettings: {
|
|
77
|
-
path: '/bitrix/install',
|
|
78
|
-
httpMethod: 'POST',
|
|
79
|
-
isAuthRequired: false,
|
|
80
|
-
},
|
|
81
|
-
});
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { defineLogicFunction } from 'twenty-sdk/define';
|
|
2
|
-
|
|
3
|
-
import { captureFile, type FileCapture } from 'src/server/document-capture';
|
|
4
|
-
import { CAPTURE_FILE_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
5
|
-
|
|
6
|
-
const isFileCapture = (payload: Partial<FileCapture>): payload is FileCapture =>
|
|
7
|
-
typeof payload.attachmentId === 'string' &&
|
|
8
|
-
typeof payload.fileId === 'string' &&
|
|
9
|
-
typeof payload.label === 'string' &&
|
|
10
|
-
typeof payload.sourceObjectName === 'string' &&
|
|
11
|
-
typeof payload.sourceRecordId === 'string';
|
|
12
|
-
|
|
13
|
-
const handler = async (payload: Partial<FileCapture>) => {
|
|
14
|
-
if (!isFileCapture(payload)) {
|
|
15
|
-
return { status: 'INVALID_PAYLOAD' };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
status: await captureFile({
|
|
20
|
-
...payload,
|
|
21
|
-
workspaceMemberId: payload.workspaceMemberId ?? null,
|
|
22
|
-
}),
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export default defineLogicFunction({
|
|
27
|
-
universalIdentifier: CAPTURE_FILE_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
|
28
|
-
name: 'docflow-capture-file',
|
|
29
|
-
description: 'Копирует вложенный файл в документы с источником',
|
|
30
|
-
timeoutSeconds: 120,
|
|
31
|
-
handler,
|
|
32
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { defineLogicFunction } from 'twenty-sdk/define';
|
|
2
|
-
import { type RoutePayload } from 'twenty-sdk/logic-function';
|
|
3
|
-
|
|
4
|
-
import { parseFormValues } from 'src/shared/form/form-fields.util';
|
|
5
|
-
import { jsonResponse, readJsonBody } from 'src/server/http-response';
|
|
6
|
-
import { completeTask } from 'src/server/task-completion';
|
|
7
|
-
import { COMPLETE_TASK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
|
|
8
|
-
|
|
9
|
-
const handler = async (event: RoutePayload) => {
|
|
10
|
-
const body = readJsonBody(event.body);
|
|
11
|
-
|
|
12
|
-
if (typeof body.taskId !== 'string') {
|
|
13
|
-
return jsonResponse(400, { message: 'Не указано задание' });
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const result = await completeTask({
|
|
17
|
-
taskId: body.taskId,
|
|
18
|
-
values: body.values === undefined ? undefined : parseFormValues(body.values),
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
switch (result.status) {
|
|
22
|
-
case 'DONE':
|
|
23
|
-
return { status: 'DONE' };
|
|
24
|
-
case 'NOT_FOUND':
|
|
25
|
-
return jsonResponse(404, { message: 'Задание не найдено' });
|
|
26
|
-
case 'MISSING_REQUIRED':
|
|
27
|
-
return jsonResponse(422, {
|
|
28
|
-
message: `Заполните: ${result.labels.join(', ')}`,
|
|
29
|
-
});
|
|
30
|
-
case 'FAILED':
|
|
31
|
-
return jsonResponse(403, { message: result.message });
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export default defineLogicFunction({
|
|
36
|
-
universalIdentifier: COMPLETE_TASK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
|
37
|
-
name: 'docflow-complete-task',
|
|
38
|
-
description: 'Выполняет задание: проверяет поля и продолжает процесс',
|
|
39
|
-
timeoutSeconds: 30,
|
|
40
|
-
handler,
|
|
41
|
-
httpRouteTriggerSettings: {
|
|
42
|
-
path: '/docflow/tasks/complete',
|
|
43
|
-
httpMethod: 'POST',
|
|
44
|
-
isAuthRequired: true,
|
|
45
|
-
},
|
|
46
|
-
});
|