@haiiro/docuflow 0.0.12 → 0.0.15

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 (40) hide show
  1. package/README.md +41 -9
  2. package/bin/install.mjs +69 -3
  3. package/manifest.json +114 -18
  4. package/package.json +6 -3
  5. package/public/app/docflow-app.css +2 -0
  6. package/public/app/docflow-app.js +95 -0
  7. package/src/front-components/docflow-documents.front-component.mjs +41 -41
  8. package/src/front-components/docflow-documents.front-component.tsx +49 -32
  9. package/src/front-components/docflow-processes.front-component.mjs +34 -34
  10. package/src/front-components/docflow-processes.front-component.tsx +24 -11
  11. package/src/front-components/docflow-tasks.front-component.mjs +40 -39
  12. package/src/front-components/docflow-tasks.front-component.tsx +19 -14
  13. package/src/front-components/launch-process.front-component.mjs +75 -0
  14. package/src/front-components/launch-process.front-component.tsx +270 -0
  15. package/src/front-components/process-steps.front-component.mjs +26 -26
  16. package/src/front-components/process-steps.front-component.tsx +56 -10
  17. package/src/front-components/task-form.front-component.mjs +45 -44
  18. package/src/front-components/task-form.front-component.tsx +12 -1
  19. package/src/logic-functions/bitrix-app.logic-function.mjs +186 -88
  20. package/src/logic-functions/bitrix-app.logic-function.ts +24 -61
  21. package/src/logic-functions/bitrix-event.logic-function.mjs +22 -1
  22. package/src/logic-functions/bitrix-event.logic-function.ts +2 -2
  23. package/src/logic-functions/bitrix-install.logic-function.mjs +26 -12
  24. package/src/logic-functions/bitrix-install.logic-function.ts +2 -5
  25. package/src/logic-functions/complete-task.logic-function.mjs +22 -1
  26. package/src/logic-functions/complete-task.logic-function.ts +2 -2
  27. package/src/logic-functions/docflow-app-session.logic-function.mjs +60064 -0
  28. package/src/logic-functions/docflow-app-session.logic-function.ts +54 -0
  29. package/src/logic-functions/docflow-app.logic-function.mjs +60149 -0
  30. package/src/logic-functions/docflow-app.logic-function.ts +38 -0
  31. package/src/logic-functions/launch-process.logic-function.mjs +134 -40
  32. package/src/logic-functions/launch-process.logic-function.ts +85 -24
  33. package/src/logic-functions/publish-process.logic-function.mjs +32 -11
  34. package/src/logic-functions/publish-process.logic-function.ts +8 -4
  35. package/src/logic-functions/save-launch-fields.logic-function.mjs +194 -17
  36. package/src/logic-functions/save-launch-fields.logic-function.ts +26 -8
  37. package/src/logic-functions/save-step-settings.logic-function.mjs +24 -2
  38. package/src/logic-functions/save-step-settings.logic-function.ts +2 -2
  39. package/src/logic-functions/save-task-form.logic-function.mjs +22 -1
  40. package/src/logic-functions/save-task-form.logic-function.ts +2 -2
