@ketvietlab/ketsuite 0.1.22 → 0.1.25

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 (237) hide show
  1. package/dist/cli.js +9 -3
  2. package/dist/cli.js.map +1 -1
  3. package/dist/demo/crm.d.ts +3 -0
  4. package/dist/demo/crm.d.ts.map +1 -0
  5. package/dist/demo/crm.js +418 -0
  6. package/dist/demo/crm.js.map +1 -0
  7. package/dist/demo/partner.d.ts +8 -0
  8. package/dist/demo/partner.d.ts.map +1 -0
  9. package/dist/demo/partner.js +179 -0
  10. package/dist/demo/partner.js.map +1 -0
  11. package/dist/demo/product.d.ts +25 -0
  12. package/dist/demo/product.d.ts.map +1 -0
  13. package/dist/demo/product.js +579 -0
  14. package/dist/demo/product.js.map +1 -0
  15. package/dist/demo/purchase.d.ts +4 -0
  16. package/dist/demo/purchase.d.ts.map +1 -0
  17. package/dist/demo/purchase.js +58 -0
  18. package/dist/demo/purchase.js.map +1 -0
  19. package/dist/demo/sale.d.ts +4 -0
  20. package/dist/demo/sale.d.ts.map +1 -0
  21. package/dist/demo/sale.js +52 -0
  22. package/dist/demo/sale.js.map +1 -0
  23. package/dist/demo/seed.d.ts +14 -0
  24. package/dist/demo/seed.d.ts.map +1 -0
  25. package/dist/demo/seed.js +88 -0
  26. package/dist/demo/seed.js.map +1 -0
  27. package/dist/demo/stock.d.ts +7 -0
  28. package/dist/demo/stock.d.ts.map +1 -0
  29. package/dist/demo/stock.js +26 -0
  30. package/dist/demo/stock.js.map +1 -0
  31. package/dist/demo/user.d.ts +4 -0
  32. package/dist/demo/user.d.ts.map +1 -0
  33. package/dist/demo/user.js +41 -0
  34. package/dist/demo/user.js.map +1 -0
  35. package/dist/demo/util.d.ts +7 -0
  36. package/dist/demo/util.d.ts.map +1 -0
  37. package/dist/demo/util.js +19 -0
  38. package/dist/demo/util.js.map +1 -0
  39. package/dist/modules/backend/design/client/backend-shell.d.mts +2 -2
  40. package/dist/modules/backend/design/client/backend-shell.d.mts.map +1 -1
  41. package/dist/modules/backend/design/client/backend-shell.mjs +1 -1
  42. package/dist/modules/backend/design/client/backend-shell.mjs.map +3 -3
  43. package/dist/modules/backend/design/forms.css +7 -0
  44. package/dist/modules/backend/screen.d.ts.map +1 -1
  45. package/dist/modules/backend/screen.js +11 -2
  46. package/dist/modules/backend/screen.js.map +1 -1
  47. package/dist/modules/crm/functions/access.d.ts +3 -0
  48. package/dist/modules/crm/functions/access.d.ts.map +1 -0
  49. package/dist/modules/crm/functions/access.js +89 -0
  50. package/dist/modules/crm/functions/access.js.map +1 -0
  51. package/dist/modules/crm/functions/activities.d.ts +3 -0
  52. package/dist/modules/crm/functions/activities.d.ts.map +1 -0
  53. package/dist/modules/crm/functions/activities.js +174 -0
  54. package/dist/modules/crm/functions/activities.js.map +1 -0
  55. package/dist/modules/crm/functions/cases.d.ts +3 -0
  56. package/dist/modules/crm/functions/cases.d.ts.map +1 -0
  57. package/dist/modules/crm/functions/cases.js +585 -0
  58. package/dist/modules/crm/functions/cases.js.map +1 -0
  59. package/dist/modules/crm/functions/configuration.d.ts +3 -0
  60. package/dist/modules/crm/functions/configuration.d.ts.map +1 -0
  61. package/dist/modules/crm/functions/configuration.js +331 -0
  62. package/dist/modules/crm/functions/configuration.js.map +1 -0
  63. package/dist/modules/crm/functions/gamification.d.ts +3 -0
  64. package/dist/modules/crm/functions/gamification.d.ts.map +1 -0
  65. package/dist/modules/crm/functions/gamification.js +58 -0
  66. package/dist/modules/crm/functions/gamification.js.map +1 -0
  67. package/dist/modules/crm/functions/index.d.ts +4 -0
  68. package/dist/modules/crm/functions/index.d.ts.map +1 -0
  69. package/dist/modules/crm/functions/index.js +33 -0
  70. package/dist/modules/crm/functions/index.js.map +1 -0
  71. package/dist/modules/crm/functions/modal-context.d.ts +3 -0
  72. package/dist/modules/crm/functions/modal-context.d.ts.map +1 -0
  73. package/dist/modules/crm/functions/modal-context.js +248 -0
  74. package/dist/modules/crm/functions/modal-context.js.map +1 -0
  75. package/dist/modules/crm/functions/previews.d.ts +3 -0
  76. package/dist/modules/crm/functions/previews.d.ts.map +1 -0
  77. package/dist/modules/crm/functions/previews.js +20 -0
  78. package/dist/modules/crm/functions/previews.js.map +1 -0
  79. package/dist/modules/crm/functions/shared.d.ts +31 -0
  80. package/dist/modules/crm/functions/shared.d.ts.map +1 -0
  81. package/dist/modules/crm/functions/shared.js +194 -0
  82. package/dist/modules/crm/functions/shared.js.map +1 -0
  83. package/dist/modules/crm/index.d.ts +1 -1
  84. package/dist/modules/crm/index.d.ts.map +1 -1
  85. package/dist/modules/crm/index.js +2 -2
  86. package/dist/modules/crm/index.js.map +1 -1
  87. package/dist/modules/crm/messages.d.ts +4 -1
  88. package/dist/modules/crm/messages.d.ts.map +1 -1
  89. package/dist/modules/crm/messages.js +6 -0
  90. package/dist/modules/crm/messages.js.map +1 -1
  91. package/dist/modules/crm/record-modal-labels.d.ts +9 -0
  92. package/dist/modules/crm/record-modal-labels.d.ts.map +1 -0
  93. package/dist/modules/crm/record-modal-labels.js +36 -0
  94. package/dist/modules/crm/record-modal-labels.js.map +1 -0
  95. package/dist/modules/crm/types.d.ts +5 -0
  96. package/dist/modules/crm/types.d.ts.map +1 -1
  97. package/dist/modules/crm/types.js +9 -0
  98. package/dist/modules/crm/types.js.map +1 -1
  99. package/dist/modules/crm_backend/client/crm-configuration-modal.d.mts +352 -0
  100. package/dist/modules/crm_backend/client/crm-configuration-modal.d.mts.map +1 -0
  101. package/dist/modules/crm_backend/client/crm-configuration-modal.mjs +2 -0
  102. package/dist/modules/crm_backend/client/crm-configuration-modal.mjs.map +7 -0
  103. package/dist/modules/crm_backend/index.d.ts.map +1 -1
  104. package/dist/modules/crm_backend/index.js +5 -1
  105. package/dist/modules/crm_backend/index.js.map +1 -1
  106. package/dist/modules/crm_backend/islands.d.ts +27 -0
  107. package/dist/modules/crm_backend/islands.d.ts.map +1 -1
  108. package/dist/modules/crm_backend/islands.js +22 -0
  109. package/dist/modules/crm_backend/islands.js.map +1 -1
  110. package/dist/modules/crm_backend/messages.d.ts +30 -0
  111. package/dist/modules/crm_backend/messages.d.ts.map +1 -1
  112. package/dist/modules/crm_backend/messages.js +30 -0
  113. package/dist/modules/crm_backend/messages.js.map +1 -1
  114. package/dist/modules/crm_backend/modal/configuration-modal-view.d.ts +55 -0
  115. package/dist/modules/crm_backend/modal/configuration-modal-view.d.ts.map +1 -0
  116. package/dist/modules/crm_backend/modal/configuration-modal-view.js +601 -0
  117. package/dist/modules/crm_backend/modal/configuration-modal-view.js.map +1 -0
  118. package/dist/modules/crm_backend/routes.d.ts.map +1 -1
  119. package/dist/modules/crm_backend/routes.js +69 -359
  120. package/dist/modules/crm_backend/routes.js.map +1 -1
  121. package/dist/modules/crm_backend/screens/configuration.d.ts +24 -32
  122. package/dist/modules/crm_backend/screens/configuration.d.ts.map +1 -1
  123. package/dist/modules/crm_backend/screens/configuration.js +183 -274
  124. package/dist/modules/crm_backend/screens/configuration.js.map +1 -1
  125. package/dist/modules/crm_backend/screens/index.d.ts +2 -2
  126. package/dist/modules/crm_backend/screens/index.d.ts.map +1 -1
  127. package/dist/modules/crm_backend/screens/index.js +1 -1
  128. package/dist/modules/crm_backend/screens/index.js.map +1 -1
  129. package/dist/modules/product/functions.d.ts +14 -1
  130. package/dist/modules/product/functions.d.ts.map +1 -1
  131. package/dist/modules/product/functions.js +36 -31
  132. package/dist/modules/product/functions.js.map +1 -1
  133. package/dist/modules/product/product-modal-context.d.ts +3 -0
  134. package/dist/modules/product/product-modal-context.d.ts.map +1 -0
  135. package/dist/modules/product/product-modal-context.js +173 -0
  136. package/dist/modules/product/product-modal-context.js.map +1 -0
  137. package/dist/modules/product/product-record-tabs.d.ts +14 -0
  138. package/dist/modules/product/product-record-tabs.d.ts.map +1 -0
  139. package/dist/modules/product/product-record-tabs.js +23 -0
  140. package/dist/modules/product/product-record-tabs.js.map +1 -0
  141. package/dist/modules/product_backend/client/product-modal.d.mts +144 -0
  142. package/dist/modules/product_backend/client/product-modal.d.mts.map +1 -0
  143. package/dist/modules/product_backend/client/product-modal.mjs +2 -0
  144. package/dist/modules/product_backend/client/product-modal.mjs.map +7 -0
  145. package/dist/modules/product_backend/index.d.ts.map +1 -1
  146. package/dist/modules/product_backend/index.js +25 -0
  147. package/dist/modules/product_backend/index.js.map +1 -1
  148. package/dist/modules/product_backend/islands.d.ts +1 -0
  149. package/dist/modules/product_backend/islands.d.ts.map +1 -1
  150. package/dist/modules/product_backend/islands.js +9 -0
  151. package/dist/modules/product_backend/islands.js.map +1 -1
  152. package/dist/modules/product_backend/modal/product-modal-view.d.ts +49 -0
  153. package/dist/modules/product_backend/modal/product-modal-view.d.ts.map +1 -0
  154. package/dist/modules/product_backend/modal/product-modal-view.js +609 -0
  155. package/dist/modules/product_backend/modal/product-modal-view.js.map +1 -0
  156. package/dist/modules/product_backend/routes.d.ts.map +1 -1
  157. package/dist/modules/product_backend/routes.js +44 -330
  158. package/dist/modules/product_backend/routes.js.map +1 -1
  159. package/dist/modules/product_backend/screens/detail.d.ts.map +1 -1
  160. package/dist/modules/product_backend/screens/detail.js +6 -108
  161. package/dist/modules/product_backend/screens/detail.js.map +1 -1
  162. package/dist/modules/product_backend/screens/index.d.ts +0 -1
  163. package/dist/modules/product_backend/screens/index.d.ts.map +1 -1
  164. package/dist/modules/product_backend/screens/index.js +0 -1
  165. package/dist/modules/product_backend/screens/index.js.map +1 -1
  166. package/dist/modules/product_backend/screens/list.d.ts +3 -1
  167. package/dist/modules/product_backend/screens/list.d.ts.map +1 -1
  168. package/dist/modules/product_backend/screens/list.js +6 -5
  169. package/dist/modules/product_backend/screens/list.js.map +1 -1
  170. package/dist/modules/user/authorization.d.ts.map +1 -1
  171. package/dist/modules/user/authorization.js +114 -0
  172. package/dist/modules/user/authorization.js.map +1 -1
  173. package/dist/modules/user/functions.d.ts.map +1 -1
  174. package/dist/modules/user/functions.js +2 -0
  175. package/dist/modules/user/functions.js.map +1 -1
  176. package/dist/modules/user/user-modal-context.d.ts +3 -0
  177. package/dist/modules/user/user-modal-context.d.ts.map +1 -0
  178. package/dist/modules/user/user-modal-context.js +151 -0
  179. package/dist/modules/user/user-modal-context.js.map +1 -0
  180. package/dist/modules/user_backend/client/user-modal.d.mts +72 -0
  181. package/dist/modules/user_backend/client/user-modal.d.mts.map +1 -0
  182. package/dist/modules/user_backend/client/user-modal.mjs +2 -0
  183. package/dist/modules/user_backend/client/user-modal.mjs.map +7 -0
  184. package/dist/modules/user_backend/index.d.ts.map +1 -1
  185. package/dist/modules/user_backend/index.js +9 -0
  186. package/dist/modules/user_backend/index.js.map +1 -1
  187. package/dist/modules/user_backend/islands.d.ts +15 -0
  188. package/dist/modules/user_backend/islands.d.ts.map +1 -0
  189. package/dist/modules/user_backend/islands.js +14 -0
  190. package/dist/modules/user_backend/islands.js.map +1 -0
  191. package/dist/modules/user_backend/messages.d.ts +30 -0
  192. package/dist/modules/user_backend/messages.d.ts.map +1 -1
  193. package/dist/modules/user_backend/messages.js +30 -0
  194. package/dist/modules/user_backend/messages.js.map +1 -1
  195. package/dist/modules/user_backend/modal/user-modal-view.d.ts +26 -0
  196. package/dist/modules/user_backend/modal/user-modal-view.d.ts.map +1 -0
  197. package/dist/modules/user_backend/modal/user-modal-view.js +320 -0
  198. package/dist/modules/user_backend/modal/user-modal-view.js.map +1 -0
  199. package/dist/modules/user_backend/routes.d.ts.map +1 -1
  200. package/dist/modules/user_backend/routes.js +15 -3
  201. package/dist/modules/user_backend/routes.js.map +1 -1
  202. package/dist/permission-catalogue.d.ts.map +1 -1
  203. package/dist/permission-catalogue.js +8 -0
  204. package/dist/permission-catalogue.js.map +1 -1
  205. package/dist/scaffold/index.js +1 -1
  206. package/dist/ui/client/record-modal-form.d.ts +67 -0
  207. package/dist/ui/client/record-modal-form.d.ts.map +1 -0
  208. package/dist/ui/client/record-modal-form.js +45 -0
  209. package/dist/ui/client/record-modal-form.js.map +1 -0
  210. package/dist/ui/client/record-modal.d.ts +103 -6
  211. package/dist/ui/client/record-modal.d.ts.map +1 -1
  212. package/dist/ui/client/record-modal.js +333 -65
  213. package/dist/ui/client/record-modal.js.map +1 -1
  214. package/dist/ui/index.d.ts +2 -1
  215. package/dist/ui/index.d.ts.map +1 -1
  216. package/dist/ui/index.js +2 -1
  217. package/dist/ui/index.js.map +1 -1
  218. package/dist/ui/nav.d.ts +7 -0
  219. package/dist/ui/nav.d.ts.map +1 -1
  220. package/dist/ui/nav.js +1 -1
  221. package/dist/ui/nav.js.map +1 -1
  222. package/dist/ui/record-modal.d.ts +15 -0
  223. package/dist/ui/record-modal.d.ts.map +1 -1
  224. package/dist/ui/record-modal.js +12 -0
  225. package/dist/ui/record-modal.js.map +1 -1
  226. package/dist/ui/table.d.ts.map +1 -1
  227. package/dist/ui/table.js +1 -1
  228. package/dist/ui/table.js.map +1 -1
  229. package/package.json +4 -4
  230. package/dist/modules/crm/functions.d.ts +0 -10
  231. package/dist/modules/crm/functions.d.ts.map +0 -1
  232. package/dist/modules/crm/functions.js +0 -1394
  233. package/dist/modules/crm/functions.js.map +0 -1
  234. package/dist/modules/product_backend/screens/create.d.ts +0 -16
  235. package/dist/modules/product_backend/screens/create.d.ts.map +0 -1
  236. package/dist/modules/product_backend/screens/create.js +0 -86
  237. package/dist/modules/product_backend/screens/create.js.map +0 -1
