@open-mercato/core 0.6.6 → 0.6.7-develop.6553.1.2ec7246e04

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 (271) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/gateway_session_initialization/index.js +23 -0
  3. package/dist/generated/entities/gateway_session_initialization/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +1 -0
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +12 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/api_keys/api/keys/route.js +30 -13
  9. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  10. package/dist/modules/api_keys/cli.js +2 -1
  11. package/dist/modules/api_keys/cli.js.map +2 -2
  12. package/dist/modules/attachments/api/file/[id]/route.js +9 -7
  13. package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
  14. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +9 -7
  15. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
  16. package/dist/modules/attachments/api/route.js +21 -17
  17. package/dist/modules/attachments/api/route.js.map +2 -2
  18. package/dist/modules/attachments/components/AttachmentContentPreview.js +6 -35
  19. package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
  20. package/dist/modules/attachments/lib/reconcileOrganization.js +110 -0
  21. package/dist/modules/attachments/lib/reconcileOrganization.js.map +7 -0
  22. package/dist/modules/attachments/lib/requestScope.js +10 -0
  23. package/dist/modules/attachments/lib/requestScope.js.map +7 -0
  24. package/dist/modules/audit_logs/backend/audit-logs/page.js +14 -24
  25. package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
  26. package/dist/modules/auth/api/autologin.js +85 -0
  27. package/dist/modules/auth/api/autologin.js.map +7 -0
  28. package/dist/modules/auth/api/users/route.js +6 -2
  29. package/dist/modules/auth/api/users/route.js.map +2 -2
  30. package/dist/modules/auth/cli.js +3 -2
  31. package/dist/modules/auth/cli.js.map +2 -2
  32. package/dist/modules/auth/lib/autologin.js +19 -0
  33. package/dist/modules/auth/lib/autologin.js.map +7 -0
  34. package/dist/modules/configs/lib/upgrade-actions.js +22 -0
  35. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  36. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +1 -0
  37. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
  38. package/dist/modules/customer_accounts/api/admin/roles/[id].js +17 -3
  39. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  40. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +6 -1
  41. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  42. package/dist/modules/customer_accounts/api/portal/users-invite.js +6 -1
  43. package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
  44. package/dist/modules/customer_accounts/services/customerInvitationService.js +1 -0
  45. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
  46. package/dist/modules/customer_accounts/services/customerRbacService.js +6 -1
  47. package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
  48. package/dist/modules/customer_accounts/widgets/injection-table.js +0 -2
  49. package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
  50. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +7 -1
  51. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  52. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +2 -2
  53. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
  54. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +37 -43
  55. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  56. package/dist/modules/customers/components/detail/DealDetailTabs.js +23 -35
  57. package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
  58. package/dist/modules/customers/components/detail/DetailTabsLayout.js +6 -21
  59. package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
  60. package/dist/modules/customers/components/detail/MobilePersonDetail.js +26 -32
  61. package/dist/modules/customers/components/detail/MobilePersonDetail.js.map +2 -2
  62. package/dist/modules/customers/components/detail/PersonDetailTabs.js +29 -39
  63. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  64. package/dist/modules/customers/components/detail/useDealsAccess.js +16 -0
  65. package/dist/modules/customers/components/detail/useDealsAccess.js.map +7 -0
  66. package/dist/modules/dashboards/api/roles/widgets/route.js +22 -3
  67. package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
  68. package/dist/modules/dashboards/api/users/widgets/route.js +22 -3
  69. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  70. package/dist/modules/dashboards/cli.js +4 -3
  71. package/dist/modules/dashboards/cli.js.map +2 -2
  72. package/dist/modules/dashboards/lib/widgetAssignmentScope.js +9 -1
  73. package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
  74. package/dist/modules/dictionaries/commands/entries.js +7 -14
  75. package/dist/modules/dictionaries/commands/entries.js.map +2 -2
  76. package/dist/modules/dictionaries/commands/entry-operations.js +7 -16
  77. package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
  78. package/dist/modules/dictionaries/commands/factory.js +9 -3
  79. package/dist/modules/dictionaries/commands/factory.js.map +2 -2
  80. package/dist/modules/dictionaries/fields/dictionary.js +56 -41
  81. package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
  82. package/dist/modules/directory/utils/organizationScope.js +10 -3
  83. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  84. package/dist/modules/entities/api/records.js +5 -4
  85. package/dist/modules/entities/api/records.js.map +2 -2
  86. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +1 -0
  87. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js.map +2 -2
  88. package/dist/modules/entities/backend/entities/user/[entityId]/page.js +49 -49
  89. package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
  90. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js +1 -0
  91. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
  92. package/dist/modules/entities/cli.js +2 -1
  93. package/dist/modules/entities/cli.js.map +2 -2
  94. package/dist/modules/feature_toggles/api/check/boolean/route.js +0 -14
  95. package/dist/modules/feature_toggles/api/check/boolean/route.js.map +2 -2
  96. package/dist/modules/feature_toggles/defaults.json +8 -0
  97. package/dist/modules/integrations/backend/integrations/[id]/page.js +9 -80
  98. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  99. package/dist/modules/payment_gateways/data/entities.js +45 -0
  100. package/dist/modules/payment_gateways/data/entities.js.map +2 -2
  101. package/dist/modules/payment_gateways/lib/gateway-service.js +173 -38
  102. package/dist/modules/payment_gateways/lib/gateway-service.js.map +3 -3
  103. package/dist/modules/payment_gateways/lib/session-idempotency.js +104 -0
  104. package/dist/modules/payment_gateways/lib/session-idempotency.js.map +7 -0
  105. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js +11 -0
  106. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js.map +7 -0
  107. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +16 -14
  108. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
  109. package/dist/modules/planner/components/AvailabilityRulesEditor.js +18 -30
  110. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  111. package/dist/modules/query_index/api/status.js +2 -8
  112. package/dist/modules/query_index/api/status.js.map +2 -2
  113. package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -24
  114. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  115. package/dist/modules/sales/backend/sales/channels/create/page.js +6 -0
  116. package/dist/modules/sales/backend/sales/channels/create/page.js.map +2 -2
  117. package/dist/modules/sales/backend/sales/channels/create/page.meta.js +2 -0
  118. package/dist/modules/sales/backend/sales/channels/create/page.meta.js.map +2 -2
  119. package/dist/modules/sales/backend/sales/channels/offers/page.js +6 -0
  120. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  121. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js +2 -0
  122. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js.map +2 -2
  123. package/dist/modules/sales/backend/sales/channels/page.js +6 -0
  124. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  125. package/dist/modules/sales/backend/sales/channels/page.meta.js +2 -0
  126. package/dist/modules/sales/backend/sales/channels/page.meta.js.map +2 -2
  127. package/dist/modules/sales/backend/sales/documents/[id]/page.js +6 -4
  128. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  129. package/dist/modules/sales/commands/documents.js +44 -0
  130. package/dist/modules/sales/commands/documents.js.map +2 -2
  131. package/dist/modules/sales/commands/returns.js +2 -27
  132. package/dist/modules/sales/commands/returns.js.map +2 -2
  133. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js +15 -0
  134. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js.map +7 -0
  135. package/dist/modules/sales/components/documents/AddressesSection.js +6 -2
  136. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  137. package/dist/modules/sales/components/documents/ItemsSection.js +1 -0
  138. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  139. package/dist/modules/sales/components/documents/LineItemDialog.js +9 -0
  140. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  141. package/dist/modules/sales/components/documents/SalesDocumentForm.js +51 -3
  142. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  143. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -8
  144. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  145. package/dist/modules/sales/components/documents/ShipmentDialog.js +59 -38
  146. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  147. package/dist/modules/sales/components/useSalesChannelsEnabled.js +44 -0
  148. package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +7 -0
  149. package/dist/modules/sales/lib/calculations.js +6 -0
  150. package/dist/modules/sales/lib/calculations.js.map +2 -2
  151. package/dist/modules/sales/lib/salesChannelsToggle.js +18 -0
  152. package/dist/modules/sales/lib/salesChannelsToggle.js.map +7 -0
  153. package/dist/modules/sales/lib/salesChannelsToggleId.js +5 -0
  154. package/dist/modules/sales/lib/salesChannelsToggleId.js.map +7 -0
  155. package/dist/modules/sales/lib/shipments/snapshots.js +27 -1
  156. package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
  157. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +22 -25
  158. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  159. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -14
  160. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  161. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +10 -29
  162. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +2 -2
  163. package/dist/modules/sync_excel/lib/adapters/customers.js +19 -20
  164. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  165. package/dist/modules/sync_excel/lib/parser.js +185 -0
  166. package/dist/modules/sync_excel/lib/parser.js.map +2 -2
  167. package/dist/modules/sync_excel/lib/upload-storage.js +20 -1
  168. package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
  169. package/generated/entities/gateway_session_initialization/index.ts +10 -0
  170. package/generated/entities.ids.generated.ts +1 -0
  171. package/generated/entity-fields-registry.ts +12 -0
  172. package/package.json +9 -8
  173. package/src/modules/api_keys/api/keys/route.ts +36 -13
  174. package/src/modules/api_keys/cli.ts +2 -3
  175. package/src/modules/attachments/api/file/[id]/route.ts +11 -7
  176. package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +11 -7
  177. package/src/modules/attachments/api/route.ts +26 -21
  178. package/src/modules/attachments/components/AttachmentContentPreview.tsx +9 -44
  179. package/src/modules/attachments/lib/reconcileOrganization.ts +188 -0
  180. package/src/modules/attachments/lib/requestScope.ts +27 -0
  181. package/src/modules/audit_logs/backend/audit-logs/page.tsx +12 -22
  182. package/src/modules/auth/api/autologin.ts +100 -0
  183. package/src/modules/auth/api/users/route.ts +11 -2
  184. package/src/modules/auth/cli.ts +3 -2
  185. package/src/modules/auth/lib/autologin.ts +36 -0
  186. package/src/modules/configs/i18n/de.json +5 -1
  187. package/src/modules/configs/i18n/en.json +5 -1
  188. package/src/modules/configs/i18n/es.json +5 -1
  189. package/src/modules/configs/i18n/pl.json +5 -1
  190. package/src/modules/configs/lib/upgrade-actions.ts +24 -0
  191. package/src/modules/customer_accounts/AGENTS.md +8 -6
  192. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +1 -0
  193. package/src/modules/customer_accounts/api/admin/roles/[id].ts +17 -3
  194. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +6 -1
  195. package/src/modules/customer_accounts/api/portal/users-invite.ts +6 -1
  196. package/src/modules/customer_accounts/services/customerInvitationService.ts +1 -0
  197. package/src/modules/customer_accounts/services/customerRbacService.ts +9 -2
  198. package/src/modules/customer_accounts/widgets/injection-table.ts +0 -2
  199. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +11 -1
  200. package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +6 -2
  201. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +39 -55
  202. package/src/modules/customers/components/detail/DealDetailTabs.tsx +24 -49
  203. package/src/modules/customers/components/detail/DetailTabsLayout.tsx +13 -24
  204. package/src/modules/customers/components/detail/MobilePersonDetail.tsx +25 -31
  205. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +29 -50
  206. package/src/modules/customers/components/detail/useDealsAccess.ts +19 -0
  207. package/src/modules/dashboards/api/roles/widgets/route.ts +22 -3
  208. package/src/modules/dashboards/api/users/widgets/route.ts +22 -3
  209. package/src/modules/dashboards/cli.ts +4 -9
  210. package/src/modules/dashboards/lib/widgetAssignmentScope.ts +18 -0
  211. package/src/modules/dictionaries/commands/entries.ts +7 -16
  212. package/src/modules/dictionaries/commands/entry-operations.ts +8 -18
  213. package/src/modules/dictionaries/commands/factory.ts +8 -2
  214. package/src/modules/dictionaries/fields/dictionary.tsx +55 -32
  215. package/src/modules/dictionaries/i18n/de.json +2 -0
  216. package/src/modules/dictionaries/i18n/en.json +2 -0
  217. package/src/modules/dictionaries/i18n/es.json +2 -0
  218. package/src/modules/dictionaries/i18n/pl.json +2 -0
  219. package/src/modules/directory/utils/organizationScope.ts +36 -2
  220. package/src/modules/entities/api/records.ts +5 -7
  221. package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +1 -0
  222. package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +1 -0
  223. package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +51 -49
  224. package/src/modules/entities/cli.ts +2 -1
  225. package/src/modules/entities/i18n/de.json +34 -0
  226. package/src/modules/entities/i18n/en.json +34 -0
  227. package/src/modules/entities/i18n/es.json +34 -0
  228. package/src/modules/entities/i18n/pl.json +34 -0
  229. package/src/modules/feature_toggles/api/check/boolean/route.ts +0 -15
  230. package/src/modules/feature_toggles/defaults.json +8 -0
  231. package/src/modules/integrations/backend/integrations/[id]/page.tsx +16 -59
  232. package/src/modules/payment_gateways/data/entities.ts +39 -0
  233. package/src/modules/payment_gateways/lib/gateway-service.ts +211 -40
  234. package/src/modules/payment_gateways/lib/session-idempotency.ts +142 -0
  235. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +236 -0
  236. package/src/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.ts +10 -0
  237. package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +14 -20
  238. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +16 -34
  239. package/src/modules/query_index/api/status.ts +2 -8
  240. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +27 -37
  241. package/src/modules/sales/backend/sales/channels/create/page.meta.ts +3 -0
  242. package/src/modules/sales/backend/sales/channels/create/page.tsx +7 -0
  243. package/src/modules/sales/backend/sales/channels/offers/page.meta.ts +3 -0
  244. package/src/modules/sales/backend/sales/channels/offers/page.tsx +7 -0
  245. package/src/modules/sales/backend/sales/channels/page.meta.ts +4 -0
  246. package/src/modules/sales/backend/sales/channels/page.tsx +7 -0
  247. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +7 -5
  248. package/src/modules/sales/commands/documents.ts +65 -0
  249. package/src/modules/sales/commands/returns.ts +2 -32
  250. package/src/modules/sales/components/SalesChannelsDisabledNotice.tsx +21 -0
  251. package/src/modules/sales/components/documents/AddressesSection.tsx +6 -2
  252. package/src/modules/sales/components/documents/ItemsSection.tsx +5 -0
  253. package/src/modules/sales/components/documents/LineItemDialog.tsx +10 -0
  254. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +53 -3
  255. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +10 -8
  256. package/src/modules/sales/components/documents/ShipmentDialog.tsx +65 -40
  257. package/src/modules/sales/components/useSalesChannelsEnabled.ts +59 -0
  258. package/src/modules/sales/i18n/de.json +5 -0
  259. package/src/modules/sales/i18n/en.json +5 -0
  260. package/src/modules/sales/i18n/es.json +5 -0
  261. package/src/modules/sales/i18n/pl.json +5 -0
  262. package/src/modules/sales/lib/calculations.ts +15 -0
  263. package/src/modules/sales/lib/salesChannelsToggle.ts +26 -0
  264. package/src/modules/sales/lib/salesChannelsToggleId.ts +1 -0
  265. package/src/modules/sales/lib/shipments/snapshots.ts +30 -0
  266. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +25 -34
  267. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +11 -17
  268. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +11 -28
  269. package/src/modules/sync_excel/lib/adapters/customers.ts +20 -22
  270. package/src/modules/sync_excel/lib/parser.ts +245 -0
  271. package/src/modules/sync_excel/lib/upload-storage.ts +23 -1
