@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,64 +0,0 @@
1
- // An attachment points at the record it is attached to through one of its
2
- // target<Object>Id columns (targetCompanyId, targetTaskId, target<custom>Id).
3
- // The file belongs to that record, not to the attachment.
4
- const TARGET_KEY_PATTERN = /^target([A-Z][A-Za-z0-9]*)Id$/;
5
-
6
- export const getAttachmentTarget = (
7
- attachment: Record<string, unknown>,
8
- ): { objectNameSingular: string; recordId: string } | undefined => {
9
- for (const [key, value] of Object.entries(attachment)) {
10
- const match = TARGET_KEY_PATTERN.exec(key);
11
-
12
- if (match !== null && typeof value === 'string' && value.length > 0) {
13
- const [first, ...rest] = match[1];
14
-
15
- return {
16
- objectNameSingular: `${first.toLowerCase()}${rest.join('')}`,
17
- recordId: value,
18
- };
19
- }
20
- }
21
-
22
- return undefined;
23
- };
24
-
25
- export type FileItem = { fileId: string; label: string; url?: string };
26
-
27
- export const readFileItems = (value: unknown): FileItem[] =>
28
- Array.isArray(value)
29
- ? value.flatMap((item: unknown) => {
30
- if (typeof item !== 'object' || item === null) {
31
- return [];
32
- }
33
-
34
- const { fileId, label, url } = item as Record<string, unknown>;
35
-
36
- return typeof fileId === 'string' && fileId.length > 0 && typeof label === 'string'
37
- ? [{ fileId, label, url: typeof url === 'string' ? url : undefined }]
38
- : [];
39
- })
40
- : [];
41
-
42
- // "Договор 12.pdf" → "Договор 12".
43
- export const getDocumentNameFromFile = (fileName: string): string => {
44
- const extensionStart = fileName.lastIndexOf('.');
45
-
46
- return extensionStart > 0 ? fileName.slice(0, extensionStart) : fileName;
47
- };
48
-
49
- export const formatRecordLabel = (value: unknown): string | undefined => {
50
- if (typeof value === 'string' && value.trim().length > 0) {
51
- return value.trim();
52
- }
53
-
54
- if (typeof value === 'object' && value !== null) {
55
- const { firstName, lastName } = value as Record<string, unknown>;
56
- const fullName = [firstName, lastName]
57
- .filter((part): part is string => typeof part === 'string' && part.length > 0)
58
- .join(' ');
59
-
60
- return fullName.length > 0 ? fullName : undefined;
61
- }
62
-
63
- return undefined;
64
- };
@@ -1,11 +0,0 @@
1
- // Who does the server-side work of docflow in this workspace. APP: the
2
- // app's logic functions (any Twenty). FORK: the NorDiv fork server modules,
3
- // which then must not be doubled by the app.
4
- export type DocflowEngine = 'APP' | 'FORK';
5
-
6
- export const getDocflowEngine = (
7
- value: string | undefined = process.env.DOCFLOW_ENGINE,
8
- ): DocflowEngine => (value === 'FORK' ? 'FORK' : 'APP');
9
-
10
- export const isAppEngine = (value?: string): boolean =>
11
- getDocflowEngine(value) === 'APP';
@@ -1,122 +0,0 @@
1
- import { FORM_FIELD_REQUIRED_SETTING } from 'src/shared/docflow.constants';
2
- import { type FormField } from 'src/shared/form/form-field.type';
3
- import { getFillableFormFields } from 'src/shared/form/form-fields.util';
4
-
5
- // What the field builder offers: the types the standard form step and the
6
- // fork render too, so one field list works in the app, in the fork and in
7
- // the workflow editor.
8
- export const BUILDER_FIELD_KINDS = [
9
- { value: 'TEXT', label: 'Текст', type: 'TEXT' },
10
- { value: 'NUMBER', label: 'Число', type: 'NUMBER' },
11
- { value: 'DATE', label: 'Дата', type: 'DATE' },
12
- { value: 'MEMBER', label: 'Сотрудник', type: 'RECORD', objectName: 'workspaceMember' },
13
- { value: 'COMPANY', label: 'Компания', type: 'RECORD', objectName: 'company' },
14
- { value: 'PERSON', label: 'Контакт', type: 'RECORD', objectName: 'person' },
15
- { value: 'OPPORTUNITY', label: 'Сделка', type: 'RECORD', objectName: 'opportunity' },
16
- ] as const;
17
-
18
- export type BuilderFieldKind = (typeof BUILDER_FIELD_KINDS)[number]['value'];
19
-
20
- const omitSetting = (settings: FormField['settings'], key: string): Record<string, unknown> =>
21
- Object.fromEntries(Object.entries(settings ?? {}).filter(([settingKey]) => settingKey !== key));
22
-
23
- export const getBuilderFieldKind = (field: FormField): BuilderFieldKind | undefined =>
24
- BUILDER_FIELD_KINDS.find(
25
- (kind) =>
26
- kind.type === field.type &&
27
- (!('objectName' in kind) || kind.objectName === field.settings?.objectName),
28
- )?.value;
29
-
30
- export const applyBuilderFieldKind = (field: FormField, kindValue: BuilderFieldKind): FormField => {
31
- const kind = BUILDER_FIELD_KINDS.find((candidate) => candidate.value === kindValue);
32
-
33
- if (kind === undefined) {
34
- return field;
35
- }
36
-
37
- const settings = omitSetting(field.settings, 'objectName');
38
-
39
- return {
40
- ...field,
41
- type: kind.type,
42
- value: undefined,
43
- settings: 'objectName' in kind ? { ...settings, objectName: kind.objectName } : settings,
44
- };
45
- };
46
-
47
- const CYRILLIC_TO_LATIN: Record<string, string> = {
48
- а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'e', ж: 'zh', з: 'z', и: 'i',
49
- й: 'y', к: 'k', л: 'l', м: 'm', н: 'n', о: 'o', п: 'p', р: 'r', с: 's', т: 't',
50
- у: 'u', ф: 'f', х: 'h', ц: 'ts', ч: 'ch', ш: 'sh', щ: 'sch', ъ: '', ы: 'y', ь: '',
51
- э: 'e', ю: 'yu', я: 'ya',
52
- };
53
-
54
- // The name is what later steps write in a variable, {{step.summaDogovora}}:
55
- // latin letters and digits, readable from the label.
56
- export const buildFieldNameFromLabel = (label: string): string => {
57
- const words = [...label.toLowerCase()]
58
- .map((character) => CYRILLIC_TO_LATIN[character] ?? character)
59
- .join('')
60
- .split(/[^a-z0-9]+/)
61
- .filter((word) => word.length > 0);
62
- const name = words
63
- .map((word, index) => (index === 0 ? word : word[0].toUpperCase() + word.slice(1)))
64
- .join('');
65
-
66
- return /^[a-z]/.test(name) ? name : `field${name}`;
67
- };
68
-
69
- export const buildUniqueFieldName = (label: string, takenNames: string[]): string => {
70
- const baseName = buildFieldNameFromLabel(label);
71
- let name = baseName;
72
- let suffix = 2;
73
-
74
- while (takenNames.includes(name)) {
75
- name = `${baseName}${suffix}`;
76
- suffix++;
77
- }
78
-
79
- return name;
80
- };
81
-
82
- export const buildNewBuilderField = (fields: FormField[], createId: () => string): FormField => ({
83
- id: createId(),
84
- name: buildUniqueFieldName(`field ${fields.length + 1}`, fields.map(({ name }) => name)),
85
- label: '',
86
- type: 'TEXT',
87
- settings: {},
88
- });
89
-
90
- export const setFieldRequired = (field: FormField, isRequired: boolean): FormField => {
91
- const settings = omitSetting(field.settings, FORM_FIELD_REQUIRED_SETTING);
92
-
93
- return { ...field, settings: isRequired ? { ...settings, [FORM_FIELD_REQUIRED_SETTING]: true } : settings };
94
- };
95
-
96
- const OUTPUT_LEAF_TYPES: Record<string, string> = {
97
- NUMBER: 'number',
98
- DATE: 'string',
99
- TEXT: 'string',
100
- };
101
-
102
- // The output schema of a step whose output is these fields, in the format the
103
- // workflow editor reads for its variable picker. A record comes out as { id }.
104
- export const buildFieldsOutputSchema = (fields: FormField[]): Record<string, unknown> =>
105
- Object.fromEntries(
106
- getFillableFormFields(fields).map((field) => [
107
- field.name,
108
- field.type === 'RECORD'
109
- ? {
110
- isLeaf: false,
111
- type: 'object',
112
- label: field.label,
113
- value: { id: { isLeaf: true, type: 'string', label: 'Id', value: null } },
114
- }
115
- : {
116
- isLeaf: true,
117
- type: OUTPUT_LEAF_TYPES[field.type] ?? 'unknown',
118
- label: field.label,
119
- value: null,
120
- },
121
- ]),
122
- );
@@ -1,23 +0,0 @@
1
- // A field of a workflow FORM step and of a task form: one format for both, so
2
- // a step's fields become the task's fields as they are.
3
- export type FormFieldType =
4
- | 'TEXT'
5
- | 'NUMBER'
6
- | 'DATE'
7
- | 'SELECT'
8
- | 'MULTI_SELECT'
9
- | 'RECORD';
10
-
11
- export type FormField = {
12
- id: string;
13
- name: string;
14
- label: string;
15
- type: FormFieldType | string;
16
- placeholder?: string;
17
- value?: unknown;
18
- settings?: Record<string, unknown> | null;
19
- };
20
-
21
- export type FormFieldRole = 'ASSIGNEE' | 'DUE_AT';
22
-
23
- export type FormValues = Record<string, unknown>;
@@ -1,138 +0,0 @@
1
- import {
2
- FORM_FIELD_REQUIRED_SETTING,
3
- FORM_FIELD_ROLE_SETTING,
4
- } from 'src/shared/docflow.constants';
5
- import {
6
- type FormField,
7
- type FormFieldRole,
8
- type FormValues,
9
- } from 'src/shared/form/form-field.type';
10
-
11
- export const getFormFieldRole = (
12
- field: Pick<FormField, 'settings'>,
13
- ): FormFieldRole | undefined => {
14
- const role = field.settings?.[FORM_FIELD_ROLE_SETTING];
15
-
16
- return role === 'ASSIGNEE' || role === 'DUE_AT' ? role : undefined;
17
- };
18
-
19
- export const isFormFieldRequired = (
20
- field: Pick<FormField, 'settings'>,
21
- ): boolean => field.settings?.[FORM_FIELD_REQUIRED_SETTING] === true;
22
-
23
- // The fields a person fills: the assignee and the deadline are set by the
24
- // process.
25
- export const getFillableFormFields = <TField extends Pick<FormField, 'settings'>>(
26
- fields: TField[],
27
- ): TField[] => fields.filter((field) => getFormFieldRole(field) === undefined);
28
-
29
- // A record picker answers with { id }, a multi-select with an array.
30
- export const isFormValueEmpty = (value: unknown): boolean => {
31
- if (value === null || value === undefined) {
32
- return true;
33
- }
34
-
35
- if (typeof value === 'string') {
36
- return value.trim().length === 0;
37
- }
38
-
39
- if (Array.isArray(value)) {
40
- return value.length === 0;
41
- }
42
-
43
- if (typeof value === 'object' && 'id' in value) {
44
- const id = (value as { id: unknown }).id;
45
-
46
- return typeof id !== 'string' || id.length === 0;
47
- }
48
-
49
- return false;
50
- };
51
-
52
- export const findMissingRequiredFields = <TField extends FormField>({
53
- fields,
54
- values,
55
- }: {
56
- fields: TField[];
57
- values: FormValues;
58
- }): TField[] =>
59
- getFillableFormFields(fields).filter(
60
- (field) => isFormFieldRequired(field) && isFormValueEmpty(values[field.name]),
61
- );
62
-
63
- // A value for every field a person fills, typed or preset in the form. The
64
- // server checks required fields against exactly this object, so the page and
65
- // the server agree.
66
- export const buildFormValues = ({
67
- fields,
68
- values,
69
- }: {
70
- fields: FormField[];
71
- values: FormValues;
72
- }): FormValues =>
73
- Object.fromEntries(
74
- getFillableFormFields(fields).flatMap((field) => {
75
- const value = field.name in values ? values[field.name] : field.value;
76
-
77
- return value === undefined || value === null ? [] : [[field.name, value]];
78
- }),
79
- );
80
-
81
- // The answer of a form step carries every field, service ones included:
82
- // later steps read the assignee and the deadline from it.
83
- export const buildFormStepResponse = ({
84
- fields,
85
- values,
86
- }: {
87
- fields: Pick<FormField, 'name' | 'value'>[];
88
- values: FormValues;
89
- }): FormValues =>
90
- Object.fromEntries(
91
- fields.map((field) => [
92
- field.name,
93
- field.name in values ? values[field.name] : (field.value ?? null),
94
- ]),
95
- );
96
-
97
- const FORM_FIELD_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9]*$/;
98
-
99
- // A task form is edited by people, so it is checked before it is saved:
100
- // unique names the engine can address, known types.
101
- export const getFormFieldsError = (fields: FormField[]): string | undefined => {
102
- const names = new Set<string>();
103
-
104
- for (const field of fields) {
105
- if (!FORM_FIELD_NAME_PATTERN.test(field.name)) {
106
- return `Имя поля «${field.label}» должно начинаться с латинской буквы и состоять из букв и цифр`;
107
- }
108
-
109
- if (names.has(field.name)) {
110
- return `Поле «${field.name}» повторяется`;
111
- }
112
-
113
- if (field.label.trim().length === 0) {
114
- return 'У каждого поля должно быть название';
115
- }
116
-
117
- names.add(field.name);
118
- }
119
-
120
- return undefined;
121
- };
122
-
123
- export const parseFormFields = (value: unknown): FormField[] =>
124
- Array.isArray(value)
125
- ? value.filter(
126
- (field): field is FormField =>
127
- typeof field === 'object' &&
128
- field !== null &&
129
- typeof (field as FormField).name === 'string' &&
130
- typeof (field as FormField).label === 'string' &&
131
- typeof (field as FormField).type === 'string',
132
- )
133
- : [];
134
-
135
- export const parseFormValues = (value: unknown): FormValues =>
136
- typeof value === 'object' && value !== null && !Array.isArray(value)
137
- ? (value as FormValues)
138
- : {};
@@ -1,104 +0,0 @@
1
- import {
2
- FORM_FIELD_DUE_IN_DAYS_SETTING,
3
- FORM_FIELD_REQUIRED_SETTING,
4
- FORM_FIELD_ROLE_SETTING,
5
- } from 'src/shared/docflow.constants';
6
- import { type FormField, type FormFieldRole } from 'src/shared/form/form-field.type';
7
- import { getFormFieldRole } from 'src/shared/form/form-fields.util';
8
-
9
- export type StepSettings = {
10
- assignee: string | null;
11
- dueInDays: number | null;
12
- requiredFieldIds: string[];
13
- };
14
-
15
- const ASSIGNEE_FIELD = {
16
- name: 'smbAssignee',
17
- label: 'Исполнитель',
18
- type: 'RECORD',
19
- } as const;
20
- const DUE_AT_FIELD = { name: 'smbDueAt', label: 'Срок', type: 'DATE' } as const;
21
-
22
- export const readStepSettings = (fields: FormField[]): StepSettings => {
23
- const assignee = fields.find((field) => getFormFieldRole(field) === 'ASSIGNEE')?.value;
24
- const dueInDays = fields.find((field) => getFormFieldRole(field) === 'DUE_AT')?.settings?.[
25
- FORM_FIELD_DUE_IN_DAYS_SETTING
26
- ];
27
-
28
- return {
29
- assignee: typeof assignee === 'string' && assignee.length > 0 ? assignee : null,
30
- dueInDays: typeof dueInDays === 'number' && dueInDays > 0 ? dueInDays : null,
31
- requiredFieldIds: fields
32
- .filter(
33
- (field) =>
34
- getFormFieldRole(field) === undefined &&
35
- field.settings?.[FORM_FIELD_REQUIRED_SETTING] === true,
36
- )
37
- .map(({ id }) => id),
38
- };
39
- };
40
-
41
- const withRole = (
42
- role: FormFieldRole,
43
- existing: FormField | undefined,
44
- template: { name: string; label: string; type: string },
45
- patch: Pick<FormField, 'value' | 'settings'>,
46
- createId: () => string,
47
- ): FormField => ({
48
- ...template,
49
- ...existing,
50
- id: existing?.id ?? createId(),
51
- value: patch.value,
52
- settings: { ...existing?.settings, ...patch.settings, [FORM_FIELD_ROLE_SETTING]: role },
53
- });
54
-
55
- // The role fields stay in the step's field list, where the server reads them;
56
- // an empty assignee or deadline removes its field.
57
- export const applyStepSettings = ({
58
- fields,
59
- settings,
60
- createId,
61
- }: {
62
- fields: FormField[];
63
- settings: StepSettings;
64
- createId: () => string;
65
- }): FormField[] => {
66
- const ordinaryFields = fields
67
- .filter((field) => getFormFieldRole(field) === undefined)
68
- .map((field) => {
69
- const { [FORM_FIELD_REQUIRED_SETTING]: _removed, ...rest } = field.settings ?? {};
70
- const isRequired = settings.requiredFieldIds.includes(field.id);
71
-
72
- return {
73
- ...field,
74
- settings: isRequired ? { ...rest, [FORM_FIELD_REQUIRED_SETTING]: true } : rest,
75
- };
76
- });
77
- const roleFields: FormField[] = [];
78
-
79
- if (settings.assignee !== null) {
80
- roleFields.push(
81
- withRole(
82
- 'ASSIGNEE',
83
- fields.find((field) => getFormFieldRole(field) === 'ASSIGNEE'),
84
- ASSIGNEE_FIELD,
85
- { value: settings.assignee, settings: {} },
86
- createId,
87
- ),
88
- );
89
- }
90
-
91
- if (settings.dueInDays !== null) {
92
- roleFields.push(
93
- withRole(
94
- 'DUE_AT',
95
- fields.find((field) => getFormFieldRole(field) === 'DUE_AT'),
96
- DUE_AT_FIELD,
97
- { value: undefined, settings: { [FORM_FIELD_DUE_IN_DAYS_SETTING]: settings.dueInDays } },
98
- createId,
99
- ),
100
- );
101
- }
102
-
103
- return [...roleFields, ...ordinaryFields];
104
- };
@@ -1,56 +0,0 @@
1
- import {
2
- DOCFLOW_LAUNCH_PARAMETERS_KEY,
3
- LAUNCH_PARAMETERS_STEP_INPUT,
4
- } from 'src/shared/docflow.constants';
5
- import { type FormField, type FormValues } from 'src/shared/form/form-field.type';
6
- import { getFillableFormFields, parseFormFields } from 'src/shared/form/form-fields.util';
7
- import { type WorkflowStep } from 'src/shared/workflow/workflow-run.type';
8
-
9
- const isPlainRecord = (value: unknown): value is Record<string, unknown> =>
10
- typeof value === 'object' && value !== null && !Array.isArray(value);
11
-
12
- // The "Launch with parameters" step is the app's workflow action whose input
13
- // takes the parameters of the run; the fork finds it by the same input.
14
- export const isLaunchParametersStep = (step: WorkflowStep): boolean => {
15
- const input = step.settings?.input;
16
-
17
- if (step.type !== 'LOGIC_FUNCTION' || !isPlainRecord(input)) {
18
- return false;
19
- }
20
-
21
- return (
22
- isPlainRecord(input.logicFunctionInput) &&
23
- input.logicFunctionInput.parameters === LAUNCH_PARAMETERS_STEP_INPUT
24
- );
25
- };
26
-
27
- export const findLaunchParametersStep = (
28
- steps: WorkflowStep[] | null | undefined,
29
- ): WorkflowStep | undefined => (steps ?? []).find(isLaunchParametersStep);
30
-
31
- export const parseLaunchFields = (value: unknown): FormField[] =>
32
- getFillableFormFields(parseFormFields(value));
33
-
34
- // Every parameter goes into the run, an empty one as null: a later step that
35
- // reads {{launch.contract}} gets an empty value, never the text "undefined".
36
- export const buildLaunchParameters = ({
37
- fields,
38
- values,
39
- }: {
40
- fields: FormField[];
41
- values: FormValues;
42
- }): FormValues =>
43
- Object.fromEntries(
44
- fields.map((field) => {
45
- const value = field.name in values ? values[field.name] : field.value;
46
-
47
- return [field.name, value === undefined || value === '' ? null : value];
48
- }),
49
- );
50
-
51
- export const buildLaunchPayload = (parameters: FormValues): Record<string, unknown> => ({
52
- [DOCFLOW_LAUNCH_PARAMETERS_KEY]: parameters,
53
- });
54
-
55
- export const readLaunchParameters = (value: unknown): FormValues =>
56
- isPlainRecord(value) ? value : {};
@@ -1,70 +0,0 @@
1
- import { TRIGGER_STEP_ID } from 'src/shared/docflow.constants';
2
- import { type WorkflowRunState } from 'src/shared/workflow/workflow-run.type';
3
-
4
- const UUID_PATTERN =
5
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6
-
7
- // Task targets Twenty creates for every installation; other objects have
8
- // their own target fields only when they are set up for tasks.
9
- const TARGET_FIELD_NAME_BY_OBJECT_NAME: Record<string, string> = {
10
- person: 'targetPersonId',
11
- company: 'targetCompanyId',
12
- opportunity: 'targetOpportunityId',
13
- };
14
-
15
- const readRecord = (value: unknown): Record<string, unknown> | undefined =>
16
- typeof value === 'object' && value !== null && !Array.isArray(value)
17
- ? (value as Record<string, unknown>)
18
- : undefined;
19
-
20
- const readUuid = (value: unknown): string | undefined =>
21
- typeof value === 'string' && UUID_PATTERN.test(value) ? value : undefined;
22
-
23
- // A run started by an event on a deal, or launched from a deal's page, gives
24
- // its tasks to that deal, so the task shows in the deal's card.
25
- export const getRunTargetRecord = (
26
- state: WorkflowRunState | null | undefined,
27
- ): { targetFieldName: string; recordId: string } | undefined => {
28
- const trigger = state?.flow?.trigger;
29
- const payload = readRecord(state?.stepInfos?.[TRIGGER_STEP_ID]?.result);
30
-
31
- if (trigger === undefined || trigger === null || payload === undefined) {
32
- return undefined;
33
- }
34
-
35
- const settings = readRecord(trigger.settings) ?? {};
36
- let objectNameSingular: string | undefined;
37
- let recordId: string | undefined;
38
-
39
- if (trigger.type === 'DATABASE_EVENT') {
40
- const eventName = settings.eventName;
41
-
42
- objectNameSingular =
43
- typeof eventName === 'string' ? eventName.split('.')[0] : undefined;
44
- recordId =
45
- readUuid(payload.recordId) ??
46
- readUuid(readRecord(readRecord(payload.properties)?.after)?.id);
47
- }
48
-
49
- if (trigger.type === 'MANUAL') {
50
- const availability = readRecord(settings.availability);
51
-
52
- objectNameSingular =
53
- availability?.type === 'SINGLE_RECORD' &&
54
- typeof availability.objectNameSingular === 'string'
55
- ? availability.objectNameSingular
56
- : typeof settings.objectType === 'string'
57
- ? settings.objectType
58
- : undefined;
59
- recordId = readUuid(readRecord(payload.record)?.id);
60
- }
61
-
62
- const targetFieldName =
63
- objectNameSingular !== undefined
64
- ? TARGET_FIELD_NAME_BY_OBJECT_NAME[objectNameSingular]
65
- : undefined;
66
-
67
- return targetFieldName !== undefined && recordId !== undefined
68
- ? { targetFieldName, recordId }
69
- : undefined;
70
- };
@@ -1,35 +0,0 @@
1
- import { type FormField } from 'src/shared/form/form-field.type';
2
-
3
- // The part of a workflow run's state docflow reads. The engine keeps a copy
4
- // of the version in the run, so a run is read as it was when it started.
5
- export type WorkflowStepInfo = {
6
- status?: string;
7
- threadId?: string;
8
- result?: unknown;
9
- error?: unknown;
10
- };
11
-
12
- export type WorkflowStep = {
13
- id: string;
14
- name: string;
15
- type: string;
16
- settings?: { input?: unknown; outputSchema?: unknown } & Record<string, unknown>;
17
- nextStepIds?: string[] | null;
18
- };
19
-
20
- export type WorkflowTrigger = {
21
- type: string;
22
- name?: string | null;
23
- nextStepIds?: string[] | null;
24
- settings?: Record<string, unknown> | null;
25
- };
26
-
27
- export type WorkflowRunState = {
28
- flow?: { trigger?: WorkflowTrigger | null; steps?: WorkflowStep[] | null };
29
- stepInfos?: Record<string, WorkflowStepInfo | undefined>;
30
- };
31
-
32
- export type WorkflowFormStep = WorkflowStep & {
33
- type: 'FORM';
34
- settings: { input: FormField[] };
35
- };