@@ -1,14 +1,14 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { encodeListState, parseListState, table, text } from '@ketvietlab/ketjs';
3
- import { formatDateTime, formatMoney, modalWorkspace } from '../../ui/index.js';
3
+ import { formatDateTime, formatMoney, modalWorkspace, recordModalCreateHref, recordModalHref, } from '../../ui/index.js';
4
4
  import { formRefusal, readForm, seeOther } from '../backend/forms.js';
5
- import { choices, adminPage, inLocale, localeQuery, optional, timezoneOf } from '../backend/screen.js';
5
+ import { adminPage, inLocale, localeQuery, optional, timezoneOf } from '../backend/screen.js';
6
6
  import { withParam } from '../backend/paging.js';
7
7
  import { receiveAttachment } from '../storage/routes.js';
8
8
  import { caseListSearch } from '../crm/search.js';
9
- import { caseFormSchema } from '../crm/functions.js';
9
+ import { caseFormSchema } from '../crm/functions/index.js';
10
10
  import { assigneeControl, caseControl, partnerControl, productControl, stageControl, tagsControl, teamControl, } from './relation-control.js';
11
- import { CONFIGURATION_TABS, caseCloseModal, caseConvertModal, caseCreateScreen, caseDetailScreen, casesListScreen, configurationScreen, leaderboardScreen, plannerScreen, permissionScreen, pipelineScreen, teamConfigurationScreen, } from './screens/index.js';
11
+ import { CONFIGURATION_RECORD_KINDS, CONFIGURATION_SAVE_FUNCTIONS, CONFIGURATION_SECTIONS, CONFIGURATION_STATUSES, caseCloseModal, caseConvertModal, caseCreateScreen, caseDetailScreen, casesListScreen, configurationScreen, leaderboardScreen, plannerScreen, permissionScreen, pipelineScreen, } from './screens/index.js';
12
12
  import { keepForListSearch, LIST_PAGE_SIZE, listFacets, listMenus, loadListGroups, } from '../backend/list-search.js';
