@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.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/gateway_session_initialization/index.js +23 -0
- package/dist/generated/entities/gateway_session_initialization/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +12 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/api_keys/api/keys/route.js +30 -13
- package/dist/modules/api_keys/api/keys/route.js.map +2 -2
- package/dist/modules/api_keys/cli.js +2 -1
- package/dist/modules/api_keys/cli.js.map +2 -2
- package/dist/modules/attachments/api/file/[id]/route.js +9 -7
- package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +9 -7
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
- package/dist/modules/attachments/api/route.js +21 -17
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentContentPreview.js +6 -35
- package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
- package/dist/modules/attachments/lib/reconcileOrganization.js +110 -0
- package/dist/modules/attachments/lib/reconcileOrganization.js.map +7 -0
- package/dist/modules/attachments/lib/requestScope.js +10 -0
- package/dist/modules/attachments/lib/requestScope.js.map +7 -0
- package/dist/modules/audit_logs/backend/audit-logs/page.js +14 -24
- package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
- package/dist/modules/auth/api/autologin.js +85 -0
- package/dist/modules/auth/api/autologin.js.map +7 -0
- package/dist/modules/auth/api/users/route.js +6 -2
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/cli.js +3 -2
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/lib/autologin.js +19 -0
- package/dist/modules/auth/lib/autologin.js.map +7 -0
- package/dist/modules/configs/lib/upgrade-actions.js +22 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +1 -0
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id].js +17 -3
- package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +6 -1
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/users-invite.js +6 -1
- package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerInvitationService.js +1 -0
- package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerRbacService.js +6 -1
- package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection-table.js +0 -2
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +7 -1
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js +37 -43
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/DealDetailTabs.js +23 -35
- package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/DetailTabsLayout.js +6 -21
- package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
- package/dist/modules/customers/components/detail/MobilePersonDetail.js +26 -32
- package/dist/modules/customers/components/detail/MobilePersonDetail.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +29 -39
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/useDealsAccess.js +16 -0
- package/dist/modules/customers/components/detail/useDealsAccess.js.map +7 -0
- package/dist/modules/dashboards/api/roles/widgets/route.js +22 -3
- package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/api/users/widgets/route.js +22 -3
- package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/cli.js +4 -3
- package/dist/modules/dashboards/cli.js.map +2 -2
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js +9 -1
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
- package/dist/modules/dictionaries/commands/entries.js +7 -14
- package/dist/modules/dictionaries/commands/entries.js.map +2 -2
- package/dist/modules/dictionaries/commands/entry-operations.js +7 -16
- package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
- package/dist/modules/dictionaries/commands/factory.js +9 -3
- package/dist/modules/dictionaries/commands/factory.js.map +2 -2
- package/dist/modules/dictionaries/fields/dictionary.js +56 -41
- package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
- package/dist/modules/directory/utils/organizationScope.js +10 -3
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/entities/api/records.js +5 -4
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +1 -0
- package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js +49 -49
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js +1 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
- package/dist/modules/entities/cli.js +2 -1
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/feature_toggles/api/check/boolean/route.js +0 -14
- package/dist/modules/feature_toggles/api/check/boolean/route.js.map +2 -2
- package/dist/modules/feature_toggles/defaults.json +8 -0
- package/dist/modules/integrations/backend/integrations/[id]/page.js +9 -80
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/payment_gateways/data/entities.js +45 -0
- package/dist/modules/payment_gateways/data/entities.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +173 -38
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +3 -3
- package/dist/modules/payment_gateways/lib/session-idempotency.js +104 -0
- package/dist/modules/payment_gateways/lib/session-idempotency.js.map +7 -0
- package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js +11 -0
- package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js.map +7 -0
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +16 -14
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +18 -30
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/query_index/api/status.js +2 -8
- package/dist/modules/query_index/api/status.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -24
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/create/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/create/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/create/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/create/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +6 -4
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +44 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +2 -27
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/components/SalesChannelsDisabledNotice.js +15 -0
- package/dist/modules/sales/components/SalesChannelsDisabledNotice.js.map +7 -0
- package/dist/modules/sales/components/documents/AddressesSection.js +6 -2
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +1 -0
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +9 -0
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +51 -3
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -8
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +59 -38
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/components/useSalesChannelsEnabled.js +44 -0
- package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +7 -0
- package/dist/modules/sales/lib/calculations.js +6 -0
- package/dist/modules/sales/lib/calculations.js.map +2 -2
- package/dist/modules/sales/lib/salesChannelsToggle.js +18 -0
- package/dist/modules/sales/lib/salesChannelsToggle.js.map +7 -0
- package/dist/modules/sales/lib/salesChannelsToggleId.js +5 -0
- package/dist/modules/sales/lib/salesChannelsToggleId.js.map +7 -0
- package/dist/modules/sales/lib/shipments/snapshots.js +27 -1
- package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +22 -25
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -14
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +10 -29
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +19 -20
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/dist/modules/sync_excel/lib/parser.js +185 -0
- package/dist/modules/sync_excel/lib/parser.js.map +2 -2
- package/dist/modules/sync_excel/lib/upload-storage.js +20 -1
- package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
- package/generated/entities/gateway_session_initialization/index.ts +10 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +12 -0
- package/package.json +9 -8
- package/src/modules/api_keys/api/keys/route.ts +36 -13
- package/src/modules/api_keys/cli.ts +2 -3
- package/src/modules/attachments/api/file/[id]/route.ts +11 -7
- package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +11 -7
- package/src/modules/attachments/api/route.ts +26 -21
- package/src/modules/attachments/components/AttachmentContentPreview.tsx +9 -44
- package/src/modules/attachments/lib/reconcileOrganization.ts +188 -0
- package/src/modules/attachments/lib/requestScope.ts +27 -0
- package/src/modules/audit_logs/backend/audit-logs/page.tsx +12 -22
- package/src/modules/auth/api/autologin.ts +100 -0
- package/src/modules/auth/api/users/route.ts +11 -2
- package/src/modules/auth/cli.ts +3 -2
- package/src/modules/auth/lib/autologin.ts +36 -0
- package/src/modules/configs/i18n/de.json +5 -1
- package/src/modules/configs/i18n/en.json +5 -1
- package/src/modules/configs/i18n/es.json +5 -1
- package/src/modules/configs/i18n/pl.json +5 -1
- package/src/modules/configs/lib/upgrade-actions.ts +24 -0
- package/src/modules/customer_accounts/AGENTS.md +8 -6
- package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +1 -0
- package/src/modules/customer_accounts/api/admin/roles/[id].ts +17 -3
- package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +6 -1
- package/src/modules/customer_accounts/api/portal/users-invite.ts +6 -1
- package/src/modules/customer_accounts/services/customerInvitationService.ts +1 -0
- package/src/modules/customer_accounts/services/customerRbacService.ts +9 -2
- package/src/modules/customer_accounts/widgets/injection-table.ts +0 -2
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +11 -1
- package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +6 -2
- package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +39 -55
- package/src/modules/customers/components/detail/DealDetailTabs.tsx +24 -49
- package/src/modules/customers/components/detail/DetailTabsLayout.tsx +13 -24
- package/src/modules/customers/components/detail/MobilePersonDetail.tsx +25 -31
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +29 -50
- package/src/modules/customers/components/detail/useDealsAccess.ts +19 -0
- package/src/modules/dashboards/api/roles/widgets/route.ts +22 -3
- package/src/modules/dashboards/api/users/widgets/route.ts +22 -3
- package/src/modules/dashboards/cli.ts +4 -9
- package/src/modules/dashboards/lib/widgetAssignmentScope.ts +18 -0
- package/src/modules/dictionaries/commands/entries.ts +7 -16
- package/src/modules/dictionaries/commands/entry-operations.ts +8 -18
- package/src/modules/dictionaries/commands/factory.ts +8 -2
- package/src/modules/dictionaries/fields/dictionary.tsx +55 -32
- package/src/modules/dictionaries/i18n/de.json +2 -0
- package/src/modules/dictionaries/i18n/en.json +2 -0
- package/src/modules/dictionaries/i18n/es.json +2 -0
- package/src/modules/dictionaries/i18n/pl.json +2 -0
- package/src/modules/directory/utils/organizationScope.ts +36 -2
- package/src/modules/entities/api/records.ts +5 -7
- package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +1 -0
- package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +1 -0
- package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +51 -49
- package/src/modules/entities/cli.ts +2 -1
- package/src/modules/entities/i18n/de.json +34 -0
- package/src/modules/entities/i18n/en.json +34 -0
- package/src/modules/entities/i18n/es.json +34 -0
- package/src/modules/entities/i18n/pl.json +34 -0
- package/src/modules/feature_toggles/api/check/boolean/route.ts +0 -15
- package/src/modules/feature_toggles/defaults.json +8 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +16 -59
- package/src/modules/payment_gateways/data/entities.ts +39 -0
- package/src/modules/payment_gateways/lib/gateway-service.ts +211 -40
- package/src/modules/payment_gateways/lib/session-idempotency.ts +142 -0
- package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +236 -0
- package/src/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.ts +10 -0
- package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +14 -20
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +16 -34
- package/src/modules/query_index/api/status.ts +2 -8
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +27 -37
- package/src/modules/sales/backend/sales/channels/create/page.meta.ts +3 -0
- package/src/modules/sales/backend/sales/channels/create/page.tsx +7 -0
- package/src/modules/sales/backend/sales/channels/offers/page.meta.ts +3 -0
- package/src/modules/sales/backend/sales/channels/offers/page.tsx +7 -0
- package/src/modules/sales/backend/sales/channels/page.meta.ts +4 -0
- package/src/modules/sales/backend/sales/channels/page.tsx +7 -0
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +7 -5
- package/src/modules/sales/commands/documents.ts +65 -0
- package/src/modules/sales/commands/returns.ts +2 -32
- package/src/modules/sales/components/SalesChannelsDisabledNotice.tsx +21 -0
- package/src/modules/sales/components/documents/AddressesSection.tsx +6 -2
- package/src/modules/sales/components/documents/ItemsSection.tsx +5 -0
- package/src/modules/sales/components/documents/LineItemDialog.tsx +10 -0
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +53 -3
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +10 -8
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +65 -40
- package/src/modules/sales/components/useSalesChannelsEnabled.ts +59 -0
- package/src/modules/sales/i18n/de.json +5 -0
- package/src/modules/sales/i18n/en.json +5 -0
- package/src/modules/sales/i18n/es.json +5 -0
- package/src/modules/sales/i18n/pl.json +5 -0
- package/src/modules/sales/lib/calculations.ts +15 -0
- package/src/modules/sales/lib/salesChannelsToggle.ts +26 -0
- package/src/modules/sales/lib/salesChannelsToggleId.ts +1 -0
- package/src/modules/sales/lib/shipments/snapshots.ts +30 -0
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +25 -34
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +11 -17
- package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +11 -28
- package/src/modules/sync_excel/lib/adapters/customers.ts +20 -22
- package/src/modules/sync_excel/lib/parser.ts +245 -0
- 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
|
-
<
|
|
667
|
-
|
|
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
|
-
<
|
|
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
|
-
</
|
|
679
|
+
</TabsTrigger>
|
|
685
680
|
))}
|
|
686
|
-
</
|
|
687
|
-
</
|
|
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
|
-
<
|
|
694
|
-
{
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
|
19
|
-
import {
|
|
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
|
-
|
|
894
|
-
|
|
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.')
|