@@ -14,7 +14,18 @@ const TaskForm = () => {
14
14
 
15
15
  return (
16
16
  <DocflowRoot>
17
- <div style={{ padding: theme.spacing[3] }}>
17
+ {/* The panel fills the tab so the task's actions sit at its bottom */}
18
+ <div
19
+ style={{
20
+ boxSizing: 'border-box',
21
+ display: 'flex',
22
+ flex: 1,
23
+ flexDirection: 'column',
24
+ minHeight: 0,
25
+ overflowY: 'auto',
26
+ padding: theme.spacing[3],
27
+ }}
28
+ >
18
29
  {taskId === undefined ? <Message>Откройте задачу.</Message> : <TaskWorkPanel taskId={taskId} />}
19
30
  </div>
20
31
  </DocflowRoot>
@@ -1673,42 +1673,6 @@ var __anyHandler = {
1673
1673
  var __anyStub = new Proxy(() => void 0, __anyHandler);
1674
1674
  var defineLogicFunction = __defineFactoryStub;
1675
1675
 
1676
- // src/shared/api/rest-filter.util.ts
1677
- var UNSAFE_FILTER_CHARACTERS = /[()[\]'",]/g;
1678
- var sanitizeFilterText = (text) => text.replace(UNSAFE_FILTER_CHARACTERS, " ").trim();
1679
- var quote = (value) => `'${sanitizeFilterText(value)}'`;
1680
- var restEq = (field, value) => `${field}[eq]:${quote(value)}`;
1681
- var restNeq = (field, value) => `${field}[neq]:${quote(value)}`;
1682
- var restIsNull = (field) => `${field}[is]:NULL`;
1683
- var restAnd = (...conditions) => {
1684
- const present = conditions.filter(
1685
- (condition) => condition !== void 0
1686
- );
1687
- return present.length === 0 ? void 0 : `and(${present.join(",")})`;
1688
- };
1689
- var restOr = (...conditions) => `or(${conditions.join(",")})`;
1690
-
1691
- // src/shared/api/rest-response.util.ts
1692
- var readDataValue = (response) => {
1693
- const data = response?.data;
1694
- if (typeof data !== "object" || data === null) {
1695
- return void 0;
1696
- }
1697
- return Object.values(data)[0];
1698
- };
1699
- var readRestList = (response) => {
1700
- const value = readDataValue(response);
1701
- return {
1702
- records: Array.isArray(value) ? value : [],
1703
- pageInfo: response?.pageInfo ?? {},
1704
- totalCount: response?.totalCount
1705
- };
1706
- };
1707
- var readRestRecord = (response) => {
1708
- const value = readDataValue(response);
1709
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1710
- };
1711
-
1712
1676
  // src/shared/bitrix/bitrix.util.ts
1713
1677
  var BITRIX_OAUTH_TOKEN_URL = "https://oauth.bitrix.info/oauth/token/";
1714
1678
  var BITRIX_PORTAL_KV_KEY = "bitrix:portal";
@@ -1844,15 +1808,42 @@ var callBitrix = async ({
1844
1808
  return body.result;
1845
1809
  };
1846
1810
 
1847
- // src/shared/docflow.constants.ts
1848
- var DOCFLOW_SECTION = "DOCFLOW";
1849
- var DOCFLOW_LAUNCH_PARAMETERS_KEY = "smbDocflowParameters";
1850
- var LAUNCH_PARAMETERS_STEP_INPUT = `{{trigger.${DOCFLOW_LAUNCH_PARAMETERS_KEY}}}`;
1851
- var TASK_DONE_STATUS = "DONE";
1811
+ // src/shared/app/docflow-app.util.ts
1812
+ var DOCFLOW_APP_SECTIONS = ["tasks", "processes", "documents"];
1813
+ var readDocflowAppSection = (value) => DOCFLOW_APP_SECTIONS.find((section) => section === value) ?? "tasks";
1814
+ var SESSION_RENEW_BEFORE_MS = 10 * 60 * 1e3;
1815
+ var SESSION_EXPIRY_MARGIN_MS = 60 * 1e3;
1852
1816
 
1853
1817
  // src/server/bitrix-member.ts
1854
1818
  import { randomBytes } from "node:crypto";
1855
1819
 
1820
+ // src/shared/api/rest-filter.util.ts
1821
+ var UNSAFE_FILTER_CHARACTERS = /[()[\]'",]/g;
1822
+ var sanitizeFilterText = (text) => text.replace(UNSAFE_FILTER_CHARACTERS, " ").trim();
1823
+ var quote = (value) => `'${sanitizeFilterText(value)}'`;
1824
+ var restEq = (field, value) => `${field}[eq]:${quote(value)}`;
1825
+
1826
+ // src/shared/api/rest-response.util.ts
1827
+ var readDataValue = (response) => {
1828
+ const data = response?.data;
1829
+ if (typeof data !== "object" || data === null) {
1830
+ return void 0;
1831
+ }
1832
+ return Object.values(data)[0];
1833
+ };
1834
+ var readRestList = (response) => {
1835
+ const value = readDataValue(response);
1836
+ return {
1837
+ records: Array.isArray(value) ? value : [],
1838
+ pageInfo: response?.pageInfo ?? {},
1839
+ totalCount: response?.totalCount
1840
+ };
1841
+ };
1842
+ var readRestRecord = (response) => {
1843
+ const value = readDataValue(response);
1844
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1845
+ };
1846
+
1856
1847
  // src/shared/bitrix/bitrix-user.util.ts
1857
1848
  var readText = (value) => typeof value === "string" ? value.trim() : "";
1858
1849
  var readBitrixUserEmail = (user) => {
@@ -61229,6 +61220,10 @@ var u2 = class {
61229
61220
  }
61230
61221
  };
61231
61222
 
61223
+ // src/shared/docflow.constants.ts
61224
+ var DOCFLOW_LAUNCH_PARAMETERS_KEY = "smbDocflowParameters";
61225
+ var LAUNCH_PARAMETERS_STEP_INPUT = `{{trigger.${DOCFLOW_LAUNCH_PARAMETERS_KEY}}}`;
61226
+
61232
61227
  // src/server/records.ts
61233
61228
  var clients = {
61234
61229
  person: new u2(),
@@ -119722,7 +119717,6 @@ var renderBitrixPage = ({
119722
119717
  title,
119723
119718
  message,
119724
119719
  crmUrl,
119725
- tasks,
119726
119720
  isInstall
119727
119721
  }) => `<!doctype html>
119728
119722
  <html lang="ru">
@@ -119736,56 +119730,165 @@ var renderBitrixPage = ({
119736
119730
  h1 { font-size: 20px; margin: 0 0 8px; }
119737
119731
  p { color: #666; margin: 0 0 16px; }
119738
119732
  a.button { display: inline-block; padding: 8px 16px; border-radius: 8px; background: #1961ed; color: #fff; text-decoration: none; }
119739
- ul { list-style: none; padding: 0; margin: 16px 0 0; }
119740
- li { padding: 8px 0; border-top: 1px solid #eee; display: flex; justify-content: space-between; gap: 16px; }
119741
- li a { color: #1961ed; text-decoration: none; }
119742
- .due { color: #999; white-space: nowrap; }
119743
119733
  </style>
119744
119734
  </head>
119745
119735
  <body>
119746
119736
  <h1>${escapeHtml(title)}</h1>
119747
119737
  <p>${escapeHtml(message)}</p>
119748
119738
  ${crmUrl !== void 0 ? `<a class="button" href="${escapeHtml(crmUrl)}" target="_blank" rel="noopener">\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442</a>` : ""}
119749
- ${tasks.length > 0 ? `<ul>${tasks.map(
119750
- (task) => `<li>${task.url !== void 0 ? `<a href="${escapeHtml(task.url)}" target="_blank" rel="noopener">${escapeHtml(task.title)}</a>` : escapeHtml(task.title)}<span class="due">${escapeHtml(task.dueAt ?? "")}</span></li>`
119751
- ).join("")}</ul>` : ""}
119752
119739
  ${isInstall ? "<script>if (window.BX24) { BX24.init(function () { BX24.installFinish(); }); }</script>" : ""}
119753
119740
  </body>
119754
119741
  </html>`;
119755
119742
 
119743
+ // src/server/crm-url.ts
119744
+ var getCrmUrl = () => {
119745
+ const value = process.env.CRM_PUBLIC_URL?.trim();
119746
+ return value !== void 0 && /^https?:\/\//.test(value) ? value.replace(/\/$/, "") : void 0;
119747
+ };
119748
+
119749
+ // package.json
119750
+ var package_default = {
119751
+ name: "@haiiro/docuflow",
119752
+ version: "0.0.15",
119753
+ description: "NorDiv \u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442: \u0437\u0430\u0434\u0430\u043D\u0438\u044F \u0441 \u043F\u043E\u043B\u044F\u043C\u0438, \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u044B \u0441 \u0437\u0430\u043F\u0443\u0441\u043A\u043E\u043C \u043F\u043E \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u0430\u043C, \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u0441\u043E \u0432\u0441\u0435\u0445 \u0437\u0430\u043F\u0438\u0441\u0435\u0439, \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0411\u0438\u0442\u0440\u0438\u043A\u044124",
119754
+ license: "AGPL-3.0-only",
119755
+ publishConfig: {
119756
+ access: "public",
119757
+ registry: "https://registry.npmjs.org/"
119758
+ },
119759
+ bin: {
119760
+ "nordiv-docuflow": "bin/install.mjs"
119761
+ },
119762
+ files: [
119763
+ "bin/**/*",
119764
+ "manifest.json",
119765
+ "front-component-shared-dependencies.mjs",
119766
+ "yarn.lock",
119767
+ "src/**/*",
119768
+ "public/**/*",
119769
+ "README.md",
119770
+ "tsconfig.json",
119771
+ "tsconfig.spec.json",
119772
+ "vitest.unit.config.ts",
119773
+ ".oxlintrc.json",
119774
+ ".nvmrc",
119775
+ ".yarnrc.yml",
119776
+ "!**/*.map",
119777
+ "!**/__tests__/**"
119778
+ ],
119779
+ engines: {
119780
+ node: "^24.5.0",
119781
+ npm: "please-use-yarn",
119782
+ yarn: ">=4.0.2"
119783
+ },
119784
+ keywords: [
119785
+ "twenty-app"
119786
+ ],
119787
+ packageManager: "yarn@4.13.0",
119788
+ scripts: {
119789
+ twenty: "twenty",
119790
+ app: "twenty",
119791
+ build: "yarn build:app && twenty dev:build",
119792
+ lint: "oxlint -c .oxlintrc.json .",
119793
+ "lint:fix": "oxlint --fix -c .oxlintrc.json .",
119794
+ typecheck: "tsc --noEmit -p tsconfig.spec.json",
119795
+ test: "vitest run --config vitest.unit.config.ts",
119796
+ "build:app": "vite build --config vite.app.config.ts"
119797
+ },
119798
+ devDependencies: {
119799
+ "@types/node": "^24.7.2",
119800
+ "@types/react": "^19.0.0",
119801
+ "@types/react-dom": "^19.0.0",
119802
+ oxlint: "^0.16.0",
119803
+ react: "^19.0.0",
119804
+ "react-dom": "^19.0.0",
119805
+ "twenty-client-sdk": "2.45.0",
119806
+ "twenty-sdk": "2.45.0",
119807
+ "twenty-ui": "2.45.0",
119808
+ typescript: "^5.9.3",
119809
+ vite: "^8.0.0",
119810
+ "vite-tsconfig-paths": "^4.2.1",
119811
+ vitest: "^4.0.0"
119812
+ },
119813
+ frontComponentSharedDependencies: [
119814
+ "react",
119815
+ "react-dom/client",
119816
+ "twenty-ui/primitives/input",
119817
+ "twenty-ui/primitives/data-display",
119818
+ "twenty-ui/theme",
119819
+ "twenty-ui/icon"
119820
+ ]
119821
+ };
119822
+
119823
+ // src/server/docflow-app-page.ts
119824
+ var BX24_SDK_URL2 = "https://api.bitrix24.com/api/v1/";
119825
+ var buildAssetUrl = (workspaceId, file) => `/public-assets/${workspaceId}/${process.env.APPLICATION_ID ?? ""}/public/app/${file}?v=${encodeURIComponent(package_default.version)}`;
119826
+ var serializeConfig = (config) => JSON.stringify(config).replace(/</g, "\\u003c");
119827
+ var renderDocflowAppPage = ({
119828
+ workspaceId,
119829
+ config
119830
+ }) => new AL(
119831
+ `<!doctype html>
119832
+ <html lang="ru" class="dark">
119833
+ <head>
119834
+ <meta charset="utf-8">
119835
+ <meta name="viewport" content="width=device-width, initial-scale=1">
119836
+ <title>\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442</title>
119837
+ <link rel="stylesheet" href="${escapeHtml(buildAssetUrl(workspaceId, "docflow-app.css"))}">
119838
+ <style>html, body, #root { height: 100%; margin: 0; } body { font-family: Inter, -apple-system, "Segoe UI", Roboto, sans-serif; }</style>
119839
+ ${config.mode === "bitrix" ? `<script src="${BX24_SDK_URL2}"></script>` : ""}
119840
+ <script id="docflow-app-config" type="application/json">${serializeConfig(config)}</script>
119841
+ </head>
119842
+ <body>
119843
+ <div id="root"></div>
119844
+ <script src="${escapeHtml(buildAssetUrl(workspaceId, "docflow-app.js"))}"></script>
119845
+ </body>
119846
+ </html>`,
119847
+ {
119848
+ status: 200,
119849
+ headers: {
119850
+ "content-type": "text/html; charset=utf-8",
119851
+ "cache-control": "no-store"
119852
+ }
119853
+ }
119854
+ );
119855
+
119856
+ // src/shared/log.util.ts
119857
+ var PREFIX = "[docflow]";
119858
+ var describeError = (error) => {
119859
+ if (error instanceof Error) {
119860
+ const { status, body } = error;
119861
+ return { name: error.name, message: error.message, status, body };
119862
+ }
119863
+ return error;
119864
+ };
119865
+ var logDocflowError = (event, error, details) => console.error(PREFIX, event, describeError(error), details ?? "");
119866
+
119756
119867
  // src/server/http-response.ts
119868
+ var jsonResponse = (status, body) => new AL(JSON.stringify(body), {
119869
+ status,
119870
+ headers: { "content-type": "application/json; charset=utf-8" }
119871
+ });
119757
119872
  var htmlResponse = (status, html) => new AL(html, {
119758
119873
  status,
119759
119874
  headers: { "content-type": "text/html; charset=utf-8" }
119760
119875
  });
119876
+ var withRouteErrors = (name, handler2) => async (...args) => {
119877
+ try {
119878
+ return await handler2(...args);
119879
+ } catch (error) {
119880
+ logDocflowError(`${name}: unexpected error`, error);
119881
+ return jsonResponse(500, {
119882
+ message: `\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442\u0430: ${error instanceof Error ? error.message : String(error)}`
119883
+ });
119884
+ }
119885
+ };
119761
119886
 
119762
119887
  // src/universal-identifiers.ts
119763
119888
  var BITRIX_APP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER = "00fa5cea-d57b-4215-980a-c4bed9232a4f";
119764
119889
 
119765
119890
  // src/logic-functions/bitrix-app.logic-function.ts
119766
- var TASKS_SHOWN_IN_PORTAL = 20;
119767
- var getCrmUrl = () => {
119768
- const value = process.env.CRM_PUBLIC_URL?.trim();
119769
- return value !== void 0 && /^https?:\/\//.test(value) ? value.replace(/\/$/, "") : void 0;
119770
- };
119771
- var listOpenTasksForMember = async (memberId, crmUrl) => readRestList(
119772
- await restClient("application").get("/rest/tasks", {
119773
- query: {
119774
- filter: restAnd(
119775
- restEq("smbSection", DOCFLOW_SECTION),
119776
- restEq("assigneeId", memberId),
119777
- restOr(restNeq("status", TASK_DONE_STATUS), restIsNull("status"))
119778
- ),
119779
- order_by: "dueAt[AscNullsLast]",
119780
- limit: TASKS_SHOWN_IN_PORTAL
119781
- }
119782
- })
119783
- ).records.map((task) => ({
119784
- title: task.title?.trim() || "\u0411\u0435\u0437 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u044F",
119785
- url: crmUrl !== void 0 ? `${crmUrl}/object/task/${task.id}` : void 0,
119786
- dueAt: typeof task.dueAt === "string" ? new Date(task.dueAt).toLocaleDateString("ru-RU") : void 0
119787
- }));
119788
- var handler = async (event) => {
119891
+ var handler = async (event, context) => {
119789
119892
  const crmUrl = getCrmUrl();
119790
119893
  if (!isBitrixConfigured()) {
119791
119894
  return htmlResponse(
@@ -119794,7 +119897,6 @@ var handler = async (event) => {
119794
119897
  title: "\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442",
119795
119898
  message: "\u0412 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0432 CRM \u043D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u044B BITRIX_CLIENT_ID \u0438 BITRIX_CLIENT_SECRET.",
119796
119899
  crmUrl,
119797
- tasks: [],
119798
119900
  isInstall: false
119799
119901
  })
119800
119902
  );
@@ -119820,22 +119922,20 @@ var handler = async (event) => {
119820
119922
  title: "\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442",
119821
119923
  message: "\u0412 \u043F\u0440\u043E\u0444\u0438\u043B\u0435 \u0411\u0438\u0442\u0440\u0438\u043A\u044124 \u043D\u0435\u0442 \u043F\u043E\u0447\u0442\u044B \u2014 \u0431\u0435\u0437 \u043D\u0435\u0451 \u043D\u0435\u043B\u044C\u0437\u044F \u043D\u0430\u0439\u0442\u0438 \u0438\u043B\u0438 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u0441\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A\u0430 \u0432 CRM.",
119822
119924
  crmUrl,
119823
- tasks: [],
119824
119925
  isInstall: false
119825
119926
  })
119826
119927
  );
119827
119928
  }
119828
- const tasks = await listOpenTasksForMember(member.id, crmUrl);
119829
- return htmlResponse(
119830
- 200,
119831
- renderBitrixPage({
119832
- title: "\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442",
119833
- message: tasks.length === 0 ? "\u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0445 \u0437\u0430\u0434\u0430\u043D\u0438\u0439 \u043D\u0435\u0442." : "\u0412\u0430\u0448\u0438 \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u0437\u0430\u0434\u0430\u043D\u0438\u044F:",
119834
- crmUrl,
119835
- tasks,
119836
- isInstall: false
119837
- })
119838
- );
119929
+ return renderDocflowAppPage({
119930
+ workspaceId: context.workspaceId,
119931
+ config: {
119932
+ mode: "bitrix",
119933
+ crmUrl: crmUrl ?? "",
119934
+ section: readDocflowAppSection(event.queryStringParameters.section),
119935
+ expectedWorkspaceMemberId: member.id,
119936
+ portalDomain: portal.portalDomain
119937
+ }
119938
+ });
119839
119939
  } catch (error) {
119840
119940
  if (error instanceof BitrixError) {
119841
119941
  return htmlResponse(
@@ -119844,7 +119944,6 @@ var handler = async (event) => {
119844
119944
  title: "\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442",
119845
119945
  message: `\u0417\u0430\u043F\u0440\u043E\u0441 \u043F\u043E\u0440\u0442\u0430\u043B\u0430 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0451\u043D: ${error.message}`,
119846
119946
  crmUrl,
119847
- tasks: [],
119848
119947
  isInstall: false
119849
119948
  })
119850
119949
  );
@@ -119855,7 +119954,6 @@ var handler = async (event) => {
119855
119954
  title: "\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442",
119856
119955
  message: error instanceof Error ? error.message : "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442 \u0434\u043B\u044F \u0432\u0430\u0448\u0435\u0433\u043E \u043F\u0440\u043E\u0444\u0438\u043B\u044F \u0411\u0438\u0442\u0440\u0438\u043A\u044124.",
119857
119956
  crmUrl,
119858
- tasks: [],
119859
119957
  isInstall: false
119860
119958
  })
119861
119959
  );
@@ -119866,7 +119964,7 @@ var bitrix_app_logic_function_default = defineLogicFunction({
119866
119964
  name: "docflow-bitrix-app",
119867
119965
  description: "\u0421\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442\u0430 \u0432 \u043F\u043E\u0440\u0442\u0430\u043B\u0435 \u0411\u0438\u0442\u0440\u0438\u043A\u044124",
119868
119966
  timeoutSeconds: 30,
119869
- handler,
119967
+ handler: withRouteErrors("bitrix-app", handler),
119870
119968
  httpRouteTriggerSettings: {
119871
119969
  path: "/bitrix/app",
119872
119970
  httpMethod: "POST",
@@ -1,57 +1,26 @@
1
1
  import { defineLogicFunction } from 'twenty-sdk/define';
2
- import { type RoutePayload } from 'twenty-sdk/logic-function';
2
+ import {
3
+ type LogicFunctionExecutionContext,
4
+ type RoutePayload,
5
+ } from 'twenty-sdk/logic-function';
3
6
 
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
7
  import { BitrixError, callBitrix, isBitrixConfigured } from 'src/shared/bitrix/bitrix-client';
7
8
  import { type BitrixUserProfile } from 'src/shared/bitrix/bitrix-user.util';
8
9
  import { parseBitrixRequest } from 'src/shared/bitrix/bitrix.util';
9
- import { DOCFLOW_SECTION, TASK_DONE_STATUS } from 'src/shared/docflow.constants';
10
+ import { readDocflowAppSection } from 'src/shared/app/docflow-app.util';
10
11
  import { findOrCreateWorkspaceMemberFromBitrix } from 'src/server/bitrix-member';
11
12
  import { proveAndLinkBitrixPortal } from 'src/server/bitrix-portal';
12
- import { renderBitrixPage, type BitrixPageTask } from 'src/server/bitrix-page';
13
- import { htmlResponse } from 'src/server/http-response';
14
- import { restClient } from 'src/server/records';
13
+ import { renderBitrixPage } from 'src/server/bitrix-page';
14
+ import { getCrmUrl } from 'src/server/crm-url';
15
+ import { renderDocflowAppPage } from 'src/server/docflow-app-page';
16
+ import { htmlResponse, withRouteErrors } from 'src/server/http-response';
15
17
  import { BITRIX_APP_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
16
18
 
17
- const TASKS_SHOWN_IN_PORTAL = 20;
18
-
19
- const getCrmUrl = (): string | undefined => {
20
- const value = process.env.CRM_PUBLIC_URL?.trim();
21
-
22
- return value !== undefined && /^https?:\/\//.test(value)
23
- ? value.replace(/\/$/, '')
24
- : undefined;
25
- };
26
-
27
- const listOpenTasksForMember = async (
28
- memberId: string,
29
- crmUrl: string | undefined,
30
- ): Promise<BitrixPageTask[]> =>
31
- readRestList<{ id: string; title?: string | null; dueAt?: string | null }>(
32
- await restClient('application').get('/rest/tasks', {
33
- query: {
34
- filter: restAnd(
35
- restEq('smbSection', DOCFLOW_SECTION),
36
- restEq('assigneeId', memberId),
37
- restOr(restNeq('status', TASK_DONE_STATUS), restIsNull('status')),
38
- ),
39
- order_by: 'dueAt[AscNullsLast]',
40
- limit: TASKS_SHOWN_IN_PORTAL,
41
- },
42
- }),
43
- ).records.map((task) => ({
44
- title: task.title?.trim() || 'Без названия',
45
- url: crmUrl !== undefined ? `${crmUrl}/object/task/${task.id}` : undefined,
46
- dueAt:
47
- typeof task.dueAt === 'string'
48
- ? new Date(task.dueAt).toLocaleDateString('ru-RU')
49
- : undefined,
50
- }));
51
-
52
19
  // "Путь вашего обработчика" of the local Bitrix24 application: the page a
53
- // person sees when they open the app in the portal.
54
- const handler = async (event: RoutePayload) => {
20
+ // person sees when they open the app in the portal. After the portal and the
21
+ // person are checked it is the standalone docflow page; it signs in to the
22
+ // CRM itself (the portal frame gets no CRM cookie).
23
+ const handler = async (event: RoutePayload, context: LogicFunctionExecutionContext) => {
55
24
  const crmUrl = getCrmUrl();
56
25
 
57
26
  if (!isBitrixConfigured()) {
@@ -61,7 +30,6 @@ const handler = async (event: RoutePayload) => {
61
30
  title: 'Документооборот',
62
31
  message: 'В настройках приложения в CRM не указаны BITRIX_CLIENT_ID и BITRIX_CLIENT_SECRET.',
63
32
  crmUrl,
64
- tasks: [],
65
33
  isInstall: false,
66
34
  }),
67
35
  );
@@ -90,24 +58,21 @@ const handler = async (event: RoutePayload) => {
90
58
  title: 'Документооборот',
91
59
  message: 'В профиле Битрикс24 нет почты — без неё нельзя найти или создать сотрудника в CRM.',
92
60
  crmUrl,
93
- tasks: [],
94
61
  isInstall: false,
95
62
  }),
96
63
  );
97
64
  }
98
65
 
99
- const tasks = await listOpenTasksForMember(member.id, crmUrl);
100
-
101
- return htmlResponse(
102
- 200,
103
- renderBitrixPage({
104
- title: 'Документооборот',
105
- message: tasks.length === 0 ? 'Открытых заданий нет.' : 'Ваши открытые задания:',
106
- crmUrl,
107
- tasks,
108
- isInstall: false,
109
- }),
110
- );
66
+ return renderDocflowAppPage({
67
+ workspaceId: context.workspaceId,
68
+ config: {
69
+ mode: 'bitrix',
70
+ crmUrl: crmUrl ?? '',
71
+ section: readDocflowAppSection(event.queryStringParameters.section),
72
+ expectedWorkspaceMemberId: member.id,
73
+ portalDomain: portal.portalDomain,
74
+ },
75
+ });
111
76
  } catch (error) {
112
77
  if (error instanceof BitrixError) {
113
78
  return htmlResponse(
@@ -116,7 +81,6 @@ const handler = async (event: RoutePayload) => {
116
81
  title: 'Документооборот',
117
82
  message: `Запрос портала не подтверждён: ${error.message}`,
118
83
  crmUrl,
119
- tasks: [],
120
84
  isInstall: false,
121
85
  }),
122
86
  );
@@ -131,7 +95,6 @@ const handler = async (event: RoutePayload) => {
131
95
  ? error.message
132
96
  : 'Не удалось открыть документооборот для вашего профиля Битрикс24.',
133
97
  crmUrl,
134
- tasks: [],
135
98
  isInstall: false,
136
99
  }),
137
100
  );
@@ -143,7 +106,7 @@ export default defineLogicFunction({
143
106
  name: 'docflow-bitrix-app',
144
107
  description: 'Страница документооборота в портале Битрикс24',
145
108
  timeoutSeconds: 30,
146
- handler,
109
+ handler: withRouteErrors('bitrix-app', handler),
147
110
  httpRouteTriggerSettings: {
148
111
  path: '/bitrix/app',
149
112
  httpMethod: 'POST',
@@ -60049,11 +60049,32 @@ var loadBitrixPortal = () => kL.get(BITRIX_PORTAL_KV_KEY);
60049
60049
  var saveBitrixPortal = (portal) => kL.set(BITRIX_PORTAL_KV_KEY, portal);
60050
60050
  var forgetBitrixPortal = () => kL.delete(BITRIX_PORTAL_KV_KEY);
60051
60051
 
60052
+ // src/shared/log.util.ts
60053
+ var PREFIX = "[docflow]";
60054
+ var describeError = (error) => {
60055
+ if (error instanceof Error) {
60056
+ const { status, body } = error;
60057
+ return { name: error.name, message: error.message, status, body };
60058
+ }
60059
+ return error;
60060
+ };
60061
+ var logDocflowError = (event, error, details) => console.error(PREFIX, event, describeError(error), details ?? "");
60062
+
60052
60063
  // src/server/http-response.ts
60053
60064
  var jsonResponse = (status, body) => new AL(JSON.stringify(body), {
60054
60065
  status,
60055
60066
  headers: { "content-type": "application/json; charset=utf-8" }
60056
60067
  });
60068
+ var withRouteErrors = (name, handler2) => async (...args) => {
60069
+ try {
60070
+ return await handler2(...args);
60071
+ } catch (error) {
60072
+ logDocflowError(`${name}: unexpected error`, error);
60073
+ return jsonResponse(500, {
60074
+ message: `\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043E\u0431\u043E\u0440\u043E\u0442\u0430: ${error instanceof Error ? error.message : String(error)}`
60075
+ });
60076
+ }
60077
+ };
60057
60078
 
60058
60079
  // src/universal-identifiers.ts
60059
60080
  var BITRIX_EVENT_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER = "f27cfbdc-8e87-4e1a-ab37-fc2a8093946c";
@@ -60083,7 +60104,7 @@ var bitrix_event_logic_function_default = defineLogicFunction({
60083
60104
  name: "docflow-bitrix-event",
60084
60105
  description: "\u0421\u043E\u0431\u044B\u0442\u0438\u044F \u043F\u043E\u0440\u0442\u0430\u043B\u0430 \u0411\u0438\u0442\u0440\u0438\u043A\u044124 (\u0443\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F)",
60085
60106
  timeoutSeconds: 15,
60086
- handler,
60107
+ handler: withRouteErrors("bitrix-event", handler),
60087
60108
  httpRouteTriggerSettings: {
60088
60109
  path: "/bitrix/event",
60089
60110
  httpMethod: "POST",
@@ -3,7 +3,7 @@ import { type RoutePayload } from 'twenty-sdk/logic-function';
3
3
 
4
4
  import { parseBitrixRequest } from 'src/shared/bitrix/bitrix.util';
5
5
  import { forgetBitrixPortal, loadBitrixPortal, saveBitrixPortal } from 'src/server/bitrix-portal';
6
- import { jsonResponse } from 'src/server/http-response';
6
+ import { jsonResponse, withRouteErrors } from 'src/server/http-response';
7
7
  import { BITRIX_EVENT_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/universal-identifiers';
8
8
 
9
9
  // Events of the linked portal. An event must name the linked portal; the
@@ -45,7 +45,7 @@ export default defineLogicFunction({
45
45
  name: 'docflow-bitrix-event',
46
46
  description: 'События портала Битрикс24 (удаление приложения)',
47
47
  timeoutSeconds: 15,
48
- handler,
48
+ handler: withRouteErrors('bitrix-event', handler),
49
49
  httpRouteTriggerSettings: {
50
50
  path: '/bitrix/event',
51
51
  httpMethod: 'POST',