13
13
  const bool = (value) => ['1', 'true', 'on'].includes(value ?? '');
14
14
  const errorsOf = (result, _) => (result?.errors ?? []).map((error) => {
@@ -468,85 +468,57 @@ const pipelineFigures = (_, summary, money) => {
468
468
  },
469
469
  ];
470
470
  };
471
- const configurationTabOf = (url) => {
472
- const asked = url.searchParams.get('tab') ?? '';
473
- return CONFIGURATION_TABS.includes(asked) ? asked : 'teams';
471
+ const configurationSectionOf = (url) => {
472
+ const asked = url.searchParams.get('section') ?? '';
473
+ return CONFIGURATION_SECTIONS.includes(asked)
474
+ ? asked
475
+ : 'teams';
474
476
  };
475
477
  const configurationStatusOf = (url) => {
476
478
  const asked = url.searchParams.get('status') ?? 'active';
477
- return ['active', 'archived', 'all'].includes(asked) ? asked : 'active';
479
+ return CONFIGURATION_STATUSES.includes(asked)
480
+ ? asked
481
+ : 'active';
478
482
  };
479
- const teamConfigurationRoute = (ctx, creating) => async (url, req, params) => {
480
- const refused = refusePost(req);
481
- if (refused)
482
- return refused;
483
- const _ = ctx.translate(ctx.localeOf(url, req));
484
- const requestedId = creating ? '' : params.id;
485
- const memberAsked = url.searchParams.get('member');
486
- let errors = [];
487
- let teamId = requestedId;
488
- if (req.method === 'POST') {
489
- const form = await readForm(req);
490
- const call = (name, input) => ctx.call(name, input, url, req);
491
- if (memberAsked && !creating) {
492
- const result = await teamMemberWrite(call, form, requestedId, form.id || randomUUID());
493
- if (result.ok)
494
- return seeOther(inLocale(url, `/admin/crm/configuration/teams/${encodeURIComponent(requestedId)}`));
495
- errors = errorsOf(result, _);
496
- }
497
- else {
498
- teamId = form.id || requestedId || randomUUID();
499
- const result = await configurationWrite(call, 'teams', form, teamId);
500
- if (result.ok)
501
- return seeOther(inLocale(url, `/admin/crm/configuration/teams/${encodeURIComponent(teamId)}`));
502
- errors = errorsOf(result, _);
503
- }
504
- }
505
- else if (req.method !== 'GET')
506
- return text('GET or POST', { status: 405 });
507
- const [config, users, listedMembers] = await Promise.all([
508
- configuration(ctx, url, req),
509
- people(ctx, url, req, 200),
510
- creating
511
- ? Promise.resolve([])
512
- : ctx.call('crm.team.member.list', { teamId: requestedId, limit: 200 }, url, req),
513
- ]);
514
- const team = creating
515
- ? {}
516
- : ((config.teams ?? []).find((row) => String(row.id) === requestedId) ?? null);
517
- if (!team)
518
- return text('not found', { status: 404 });
519
- const memberEditing = memberAsked && memberAsked !== 'new'
520
- ? (listedMembers.find((row) => String(row.id) === memberAsked) ?? null)
521
- : null;
522
- const path = creating
523
- ? '/admin/crm/configuration/teams/new'
524
- : `/admin/crm/configuration/teams/${encodeURIComponent(requestedId)}`;
525
- const action = inLocale(url, path);
526
- const memberAction = memberAsked
527
- ? inLocale(url, `${path}?member=${encodeURIComponent(memberAsked)}`)
528
- : undefined;
529
- return adminPage(ctx, url, req, {
530
- title: 'crm_backend.configuration.title',
531
- active: '/admin/crm/configuration',
532
- body: (_, frame) => teamConfigurationScreen(_, frame, {
533
- team,
534
- fields: configurationFields(_, 'teams', team, config.teams ?? [], users),
535
- members: listedMembers,
536
- action,
537
- cancelHref: inLocale(url, '/admin/crm/configuration?tab=teams'),
538
- errors,
539
- creating,
540
- memberCreateHref: creating ? undefined : inLocale(url, `${path}?member=new`),
541
- memberEditHref: creating
542
- ? undefined
543
- : (row) => inLocale(url, `${path}?member=${encodeURIComponent(String(row.id))}`),
544
- memberEditing,
545
- memberCreating: memberAsked === 'new',
546
- memberFields: memberAsked ? teamMemberFields(_, memberEditing, users) : undefined,
547
- memberAction,
548
- }),
549
- });
483
+ /**
484
+ * Where an old configuration link lives now, or null when it is current.
485
+ *
486
+ * Catalogues used to be `?tab=`, which the record modal reserves for its own tab,
487
+ * and editing used `&edit=<id>` / `&create=1`. A `tab` that names a catalogue and
488
+ * comes without a `record` is the old shape; one next to `record` is a modal tab.
489
+ */
490
+ const legacyConfigurationHref = (url) => {
491
+ const tab = url.searchParams.get('tab');
492
+ const legacyTab = tab !== null &&
493
+ !url.searchParams.has('record') &&
494
+ CONFIGURATION_SECTIONS.includes(tab);
495
+ const edit = url.searchParams.get('edit');
496
+ const create = url.searchParams.get('create') === '1';
497
+ if (!legacyTab && edit === null && !create)
498
+ return null;
499
+ const section = legacyTab ? tab : configurationSectionOf(url);
500
+ const next = new URL(`${url.pathname}${url.search}`, 'http://ket.local');
501
+ if (legacyTab)
502
+ next.searchParams.delete('tab');
503
+ next.searchParams.delete('edit');
504
+ next.searchParams.delete('create');
505
+ next.searchParams.set('section', section);
506
+ const path = `${next.pathname}${next.search}`;
507
+ const kind = CONFIGURATION_RECORD_KINDS[section];
508
+ if (edit)
509
+ return recordModalHref(path, { kind, id: edit });
510
+ if (create)
511
+ return recordModalCreateHref(path, { kind });
512
+ return path;
513
+ };
514
+ /** The team pages became the team record modal over the teams catalogue. */
515
+ const teamPageRedirect = (creating) => async (url, req, params) => {
516
+ if (req.method !== 'GET')
517
+ return text('GET', { status: 405 });
518
+ const list = inLocale(url, '/admin/crm/configuration?section=teams');
519
+ return seeOther(creating
520
+ ? recordModalCreateHref(list, { kind: 'crm.team' })
521
+ : recordModalHref(list, { kind: 'crm.team', id: String(params.id) }));
550
522
  };
551
523
  export const routes = {
552
524
  '/admin/crm': () => async (url, req) => req.method === 'GET' ? seeOther(inLocale(url, '/admin/crm/pipeline')) : text('GET', { status: 405 }),
@@ -1319,299 +1291,37 @@ export const routes = {
1319
1291
  const refused = refusePost(req);
1320
1292
  if (refused)
1321
1293
  return refused;
1322
- const _ = ctx.translate(ctx.localeOf(url, req));
1323
- const tab = configurationTabOf(url);
1294
+ if (req.method !== 'GET')
1295
+ return text('GET', { status: 405 });
1296
+ const legacy = legacyConfigurationHref(url);
1297
+ if (legacy)
1298
+ return seeOther(legacy);
1299
+ const section = configurationSectionOf(url);
1324
1300
  const status = configurationStatusOf(url);
1325
- const statusQuery = status === 'active' ? '' : `&status=${status}`;
1326
- const back = inLocale(url, `/admin/crm/configuration?tab=${tab}${statusQuery}`);
1327
- if (tab === 'teams' && url.searchParams.has('edit'))
1328
- return seeOther(inLocale(url, `/admin/crm/configuration/teams/${encodeURIComponent(String(url.searchParams.get('edit')))}`));
1329
- if (tab === 'teams' && url.searchParams.get('create') === '1')
1330
- return seeOther(inLocale(url, '/admin/crm/configuration/teams/new'));
1331
- let errors = [];
1332
- if (req.method === 'POST') {
1333
- if (crossSite(req))
1334
- return text('Forbidden', { status: 403 });
1335
- const form = await readForm(req);
1336
- const call = (name, input) => ctx.call(name, input, url, req);
1337
- const id = form.id || randomUUID();
1338
- const result = await configurationWrite(call, tab, form, id);
1339
- if (result.ok)
1340
- return seeOther(back);
1341
- errors = errorsOf(result, _);
1342
- }
1343
- else if (req.method !== 'GET')
1344
- return text('GET or POST', { status: 405 });
1345
- const [config, tags, users] = await Promise.all([
1301
+ const [config, tags, users, canCreate] = await Promise.all([
1346
1302
  configuration(ctx, url, req),
1347
- ctx.call('crm.tag.list', { includeArchived: true, limit: 200 }, url, req),
1303
+ section === 'tags'
1304
+ ? allowed(ctx, 'crm.tag.list', { includeArchived: true, limit: 200 }, url, req, [])
1305
+ : Promise.resolve([]),
1348
1306
  people(ctx, url, req, 200),
1307
+ ctx.allows(CONFIGURATION_SAVE_FUNCTIONS[section], url, req),
1349
1308
  ]);
1350
- const allRows = tab === 'tags' ? tags : (config[tab] ?? []);
1309
+ const allRows = section === 'tags' ? tags : (config[section] ?? []);
1351
1310
  const rows = allRows.filter((row) => status === 'all' ? true : status === 'active' ? row.active !== false : row.active === false);
1352
- const asked = url.searchParams.get('edit');
1353
- const editing = asked ? (allRows.find((row) => String(row.id) === asked) ?? null) : null;
1354
- const teams = config.teams ?? [];
1355
1311
  return adminPage(ctx, url, req, {
1356
1312
  title: 'crm_backend.configuration.title',
1357
1313
  body: (_, frame) => configurationScreen(_, frame, {
1358
- tab,
1314
+ section,
1315
+ status,
1359
1316
  rows,
1360
- editing,
1361
- creating: url.searchParams.get('create') === '1' || (req.method === 'POST' && !editing),
1362
- errors,
1363
1317
  locale: localeQuery(url),
1364
- status,
1365
- teams,
1318
+ teams: config.teams ?? [],
1366
1319
  users,
1367
- fields: configurationFields(_, tab, editing, teams, users),
1320
+ canCreate,
1368
1321
  }),
1369
1322
  });
1370
1323
  },
1371
- '/admin/crm/configuration/teams/new': (ctx) => teamConfigurationRoute(ctx, true),
1372
- '/admin/crm/configuration/teams/{id}': (ctx) => teamConfigurationRoute(ctx, false),
1324
+ '/admin/crm/configuration/teams/new': () => teamPageRedirect(true),
1325
+ '/admin/crm/configuration/teams/{id}': () => teamPageRedirect(false),
1373
1326
  };
1374
- /**
1375
- * One write per configuration tab.
1376
- *
1377
- * The tabs do not share a function signature — a tag saves by name while the
1378
- * other configuration records save a versioned values bag.
1379
- */
1380
- async function configurationWrite(call, tab, form, id) {
1381
- const active = bool(form.active);
1382
- if (tab === 'tags') {
1383
- return call('crm.tag.save', { id, name: form.name ?? '', active });
1384
- }
1385
- const fn = {
1386
- teams: 'crm.team.save',
1387
- stages: 'crm.stage.save',
1388
- assignmentRules: 'crm.assignmentRule.save',
1389
- scoreRules: 'crm.scoreRule.save',
1390
- }[tab];
1391
- const values = {
1392
- ...form,
1393
- id,
1394
- active,
1395
- ...(form.expectedVersion ? { expectedVersion: Number(form.expectedVersion) } : {}),
1396
- ...(['stages', 'assignmentRules'].includes(tab)
1397
- ? { allowedKinds: ['lead', 'opportunity'].filter((kind) => bool(form[`kind_${kind}`])) }
1398
- : {}),
1399
- ...(form.sequence === undefined ? {} : { sequence: Number(form.sequence) }),
1400
- ...(form.priority === undefined ? {} : { priority: Number(form.priority) }),
1401
- ...(form.minimumScore === undefined || form.minimumScore === ''
1402
- ? { minimumScore: null }
1403
- : { minimumScore: form.minimumScore }),
1404
- ...(form.teamId === '' ? { teamId: null } : {}),
1405
- ...(form.assigneeUserId === '' ? { assigneeUserId: null } : {}),
1406
- ...(form.leaderUserId === '' ? { leaderUserId: null } : {}),
1407
- ...(form.fold === undefined ? {} : { fold: bool(form.fold) }),
1408
- };
1409
- delete values.action;
1410
- delete values.kind_lead;
1411
- delete values.kind_opportunity;
1412
- return call(fn, { values, idempotencyKey: randomUUID() });
1413
- }
1414
- async function teamMemberWrite(call, form, teamId, id) {
1415
- return call('crm.team.member.save', {
1416
- id,
1417
- teamId,
1418
- userId: form.userId ?? '',
1419
- capacity: Number(form.capacity ?? 1),
1420
- sequence: Number(form.sequence ?? 10),
1421
- active: bool(form.active),
1422
- idempotencyKey: randomUUID(),
1423
- });
1424
- }
1425
- function teamMemberFields(_, editing, users) {
1426
- const value = (name, fallback = '') => String(editing?.[name] ?? fallback);
1427
- return [
1428
- {
1429
- name: 'userId',
1430
- label: _('crm_backend.field.assignee'),
1431
- type: 'select',
1432
- required: true,
1433
- value: value('userId'),
1434
- options: choices(users),
1435
- },
1436
- {
1437
- name: 'capacity',
1438
- label: _('crm_backend.field.capacity'),
1439
- type: 'number',
1440
- value: value('capacity', '1'),
1441
- help: _('crm_backend.field.capacityHint'),
1442
- },
1443
- {
1444
- name: 'sequence',
1445
- label: _('crm_backend.field.sequence'),
1446
- type: 'number',
1447
- value: value('sequence', '10'),
1448
- },
1449
- {
1450
- name: 'active',
1451
- label: _('crm_backend.field.active'),
1452
- type: 'checkbox',
1453
- value: editing ? editing.active !== false : true,
1454
- },
1455
- ];
1456
- }
1457
- /** The form for one configuration tab, pre-filled when a row is being edited. */
1458
- function configurationFields(_, tab, editing, teams, users) {
1459
- const value = (name, fallback = '') => String(editing?.[name] ?? fallback);
1460
- const common = [
1461
- { name: 'name', label: _('crm_backend.field.name'), value: value('name'), required: true },
1462
- {
1463
- name: 'active',
1464
- label: _('crm_backend.field.active'),
1465
- type: 'checkbox',
1466
- value: editing ? editing.active !== false : true,
1467
- },
1468
- ];
1469
- if (tab === 'tags')
1470
- return common;
1471
- if (tab === 'teams')
1472
- return [
1473
- ...common,
1474
- { name: 'code', label: _('crm_backend.field.code'), value: value('code'), required: true },
1475
- {
1476
- name: 'leaderUserId',
1477
- label: _('crm_backend.field.teamLeader'),
1478
- type: 'select',
1479
- value: value('leaderUserId'),
1480
- options: [{ value: '', label: _('crm_backend.value.unset') }, ...choices(users)],
1481
- },
1482
- {
1483
- name: 'assignmentMode',
1484
- label: _('crm_backend.field.assignmentMode'),
1485
- type: 'select',
1486
- value: value('assignmentMode', 'manual'),
1487
- options: ['manual', 'round_robin', 'capacity'].map((item) => ({
1488
- value: item,
1489
- label: _(`crm_backend.assignmentMode.${item}`),
1490
- })),
1491
- },
1492
- ];
1493
- if (tab === 'stages')
1494
- return [
1495
- ...common,
1496
- { name: 'code', label: _('crm_backend.field.code'), value: value('code'), required: true },
1497
- {
1498
- name: 'sequence',
1499
- label: _('crm_backend.field.sequence'),
1500
- type: 'number',
1501
- value: value('sequence', '10'),
1502
- },
1503
- {
1504
- name: 'allowedKinds',
1505
- label: _('crm_backend.field.allowedKinds'),
1506
- type: 'checkbox-group',
1507
- required: true,
1508
- options: ['lead', 'opportunity'].map((kind) => ({
1509
- name: `kind_${kind}`,
1510
- value: '1',
1511
- label: _(`crm.kind.${kind}`),
1512
- checked: editing
1513
- ? Array.isArray(editing.allowedKinds) && editing.allowedKinds.includes(kind)
1514
- : true,
1515
- })),
1516
- },
1517
- {
1518
- name: 'teamId',
1519
- label: _('crm_backend.field.team'),
1520
- type: 'select',
1521
- value: value('teamId'),
1522
- options: [{ value: '', label: _('crm_backend.value.allTeams') }, ...choices(teams)],
1523
- },
1524
- {
1525
- name: 'terminalState',
1526
- label: _('crm_backend.field.state'),
1527
- type: 'select',
1528
- value: value('terminalState', 'open'),
1529
- options: ['open', 'won', 'lost'].map((item) => ({ value: item, label: _(`crm.terminal.${item}`) })),
1530
- },
1531
- {
1532
- name: 'fold',
1533
- label: _('crm_backend.field.fold'),
1534
- type: 'checkbox',
1535
- value: editing?.fold === true,
1536
- },
1537
- ];
1538
- if (tab === 'assignmentRules')
1539
- return [
1540
- ...common,
1541
- {
1542
- name: 'priority',
1543
- label: _('crm_backend.field.priority'),
1544
- type: 'number',
1545
- value: value('priority', '10'),
1546
- },
1547
- {
1548
- name: 'allowedKinds',
1549
- label: _('crm_backend.field.allowedKinds'),
1550
- type: 'checkbox-group',
1551
- required: true,
1552
- options: ['lead', 'opportunity'].map((kind) => ({
1553
- name: `kind_${kind}`,
1554
- value: '1',
1555
- label: _(`crm.kind.${kind}`),
1556
- checked: editing
1557
- ? Array.isArray(editing.allowedKinds) && editing.allowedKinds.includes(kind)
1558
- : true,
1559
- })),
1560
- },
1561
- {
1562
- name: 'teamId',
1563
- label: _('crm_backend.field.team'),
1564
- type: 'select',
1565
- required: true,
1566
- value: value('teamId'),
1567
- options: choices(teams),
1568
- },
1569
- {
1570
- name: 'assigneeUserId',
1571
- label: _('crm_backend.field.assignee'),
1572
- type: 'select',
1573
- value: value('assigneeUserId'),
1574
- options: [{ value: '', label: _('crm_backend.value.teamMode') }, ...choices(users)],
1575
- },
1576
- { name: 'utmSource', label: _('crm_backend.field.utmSource'), value: value('utmSource') },
1577
- {
1578
- name: 'minimumScore',
1579
- label: _('crm_backend.field.minimumScore'),
1580
- type: 'number',
1581
- value: value('minimumScore'),
1582
- },
1583
- ];
1584
- return [
1585
- ...common,
1586
- {
1587
- name: 'field',
1588
- label: _('crm_backend.field.ruleField'),
1589
- type: 'select',
1590
- value: value('field', 'email'),
1591
- required: true,
1592
- options: ['email', 'utmSource', 'expectedRevenue'].map((field) => ({
1593
- value: field,
1594
- label: _(`crm_backend.scoreField.${field}`),
1595
- })),
1596
- },
1597
- {
1598
- name: 'operator',
1599
- label: _('crm_backend.field.operator'),
1600
- type: 'select',
1601
- value: value('operator', 'eq'),
1602
- options: ['eq', 'contains', 'present', 'gte'].map((item) => ({
1603
- value: item,
1604
- label: _(`crm_backend.operator.${item}`),
1605
- })),
1606
- },
1607
- { name: 'value', label: _('crm_backend.field.ruleValue'), value: value('value') },
1608
- { name: 'points', label: _('crm_backend.field.points'), type: 'number', value: value('points', '0') },
1609
- {
1610
- name: 'sequence',
1611
- label: _('crm_backend.field.sequence'),
1612
- type: 'number',
1613
- value: value('sequence', '10'),
1614
- },
1615
- ];
1616
- }
1617
1327
  //# sourceMappingURL=routes.js.map