@@ -5,6 +5,7 @@ import { usePathname, useRouter, useSearchParams } from 'next/navigation'
5
5
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
6
  import { FormHeader } from '@open-mercato/ui/backend/forms'
7
7
  import { Button } from '@open-mercato/ui/primitives/button'
8
+ import { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'
8
9
  import { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
9
10
  import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
10
11
  import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
@@ -663,51 +664,40 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
663
664
  subtitle={t('resources.resources.detail.subtitle', 'Resource profile and activity')}
664
665
  />
665
666
 
666
- <div className="border-b">
667
- <nav className="flex flex-wrap items-center gap-5 text-sm" aria-label={t('resources.resources.tabs.label', 'Resource sections')}>
667
+ <Tabs
668
+ value={activeTab}
669
+ onValueChange={(value) => setActiveTab(value as 'details' | 'availability')}
670
+ variant="underline"
671
+ >
672
+ <TabsList
673
+ className="w-full flex-wrap"
674
+ aria-label={t('resources.resources.tabs.label', 'Resource sections')}
675
+ >
668
676
  {tabs.map((tab) => (
669
- <Button
670
- key={tab.id}
671
- type="button"
672
- role="tab"
673
- aria-selected={activeTab === tab.id}
674
- variant="ghost"
675
- size="sm"
676
- className={`relative -mb-px h-auto rounded-none border-b-2 px-0 py-2 font-medium ${
677
- activeTab === tab.id
678
- ? 'border-accent-indigo text-foreground'
679
- : 'border-transparent text-muted-foreground hover:text-foreground'
680
- }`}
681
- onClick={() => setActiveTab(tab.id as 'details' | 'availability')}
682
- >
677
+ <TabsTrigger key={tab.id} value={tab.id}>
683
678
  {tab.label}
684
- </Button>
679
+ </TabsTrigger>
685
680
  ))}
686
- </nav>
687
- </div>
681
+ </TabsList>
682
+ </Tabs>
688
683
 
689
684
  {activeTab === 'details' ? (
690
685
  <>
691
686
  <div className="rounded-lg border bg-card p-4">
692
687
  <div className="flex flex-wrap items-center justify-between gap-3">
693
- <div className="flex gap-2">
694
- {detailTabs.map((tab) => (
695
- <Button
696
- key={tab.id}
697
- type="button"
698
- variant="ghost"
699
- size="sm"
700
- className={`relative -mb-px h-auto rounded-none border-b-2 px-0 py-1 font-medium ${
701
- activeDetailTab === tab.id
702
- ? 'border-accent-indigo text-foreground'
703
- : 'border-transparent text-muted-foreground hover:text-foreground'
704
- }`}
705
- onClick={() => setActiveDetailTab(tab.id)}
706
- >
707
- {tab.label}
708
- </Button>
709
- ))}
710
- </div>
688
+ <Tabs
689
+ value={activeDetailTab}
690
+ onValueChange={(value) => setActiveDetailTab(value as 'notes' | 'activities')}
691
+ variant="underline"
692
+ >
693
+ <TabsList className="h-auto flex-wrap border-b-0">
694
+ {detailTabs.map((tab) => (
695
+ <TabsTrigger key={tab.id} value={tab.id}>
696
+ {tab.label}
697
+ </TabsTrigger>
698
+ ))}
699
+ </TabsList>
700
+ </Tabs>
711
701
  {sectionAction ? (
712
702
  <Button
713
703
  type="button"
@@ -1,4 +1,6 @@
1
1
  import React from 'react'
2
+ import type { RouteVisibilityContext } from '@open-mercato/shared/modules/registry'
3
+ import { isSalesChannelsEnabledForTenant } from '../../../../lib/salesChannelsToggle'
2
4
 
3
5
  const createIcon = React.createElement(
4
6
  'svg',
@@ -11,6 +13,7 @@ const createIcon = React.createElement(
11
13
  export const metadata = {
12
14
  requireAuth: true,
13
15
  requireFeatures: ['sales.channels.manage'],
16
+ visible: (ctx: RouteVisibilityContext) => isSalesChannelsEnabledForTenant(ctx.auth?.tenantId ?? null),
14
17
  pageTitle: 'Create channel',
15
18
  pageTitleKey: 'sales.channels.form.createTitle',
16
19
  pageGroup: 'Sales',
@@ -9,13 +9,20 @@ import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customF
9
9
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
10
10
  import { useT } from '@open-mercato/shared/lib/i18n/context'
11
11
  import { useChannelFields, buildChannelPayload, type ChannelFormValues } from '@open-mercato/core/modules/sales/components/channels/channelFormFields'
12
+ import { useSalesChannelsEnabled } from '@open-mercato/core/modules/sales/components/useSalesChannelsEnabled'
13
+ import { SalesChannelsDisabledNotice } from '@open-mercato/core/modules/sales/components/SalesChannelsDisabledNotice'
12
14
  import { E } from '#generated/entities.ids.generated'
13
15
 
14
16
  export default function CreateChannelPage() {
15
17
  const t = useT()
16
18
  const { fields, groups } = useChannelFields()
19
+ const { enabled: channelsEnabled, isLoading: channelsEnabledLoading } = useSalesChannelsEnabled()
17
20
  const router = useRouter()
18
21
 
22
+ if (!channelsEnabled && !channelsEnabledLoading) {
23
+ return <SalesChannelsDisabledNotice />
24
+ }
25
+
19
26
  return (
20
27
  <Page>
21
28
  <PageBody>
@@ -1,4 +1,6 @@
1
1
  import React from 'react'
2
+ import type { RouteVisibilityContext } from '@open-mercato/shared/modules/registry'
3
+ import { isSalesChannelsEnabledForTenant } from '../../../../lib/salesChannelsToggle'
2
4
 
3
5
  const globeIcon = React.createElement(
4
6
  'svg',
@@ -12,6 +14,7 @@ const globeIcon = React.createElement(
12
14
  export const metadata = {
13
15
  requireAuth: true,
14
16
  requireFeatures: ['sales.channels.manage'],
17
+ visible: (ctx: RouteVisibilityContext) => isSalesChannelsEnabledForTenant(ctx.auth?.tenantId ?? null),
15
18
  pageTitle: 'Sales channel offers',
16
19
  pageTitleKey: 'sales.channels.offers.listTitle',
17
20
  pageGroup: 'Sales',
@@ -18,6 +18,8 @@ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuarde
18
18
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
19
19
  import { useT } from '@open-mercato/shared/lib/i18n/context'
20
20
  import { mapOfferRow, renderOfferPriceSummary, type OfferRow } from '@open-mercato/core/modules/sales/components/channels/offerTableUtils'
21
+ import { useSalesChannelsEnabled } from '@open-mercato/core/modules/sales/components/useSalesChannelsEnabled'
22
+ import { SalesChannelsDisabledNotice } from '@open-mercato/core/modules/sales/components/SalesChannelsDisabledNotice'
21
23
  import { createLogger } from '@open-mercato/shared/lib/logger'
22
24
 
23
25
  const logger = createLogger('sales')
@@ -34,6 +36,7 @@ const SAVE_CONTEXT_ID = 'sales-channel-offers-list'
34
36
 
35
37
  export default function SalesChannelOffersListPage() {
36
38
  const t = useT()
39
+ const { enabled: channelsEnabled, isLoading: channelsEnabledLoading } = useSalesChannelsEnabled()
37
40
  const router = useRouter()
38
41
  const { runMutation, retryLastMutation } = useGuardedMutation<{
39
42
  formId: string
@@ -332,6 +335,10 @@ export default function SalesChannelOffersListPage() {
332
335
  </div>
333
336
  )
334
337
 
338
+ if (!channelsEnabled && !channelsEnabledLoading) {
339
+ return <SalesChannelsDisabledNotice />
340
+ }
341
+
335
342
  return (
336
343
  <Page>
337
344
  <PageBody>
@@ -1,6 +1,10 @@
1
+ import type { RouteVisibilityContext } from '@open-mercato/shared/modules/registry'
2
+ import { isSalesChannelsEnabledForTenant } from '../../../lib/salesChannelsToggle'
3
+
1
4
  export const metadata = {
2
5
  requireAuth: true,
3
6
  requireFeatures: ['sales.channels.manage'],
7
+ visible: (ctx: RouteVisibilityContext) => isSalesChannelsEnabledForTenant(ctx.auth?.tenantId ?? null),
4
8
  pageTitle: 'Sales channels',
5
9
  pageTitleKey: 'sales.channels.nav.title',
6
10
  pageGroup: 'Sales',
@@ -16,6 +16,8 @@ import { buildOptimisticLockHeader, extractOptimisticLockConflict } from '@open-
16
16
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
17
17
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
18
18
  import { useT } from '@open-mercato/shared/lib/i18n/context'
19
+ import { useSalesChannelsEnabled } from '../../../components/useSalesChannelsEnabled'
20
+ import { SalesChannelsDisabledNotice } from '../../../components/SalesChannelsDisabledNotice'
19
21
  import { createLogger } from '@open-mercato/shared/lib/logger'
20
22
 
21
23
  const logger = createLogger('sales')
@@ -42,6 +44,7 @@ const SAVE_CONTEXT_ID = 'sales-channels-list'
42
44
 
43
45
  export default function SalesChannelsPage() {
44
46
  const t = useT()
47
+ const { enabled: channelsEnabled, isLoading: channelsEnabledLoading } = useSalesChannelsEnabled()
45
48
  const router = useRouter()
46
49
  const { runMutation, retryLastMutation } = useGuardedMutation<{
47
50
  formId: string
@@ -190,6 +193,10 @@ export default function SalesChannelsPage() {
190
193
  }
191
194
  }, [handleRefresh, mutationContext, runMutation, t])
192
195
 
196
+ if (!channelsEnabled && !channelsEnabledLoading) {
197
+ return <SalesChannelsDisabledNotice />
198
+ }
199
+
193
200
  return (
194
201
  <Page>
195
202
  <PageBody>
@@ -69,6 +69,7 @@ import { readMarkdownPreferenceCookie, writeMarkdownPreferenceCookie } from '@op
69
69
  import { InjectionSpot, useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'
70
70
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
71
71
  import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
72
+ import { useSalesChannelsEnabled } from '@open-mercato/core/modules/sales/components/useSalesChannelsEnabled'
72
73
  import { createLogger } from '@open-mercato/shared/lib/logger'
73
74
 
74
75
  const logger = createLogger('sales')
@@ -1894,6 +1895,7 @@ export default function SalesDocumentDetailPage({
1894
1895
  includeAmountInMessageMetadata?: boolean
1895
1896
  }) {
1896
1897
  const t = useT()
1898
+ const { enabled: channelsEnabled } = useSalesChannelsEnabled()
1897
1899
  const router = useRouter()
1898
1900
  const searchParams = useSearchParams()
1899
1901
  const pathname = usePathname()
@@ -2578,11 +2580,11 @@ export default function SalesDocumentDetailPage({
2578
2580
  }, [record])
2579
2581
 
2580
2582
  React.useEffect(() => {
2581
- loadChannels().catch(() => {})
2583
+ if (channelsEnabled) loadChannels().catch(() => {})
2582
2584
  loadStatuses().catch(() => {})
2583
2585
  loadShippingMethods().catch(() => {})
2584
2586
  loadPaymentMethods().catch(() => {})
2585
- }, [loadChannels, loadPaymentMethods, loadShippingMethods, loadStatuses, scopeVersion])
2587
+ }, [channelsEnabled, loadChannels, loadPaymentMethods, loadShippingMethods, loadStatuses, scopeVersion])
2586
2588
 
2587
2589
  React.useEffect(() => {
2588
2590
  void refreshPaymentPresence()
@@ -3955,11 +3957,11 @@ export default function SalesDocumentDetailPage({
3955
3957
  emptyLabel: t('sales.documents.detail.empty', 'Not set'),
3956
3958
  type: 'email' as const,
3957
3959
  },
3958
- {
3959
- key: 'channel',
3960
+ ...(channelsEnabled ? [{
3961
+ key: 'channel' as const,
3960
3962
  title: t('sales.documents.detail.channel', 'Channel'),
3961
3963
  value: record?.channelId ?? null,
3962
- },
3964
+ }] : []),
3963
3965
  {
3964
3966
  key: 'status',
3965
3967
  title: t('sales.documents.detail.status', 'Status'),
@@ -125,6 +125,7 @@ import {
125
125
  type SalesLineCalculationResult,
126
126
  type SalesDocumentCalculationResult,
127
127
  } from "../lib/types";
128
+ import { loadShippedQuantityByLine } from "../lib/shipments/snapshots";
128
129
  import { resolveDictionaryEntryValue } from "../lib/dictionaries";
129
130
  import { resolveStatusEntryIdByValue } from "../lib/statusHelpers";
130
131
  import { SalesDocumentNumberGenerator } from "../services/salesDocumentNumberGenerator";
@@ -6521,6 +6522,69 @@ const quoteAdjustmentDeleteSchema = z.object({
6521
6522
  quoteId: z.string().uuid(),
6522
6523
  });
6523
6524
 
6525
+ const SHIPPED_QUANTITY_TOLERANCE = 1e-6;
6526
+
6527
+ const hasNumericChange = (
6528
+ next: number | null | undefined,
6529
+ previous: number | null | undefined,
6530
+ ): boolean => {
6531
+ if (next === undefined || next === null) return false;
6532
+ if (previous === undefined || previous === null) return true;
6533
+ return Math.abs(next - previous) > SHIPPED_QUANTITY_TOLERANCE;
6534
+ };
6535
+
6536
+ const hasUnitChange = (
6537
+ next: string | null | undefined,
6538
+ previous: string | null | undefined,
6539
+ ): boolean => {
6540
+ if (next === undefined || next === null) return false;
6541
+ const normalizedPrevious = (previous ?? "").trim().toLowerCase();
6542
+ if (!normalizedPrevious) return false;
6543
+ return next.trim().toLowerCase() !== normalizedPrevious;
6544
+ };
6545
+
6546
+ async function assertShippedOrderLineEditable(
6547
+ em: EntityManager,
6548
+ order: SalesOrder,
6549
+ existingSnapshot: SalesLineSnapshot | null,
6550
+ parsed: z.infer<typeof orderLineUpsertSchema>,
6551
+ ): Promise<void> {
6552
+ const lineId = existingSnapshot?.id;
6553
+ if (!existingSnapshot || !lineId) return;
6554
+ const shippedByLine = await loadShippedQuantityByLine(em, order.id, {
6555
+ tenantId: order.tenantId,
6556
+ organizationId: order.organizationId,
6557
+ });
6558
+ const shippedQuantity = shippedByLine.get(lineId) ?? 0;
6559
+ if (shippedQuantity <= 0) return;
6560
+
6561
+ const { translate } = await resolveTranslations();
6562
+ const nextQuantity = parsed.quantity ?? existingSnapshot.quantity;
6563
+ if (nextQuantity < shippedQuantity - SHIPPED_QUANTITY_TOLERANCE) {
6564
+ throw new CrudHttpError(409, {
6565
+ error: translate(
6566
+ "sales.documents.items.errorQuantityBelowShipped",
6567
+ "You cannot lower the quantity below the {{shipped}} already shipped.",
6568
+ { shipped: shippedQuantity },
6569
+ ),
6570
+ });
6571
+ }
6572
+
6573
+ const pricingChanged =
6574
+ hasNumericChange(parsed.unitPriceNet, existingSnapshot.unitPriceNet) ||
6575
+ hasNumericChange(parsed.unitPriceGross, existingSnapshot.unitPriceGross) ||
6576
+ hasNumericChange(parsed.taxRate, existingSnapshot.taxRate) ||
6577
+ hasUnitChange(parsed.quantityUnit, existingSnapshot.quantityUnit);
6578
+ if (pricingChanged) {
6579
+ throw new CrudHttpError(409, {
6580
+ error: translate(
6581
+ "sales.documents.items.errorPriceShipped",
6582
+ "You cannot change the price or unit of a line that has shipped items.",
6583
+ ),
6584
+ });
6585
+ }
6586
+ }
6587
+
6524
6588
  const orderLineUpsertCommand: CommandHandler<
6525
6589
  { body?: Record<string, unknown>; query?: Record<string, unknown> },
6526
6590
  { orderId: string; lineId: string }
@@ -6565,6 +6629,7 @@ const orderLineUpsertCommand: CommandHandler<
6565
6629
  const existingSnapshot = parsed.id
6566
6630
  ? (lineSnapshots.find((line) => line.id === parsed.id) ?? null)
6567
6631
  : null;
6632
+ await assertShippedOrderLineEditable(em, order, existingSnapshot, parsed);
6568
6633
  const priceMode =
6569
6634
  parsed.priceMode === "gross"
6570
6635
  ? "gross"
@@ -15,8 +15,8 @@ import type { SalesCalculationService } from '../services/salesCalculationServic
15
15
  import type { SalesAdjustmentDraft, SalesLineSnapshot, SalesDocumentCalculationResult } from '../lib/types'
16
16
  import { cloneJson, deriveLineNetFromGross, ensureOrganizationScope, ensureSameScope, ensureTenantScope, extractUndoPayload, toNumericString, enforceSalesDocumentOptimisticLock, SALES_RESOURCE_KIND_ORDER, SALES_RESOURCE_KIND_RETURN } from './shared'
17
17
  import { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'
18
- import { SalesOrder, SalesOrderAdjustment, SalesOrderLine, SalesReturn, SalesReturnLine, SalesShipment, SalesShipmentItem } from '../data/entities'
19
- import { coerceShipmentQuantity } from '../lib/shipments/snapshots'
18
+ import { SalesOrder, SalesOrderAdjustment, SalesOrderLine, SalesReturn, SalesReturnLine } from '../data/entities'
19
+ import { loadShippedQuantityByLine } from '../lib/shipments/snapshots'
20
20
  import { computeAvailableReturnQuantity } from '../lib/returnQuantity'
21
21
  import {
22
22
  returnCreateSchema,
@@ -581,36 +581,6 @@ async function restoreReturnEffects(
581
581
  return createdLines
582
582
  }
583
583
 
584
- async function loadShippedQuantityByLine(
585
- em: EntityManager,
586
- orderId: string,
587
- scope: { tenantId: string; organizationId: string },
588
- ): Promise<Map<string, number>> {
589
- const shipments = await findWithDecryption(
590
- em,
591
- SalesShipment,
592
- { order: orderId, deletedAt: null },
593
- {},
594
- scope,
595
- )
596
- const shippedByLine = new Map<string, number>()
597
- if (!shipments.length) return shippedByLine
598
- const items = await findWithDecryption(
599
- em,
600
- SalesShipmentItem,
601
- { shipment: { $in: shipments.map((shipment) => shipment.id) } },
602
- {},
603
- scope,
604
- )
605
- items.forEach((item) => {
606
- const orderLineId = typeof item.orderLine === 'string' ? item.orderLine : item.orderLine?.id ?? null
607
- if (!orderLineId) return
608
- const next = (shippedByLine.get(orderLineId) ?? 0) + coerceShipmentQuantity(item.quantity)
609
- shippedByLine.set(orderLineId, next)
610
- })
611
- return shippedByLine
612
- }
613
-
614
584
  function normalizeLinesInput(lines: ReturnCreateInput['lines']): ReturnLineInput[] {
615
585
  const seen = new Set<string>()
616
586
  const result: ReturnLineInput[] = []
@@ -0,0 +1,21 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
5
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
6
+
7
+ export function SalesChannelsDisabledNotice() {
8
+ const t = useT()
9
+ return (
10
+ <Page>
11
+ <PageBody>
12
+ <div className="rounded-lg border bg-muted/50 p-6 text-sm text-muted-foreground">
13
+ {t(
14
+ 'sales.channels.disabledNotice',
15
+ 'Sales channels are disabled for this tenant. Enable the "Sales Channels" feature toggle to manage them.',
16
+ )}
17
+ </div>
18
+ </PageBody>
19
+ </Page>
20
+ )
21
+ }
@@ -890,8 +890,12 @@ export function SalesDocumentAddressesSection({
890
890
  if (res?.result?.id) billingId = res.result.id
891
891
  }
892
892
 
893
- payload.shippingAddressSnapshot = shippingSnapshot ?? null
894
- payload.billingAddressSnapshot = billingSnapshot ?? null
893
+ if (shippingSnapshot || !shippingId) {
894
+ payload.shippingAddressSnapshot = shippingSnapshot ?? null
895
+ }
896
+ if (billingSnapshot || !billingId) {
897
+ payload.billingAddressSnapshot = billingSnapshot ?? null
898
+ }
895
899
  payload.shippingAddressId = shippingSnapshot ? null : shippingId
896
900
  payload.billingAddressId = billingSnapshot ? null : billingId
897
901
 
@@ -817,6 +817,11 @@ export function SalesDocumentItemsSection({
817
817
  organizationId={resolvedOrganizationId}
818
818
  tenantId={resolvedTenantId}
819
819
  initialLine={lineForEdit}
820
+ shippedQuantity={
821
+ lineForEdit
822
+ ? Math.max(0, shippedTotals.get(lineForEdit.id) ?? 0)
823
+ : 0
824
+ }
820
825
  onSaved={async () => {
821
826
  await loadItems();
822
827
  emitSalesDocumentTotalsRefresh({ documentId, kind });
@@ -278,6 +278,7 @@ type SalesLineDialogProps = {
278
278
  organizationId: string | null;
279
279
  tenantId: string | null;
280
280
  initialLine?: SalesLineRecord | null;
281
+ shippedQuantity?: number;
281
282
  onOpenChange: (open: boolean) => void;
282
283
  onSaved?: () => Promise<void> | void;
283
284
  };
@@ -472,6 +473,7 @@ export function LineItemDialog({
472
473
  organizationId,
473
474
  tenantId,
474
475
  initialLine,
476
+ shippedQuantity = 0,
475
477
  onOpenChange,
476
478
  onSaved,
477
479
  }: SalesLineDialogProps) {
@@ -1326,6 +1328,14 @@ export function LineItemDialog({
1326
1328
  },
1327
1329
  );
1328
1330
  }
1331
+ if (shippedQuantity > 0 && qtyNumber < shippedQuantity) {
1332
+ const message = t(
1333
+ "sales.documents.items.errorQuantityBelowShipped",
1334
+ "You cannot lower the quantity below the {{shipped}} already shipped.",
1335
+ { shipped: shippedQuantity },
1336
+ );
1337
+ throw createCrudFormError(message, { quantity: message });
1338
+ }
1329
1339
  const resolvedQuantityUnit = (() => {
1330
1340
  const entered = normalizeUnitCode(values.quantityUnit);
1331
1341
  if (isCustomLine) return entered;
@@ -57,6 +57,7 @@ import {
57
57
  type AddressValue,
58
58
  } from '@open-mercato/core/modules/customers/utils/addressFormat'
59
59
  import { AddressEditor, type AddressEditorDraft } from '@open-mercato/core/modules/customers/components/AddressEditor'
60
+ import { useSalesChannelsEnabled } from '../useSalesChannelsEnabled'
60
61
  import { createLogger } from '@open-mercato/shared/lib/logger'
61
62
 
62
63
  const logger = createLogger('sales')
@@ -168,6 +169,7 @@ function CustomerQuickCreate({ t, onCreated }: CustomerQuickCreateProps) {
168
169
  async (values: PersonFormValues) => {
169
170
  setSaving(true)
170
171
  try {
172
+ const addresses = Array.isArray(values.addresses) ? values.addresses : []
171
173
  const payload = buildPersonPayload(values, organizationId)
172
174
  const { result } = await createCrud<{ id?: string; entityId?: string }>('customers/people', payload, {
173
175
  errorMessage: t('sales.documents.form.customer.quick.error', 'Failed to create customer.'),
@@ -177,6 +179,51 @@ function CustomerQuickCreate({ t, onCreated }: CustomerQuickCreateProps) {
177
179
  (result && typeof result.id === 'string' && result.id) ||
178
180
  null
179
181
  if (!id) throw new Error('Missing customer id')
182
+ if (addresses.length) {
183
+ const normalize = (value?: string | null) => {
184
+ if (typeof value !== 'string') return undefined
185
+ const trimmed = value.trim()
186
+ return trimmed.length ? trimmed : undefined
187
+ }
188
+ for (const entry of addresses) {
189
+ const normalizedLine1 = normalize(entry.addressLine1)
190
+ if (!normalizedLine1) continue
191
+ const body: Record<string, unknown> = {
192
+ entityId: id,
193
+ ...(organizationId ? { organizationId } : {}),
194
+ addressLine1: normalizedLine1,
195
+ isPrimary: entry.isPrimary ?? false,
196
+ }
197
+ const name = normalize(entry.name)
198
+ if (name !== undefined) body.name = name
199
+ const purpose = normalize(entry.purpose)
200
+ if (purpose !== undefined) body.purpose = purpose
201
+ const line2 = normalize(entry.addressLine2)
202
+ if (line2 !== undefined) body.addressLine2 = line2
203
+ const buildingNumber = normalize(entry.buildingNumber)
204
+ if (buildingNumber !== undefined) body.buildingNumber = buildingNumber
205
+ const flatNumber = normalize(entry.flatNumber)
206
+ if (flatNumber !== undefined) body.flatNumber = flatNumber
207
+ const city = normalize(entry.city)
208
+ if (city !== undefined) body.city = city
209
+ const region = normalize(entry.region)
210
+ if (region !== undefined) body.region = region
211
+ const postalCode = normalize(entry.postalCode)
212
+ if (postalCode !== undefined) body.postalCode = postalCode
213
+ const country = normalize(entry.country)
214
+ if (country !== undefined) body.country = country.toUpperCase()
215
+ if (typeof entry.latitude === 'number') body.latitude = entry.latitude
216
+ if (typeof entry.longitude === 'number') body.longitude = entry.longitude
217
+ try {
218
+ await createCrud('customers/addresses', body)
219
+ } catch (addressErr) {
220
+ const message =
221
+ (addressErr instanceof Error && addressErr.message ? addressErr.message : null) ||
222
+ t('customers.people.detail.addresses.error')
223
+ flash(message, 'error')
224
+ }
225
+ }
226
+ }
180
227
  const displayName =
181
228
  typeof values.displayName === 'string' && values.displayName.trim().length
182
229
  ? values.displayName.trim()
@@ -758,6 +805,7 @@ function CustomerGroupComponent({ values, setValue, t, customers, setCustomers,
758
805
 
759
806
  export function SalesDocumentForm({ onCreated, isSubmitting = false, initialKind, inboxPreFill }: SalesDocumentFormProps) {
760
807
  const t = useT()
808
+ const { enabled: channelsEnabled } = useSalesChannelsEnabled()
761
809
  const [customers, setCustomers] = React.useState<CustomerOption[]>([])
762
810
  const [customerLoading, setCustomerLoading] = React.useState(false)
763
811
  const [channels, setChannels] = React.useState<ChannelOption[]>([])
@@ -1128,7 +1176,7 @@ export function SalesDocumentForm({ onCreated, isSubmitting = false, initialKind
1128
1176
  />
1129
1177
  ),
1130
1178
  },
1131
- {
1179
+ ...(channelsEnabled ? [{
1132
1180
  id: 'channelId',
1133
1181
  label: t('sales.documents.form.channel', 'Sales channel'),
1134
1182
  type: 'custom',
@@ -1150,7 +1198,7 @@ export function SalesDocumentForm({ onCreated, isSubmitting = false, initialKind
1150
1198
  selectedHintLabel={(id) => t('sales.documents.form.channel.selected', 'Selected channel: {{id}}', { id })}
1151
1199
  />
1152
1200
  ),
1153
- },
1201
+ } satisfies CrudField] : []),
1154
1202
  {
1155
1203
  id: 'shippingAddressSection',
1156
1204
  label: '',
@@ -1271,6 +1319,7 @@ export function SalesDocumentForm({ onCreated, isSubmitting = false, initialKind
1271
1319
  addressOptions,
1272
1320
  addressesError,
1273
1321
  addressesLoading,
1322
+ channelsEnabled,
1274
1323
  currencyLabels,
1275
1324
  fetchCurrencyOptions,
1276
1325
  loadAddresses,
@@ -1289,12 +1338,13 @@ export function SalesDocumentForm({ onCreated, isSubmitting = false, initialKind
1289
1338
  fields: [],
1290
1339
  component: (ctx) => <CustomerGroupComponent {...ctx} t={t} customers={customers} setCustomers={setCustomers} customerQuerySetter={customerQuerySetter} loadAddresses={loadAddresses} loadCustomers={loadCustomers} fetchCustomerEmail={fetchCustomerEmail} resetAddressFormState={resetAddressFormState} />,
1291
1340
  },
1292
- { id: 'channels-comments', title: '', column: 1, fields: ['channelId', 'comments'] },
1341
+ { id: 'channels-comments', title: '', column: 1, fields: channelsEnabled ? ['channelId', 'comments'] : ['comments'] },
1293
1342
  { id: 'currency', title: '', column: 2, fields: ['currencyCode'] },
1294
1343
  { id: 'shipping', title: '', column: 2, fields: ['shippingAddressSection'] },
1295
1344
  { id: 'billing', title: '', column: 2, fields: ['billingAddressSection'] },
1296
1345
  { id: 'custom', title: t('sales.documents.form.customFields', 'Custom fields'), column: 2, kind: 'customFields' },
1297
1346
  ], [
1347
+ channelsEnabled,
1298
1348
  customers,
1299
1349
  fetchCustomerEmail,
1300
1350
  loadAddresses,
@@ -24,6 +24,7 @@ import {
24
24
  normalizeDictionaryEntries,
25
25
  } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'
26
26
  import { SALES_DOCUMENT_NUMBER_COLUMN_META } from './salesDocumentsColumns'
27
+ import { useSalesChannelsEnabled } from '../useSalesChannelsEnabled'
27
28
  import { createLogger } from '@open-mercato/shared/lib/logger'
28
29
 
29
30
  const logger = createLogger('sales')
@@ -146,6 +147,7 @@ function normalizeNumberInput(value: unknown): number | null {
146
147
 
147
148
  export function SalesDocumentsTable({ kind }: { kind: SalesDocumentKind }) {
148
149
  const t = useT()
150
+ const { enabled: channelsEnabled } = useSalesChannelsEnabled()
149
151
  const router = useRouter()
150
152
  const scopeVersion = useOrganizationScopeVersion()
151
153
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
@@ -293,20 +295,20 @@ export function SalesDocumentsTable({ kind }: { kind: SalesDocumentKind }) {
293
295
  )
294
296
 
295
297
  React.useEffect(() => {
296
- loadChannelOptions().catch(() => {})
298
+ if (channelsEnabled) loadChannelOptions().catch(() => {})
297
299
  loadTagOptions().catch(() => {})
298
300
  loadCustomerOptions().catch(() => {})
299
301
  loadStatusMap().catch(() => setStatusMap({}))
300
- }, [loadChannelOptions, loadCustomerOptions, loadStatusMap, loadTagOptions, scopeVersion])
302
+ }, [channelsEnabled, loadChannelOptions, loadCustomerOptions, loadStatusMap, loadTagOptions, scopeVersion])
301
303
 
302
304
  const filters = React.useMemo<FilterDef[]>(() => [
303
- {
305
+ ...(channelsEnabled ? [{
304
306
  id: 'channelId',
305
307
  label: t('sales.documents.list.filters.channel', 'Channel'),
306
308
  type: 'select',
307
309
  options: channelOptions,
308
310
  loadOptions: loadChannelOptions,
309
- },
311
+ } satisfies FilterDef] : []),
310
312
  {
311
313
  id: 'date',
312
314
  label: t('sales.documents.list.filters.date', 'Date'),
@@ -363,7 +365,7 @@ export function SalesDocumentsTable({ kind }: { kind: SalesDocumentKind }) {
363
365
  formatValue: (val: string) => tagOptions.find((o) => o.value === val)?.label ?? val,
364
366
  formatDescription: (val: string) => tagOptions.find((o) => o.value === val)?.description ?? null,
365
367
  },
366
- ], [channelOptions, loadChannelOptions, customerOptions, loadCustomerOptions, loadTagOptions, tagOptions, t])
368
+ ], [channelsEnabled, channelOptions, loadChannelOptions, customerOptions, loadCustomerOptions, loadTagOptions, tagOptions, t])
367
369
 
368
370
  const queryParams = React.useMemo(() => {
369
371
  const params = new URLSearchParams()
@@ -615,7 +617,7 @@ export function SalesDocumentsTable({ kind }: { kind: SalesDocumentKind }) {
615
617
  ),
616
618
  enableSorting: false,
617
619
  },
618
- {
620
+ ...(channelsEnabled ? [{
619
621
  accessorKey: 'channelId',
620
622
  header: t('sales.documents.list.table.channel', 'Channel'),
621
623
  cell: ({ row }) => {
@@ -627,7 +629,7 @@ export function SalesDocumentsTable({ kind }: { kind: SalesDocumentKind }) {
627
629
  )
628
630
  },
629
631
  enableSorting: false,
630
- },
632
+ } satisfies ColumnDef<SalesDocumentRow>] : []),
631
633
  {
632
634
  id: 'lineItemCount',
633
635
  accessorKey: 'lineItemCount',
@@ -661,7 +663,7 @@ export function SalesDocumentsTable({ kind }: { kind: SalesDocumentKind }) {
661
663
  ? <span className="text-xs text-muted-foreground">{new Date(row.original.date).toLocaleString()}</span>
662
664
  : <span className="text-xs text-muted-foreground">—</span>,
663
665
  },
664
- ], [channelOptions, kind, statusMap, t])
666
+ ], [channelsEnabled, channelOptions, kind, statusMap, t])
665
667
 
666
668
  const emptyLabel = kind === 'order'
667
669
  ? t('sales.documents.list.table.emptyOrders', 'No orders yet.')