@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,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
- });