@open-mercato/core 0.6.6-develop.6317.1.b3be10ab84 → 0.6.6-develop.6331.1.a33b8e99b7
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 +1 -1
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js +3 -0
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
- package/dist/modules/auth/api/roles/acl/route.js +2 -2
- package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
- package/dist/modules/auth/api/users/acl/route.js +2 -2
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/backend/roles/[id]/edit/page.js +12 -0
- package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +12 -0
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/data/entities.js +2 -2
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/business_rules/api/rules/route.js +3 -3
- package/dist/modules/business_rules/api/rules/route.js.map +2 -2
- package/dist/modules/business_rules/api/sets/route.js +3 -3
- package/dist/modules/business_rules/api/sets/route.js.map +2 -2
- package/dist/modules/business_rules/backend/rules/[id]/page.js +12 -1
- package/dist/modules/business_rules/backend/rules/[id]/page.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/[id]/page.js +12 -1
- package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
- package/dist/modules/catalog/api/product-media/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +12 -0
- package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +15 -0
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +12 -1
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
- package/dist/modules/catalog/components/PriceKindSettings.js +13 -3
- package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
- package/dist/modules/catalog/lib/bulkDelete.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/[id]/page.js +15 -3
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +2 -1
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +12 -1
- package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +3 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id].js +3 -3
- package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users/[id].js +3 -3
- package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +7 -12
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +5 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -1
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js +2 -1
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js.map +2 -2
- package/dist/modules/customers/api/addresses/route.js +4 -2
- package/dist/modules/customers/api/addresses/route.js.map +2 -2
- package/dist/modules/customers/api/tags/route.js +8 -3
- package/dist/modules/customers/api/tags/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +13 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +24 -9
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js +53 -23
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js +13 -3
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +19 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +13 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +5 -5
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/commands/pipeline-stages.js +24 -2
- package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
- package/dist/modules/customers/commands/pipelines.js +24 -2
- package/dist/modules/customers/commands/pipelines.js.map +2 -2
- package/dist/modules/customers/components/detail/AddressesSection.js +46 -29
- package/dist/modules/customers/components/detail/AddressesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +5 -1
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/EntityTagsDialog.js +76 -67
- package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +3 -3
- package/dist/modules/customers/components/detail/notesAdapter.js +25 -18
- package/dist/modules/customers/components/detail/notesAdapter.js.map +2 -2
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +4 -3
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
- package/dist/modules/data_sync/api/runs.js +1 -0
- package/dist/modules/data_sync/api/runs.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +38 -19
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +1 -1
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +35 -16
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/data/validators.js +1 -0
- package/dist/modules/data_sync/data/validators.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-run-service.js +20 -0
- package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-schedule-service.js +19 -5
- package/dist/modules/data_sync/lib/sync-schedule-service.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +3 -3
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/route.js +3 -3
- package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js +12 -1
- package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +12 -0
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +12 -0
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
- package/dist/modules/entities/api/entities.js +2 -2
- package/dist/modules/entities/api/entities.js.map +2 -2
- package/dist/modules/entities/api/records.js +18 -6
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +17 -10
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
- package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js.map +2 -2
- package/dist/modules/feature_toggles/data/entities.js +1 -1
- package/dist/modules/feature_toggles/data/entities.js.map +2 -2
- package/dist/modules/feature_toggles/data/validators.js +2 -1
- package/dist/modules/feature_toggles/data/validators.js.map +2 -2
- package/dist/modules/feature_toggles/lib/queries.js +2 -1
- package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
- package/dist/modules/inbox_ops/api/settings/route.js +2 -2
- package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
- package/dist/modules/messages/api/unread-count/route.js +32 -0
- package/dist/modules/messages/api/unread-count/route.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +13 -5
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +12 -1
- package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +12 -1
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/resources/components/detail/notesAdapter.js +25 -18
- package/dist/modules/resources/components/detail/notesAdapter.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +14 -1
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +11 -10
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +6 -1
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +3 -3
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/commands/shared.js +3 -3
- package/dist/modules/sales/commands/shared.js.map +2 -2
- package/dist/modules/sales/commands/shipments.js +6 -1
- package/dist/modules/sales/commands/shipments.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentDialog.js +6 -3
- package/dist/modules/sales/components/documents/PaymentDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentsSection.js +7 -3
- package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +6 -3
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentsSection.js +7 -3
- package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
- package/dist/modules/sales/di.js +39 -3
- package/dist/modules/sales/di.js.map +2 -2
- package/dist/modules/sales/setup.js +2 -0
- package/dist/modules/sales/setup.js.map +2 -2
- package/dist/modules/shipping_carriers/i18n/en.js +11 -1
- package/dist/modules/shipping_carriers/i18n/en.js.map +2 -2
- package/dist/modules/shipping_carriers/i18n/pl.js +11 -1
- package/dist/modules/shipping_carriers/i18n/pl.js.map +2 -2
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js +25 -0
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js.map +7 -0
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js +3 -17
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js.map +2 -2
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +12 -1
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +12 -1
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +12 -1
- package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -1
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/commands/job-histories.js +3 -3
- package/dist/modules/staff/commands/job-histories.js.map +2 -2
- package/dist/modules/staff/components/detail/addressesAdapter.js +33 -24
- package/dist/modules/staff/components/detail/addressesAdapter.js.map +2 -2
- package/dist/modules/staff/components/detail/notesAdapter.js +25 -18
- package/dist/modules/staff/components/detail/notesAdapter.js.map +2 -2
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js +37 -0
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +7 -1
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +19 -14
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
- package/dist/modules/workflows/api/definitions/[id]/route.js +3 -3
- package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.js +12 -1
- package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +11 -1
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +3 -0
- package/src/modules/auth/api/roles/acl/route.ts +2 -2
- package/src/modules/auth/api/users/acl/route.ts +2 -2
- package/src/modules/auth/backend/roles/[id]/edit/page.tsx +17 -0
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +17 -0
- package/src/modules/auth/data/entities.ts +2 -2
- package/src/modules/business_rules/api/rules/route.ts +3 -3
- package/src/modules/business_rules/api/sets/route.ts +3 -3
- package/src/modules/business_rules/backend/rules/[id]/page.tsx +17 -1
- package/src/modules/business_rules/backend/sets/[id]/page.tsx +18 -1
- package/src/modules/catalog/api/product-media/route.ts +4 -0
- package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +17 -0
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +21 -0
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +16 -1
- package/src/modules/catalog/components/PriceKindSettings.tsx +15 -7
- package/src/modules/catalog/lib/bulkDelete.ts +6 -0
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +20 -3
- package/src/modules/currencies/backend/currencies/page.tsx +2 -1
- package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +17 -1
- package/src/modules/currencies/backend/exchange-rates/page.tsx +3 -2
- package/src/modules/customer_accounts/api/admin/roles/[id].ts +3 -3
- package/src/modules/customer_accounts/api/admin/users/[id].ts +3 -3
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +7 -10
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +6 -7
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +3 -2
- package/src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx +3 -2
- package/src/modules/customers/api/addresses/route.ts +2 -0
- package/src/modules/customers/api/tags/route.ts +6 -1
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +19 -2
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +29 -8
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.ts +47 -20
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.ts +13 -3
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +21 -1
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -2
- package/src/modules/customers/commands/interactions.ts +5 -5
- package/src/modules/customers/commands/pipeline-stages.ts +26 -2
- package/src/modules/customers/commands/pipelines.ts +26 -2
- package/src/modules/customers/components/detail/AddressesSection.tsx +50 -28
- package/src/modules/customers/components/detail/DealForm.tsx +12 -0
- package/src/modules/customers/components/detail/EntityTagsDialog.tsx +89 -71
- package/src/modules/customers/components/detail/notesAdapter.ts +29 -18
- package/src/modules/customers/i18n/de.json +2 -0
- package/src/modules/customers/i18n/en.json +2 -0
- package/src/modules/customers/i18n/es.json +2 -0
- package/src/modules/customers/i18n/pl.json +2 -0
- package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +6 -3
- package/src/modules/data_sync/api/runs.ts +1 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +38 -20
- package/src/modules/data_sync/api/schedules/route.ts +1 -1
- package/src/modules/data_sync/backend/data-sync/page.tsx +35 -16
- package/src/modules/data_sync/data/validators.ts +1 -0
- package/src/modules/data_sync/i18n/de.json +1 -0
- package/src/modules/data_sync/i18n/en.json +1 -0
- package/src/modules/data_sync/i18n/es.json +1 -0
- package/src/modules/data_sync/i18n/pl.json +1 -0
- package/src/modules/data_sync/lib/sync-run-service.ts +23 -0
- package/src/modules/data_sync/lib/sync-schedule-service.ts +30 -5
- package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +3 -3
- package/src/modules/dictionaries/api/[dictionaryId]/route.ts +3 -3
- package/src/modules/dictionaries/components/DictionariesManager.tsx +21 -1
- package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +2 -0
- package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +17 -0
- package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +17 -0
- package/src/modules/entities/api/entities.ts +2 -2
- package/src/modules/entities/api/records.ts +24 -6
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +25 -12
- package/src/modules/feature_toggles/api/overrides/route.ts +2 -2
- package/src/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.tsx +12 -0
- package/src/modules/feature_toggles/data/entities.ts +1 -1
- package/src/modules/feature_toggles/data/validators.ts +1 -0
- package/src/modules/feature_toggles/lib/queries.ts +1 -0
- package/src/modules/inbox_ops/api/settings/route.ts +2 -2
- package/src/modules/messages/api/unread-count/route.ts +40 -0
- package/src/modules/query_index/components/QueryIndexesTable.tsx +17 -15
- package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +17 -1
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +17 -1
- package/src/modules/resources/components/detail/notesAdapter.ts +29 -18
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +17 -1
- package/src/modules/sales/commands/documents.ts +11 -10
- package/src/modules/sales/commands/payments.ts +11 -0
- package/src/modules/sales/commands/returns.ts +3 -3
- package/src/modules/sales/commands/shared.ts +10 -4
- package/src/modules/sales/commands/shipments.ts +14 -0
- package/src/modules/sales/components/documents/PaymentDialog.tsx +7 -3
- package/src/modules/sales/components/documents/PaymentsSection.tsx +8 -3
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +7 -3
- package/src/modules/sales/components/documents/ShipmentsSection.tsx +8 -3
- package/src/modules/sales/di.ts +68 -9
- package/src/modules/sales/setup.ts +2 -0
- package/src/modules/shipping_carriers/i18n/de.json +11 -1
- package/src/modules/shipping_carriers/i18n/en.json +11 -1
- package/src/modules/shipping_carriers/i18n/en.ts +10 -0
- package/src/modules/shipping_carriers/i18n/es.json +11 -1
- package/src/modules/shipping_carriers/i18n/pl.json +11 -1
- package/src/modules/shipping_carriers/i18n/pl.ts +10 -0
- package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.tsx +35 -0
- package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.tsx +3 -22
- package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +17 -1
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +17 -1
- package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +17 -1
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +17 -1
- package/src/modules/staff/commands/job-histories.ts +3 -3
- package/src/modules/staff/components/detail/addressesAdapter.ts +40 -23
- package/src/modules/staff/components/detail/notesAdapter.ts +28 -18
- package/src/modules/translations/api/[entityType]/[entityId]/route.ts +74 -0
- package/src/modules/translations/components/TranslationManager.tsx +14 -1
- package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +30 -13
- package/src/modules/workflows/api/definitions/[id]/route.ts +3 -3
- package/src/modules/workflows/backend/definitions/[id]/page.tsx +18 -1
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +19 -1
|
@@ -5,7 +5,7 @@ import type { CommandBus } from '@open-mercato/shared/lib/commands'
|
|
|
5
5
|
import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
|
|
6
6
|
import { getOverrides } from '../../lib/queries'
|
|
7
7
|
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
8
|
-
import {
|
|
8
|
+
import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
|
|
9
9
|
import {
|
|
10
10
|
runCrudMutationGuardAfterSuccess,
|
|
11
11
|
validateCrudMutationGuard,
|
|
@@ -100,7 +100,7 @@ export async function PUT(req: Request) {
|
|
|
100
100
|
toggle: { id: parsed.data.toggleId },
|
|
101
101
|
})
|
|
102
102
|
if (existingOverride) {
|
|
103
|
-
|
|
103
|
+
await enforceCommandOptimisticLockWithGuards(ctx.container, {
|
|
104
104
|
resourceKind: 'feature_toggles.feature_toggle_override',
|
|
105
105
|
resourceId: existingOverride.id,
|
|
106
106
|
current: existingOverride.updatedAt ?? null,
|
|
@@ -57,6 +57,18 @@ export default function EditFeatureTogglePage({ params }: { params?: { id?: stri
|
|
|
57
57
|
)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// record_locks scope decision (Phase 6): the global feature toggle is a
|
|
61
|
+
// NON-TENANT, superadmin-only entity. record_locks enrichment (presence,
|
|
62
|
+
// acquire/heartbeat, conflict upgrade) is tenant-scoped — its decorator skips
|
|
63
|
+
// enrichment when no tenant/resource resolves — so a global (null-tenant) record
|
|
64
|
+
// cannot participate in record_locks. This surface therefore stays OSS-floor
|
|
65
|
+
// guarded ONLY: the CrudForm auto-derives the optimistic-lock header from
|
|
66
|
+
// `optimisticLockUpdatedAt`, the route enforces it via the OSS floor, and a stale
|
|
67
|
+
// save surfaces the unified conflict bar. No `backend:record:current` presence is
|
|
68
|
+
// mounted: it would never resolve a tenant-scoped lock, and this is a
|
|
69
|
+
// single-superadmin surface with no concurrent-editor expectation. Per-tenant
|
|
70
|
+
// override values (`api/global/[id]/override`) ARE tenant-scoped and are enabled
|
|
71
|
+
// in Phase 6b.
|
|
60
72
|
return (
|
|
61
73
|
<Page>
|
|
62
74
|
<PageBody>
|
|
@@ -67,7 +67,7 @@ export class FeatureToggleOverride {
|
|
|
67
67
|
@Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
|
|
68
68
|
createdAt: Date = new Date()
|
|
69
69
|
|
|
70
|
-
@Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
|
|
70
|
+
@Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })
|
|
71
71
|
updatedAt: Date = new Date()
|
|
72
72
|
|
|
73
73
|
@Property({ name: 'value', type: 'jsonb' })
|
|
@@ -104,6 +104,7 @@ export const overrideListResponseSchema = z.object({
|
|
|
104
104
|
name: z.string(),
|
|
105
105
|
category: z.string(),
|
|
106
106
|
isOverride: z.boolean(),
|
|
107
|
+
updatedAt: z.string().nullable().optional(),
|
|
107
108
|
});
|
|
108
109
|
|
|
109
110
|
export type OverrideListResponse = z.infer<typeof overrideListResponseSchema>
|
|
@@ -2,7 +2,7 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
3
3
|
import { runWithCacheTenant } from '@open-mercato/cache'
|
|
4
4
|
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
5
|
-
import {
|
|
5
|
+
import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
|
|
6
6
|
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
7
7
|
import { InboxSettings } from '../../data/entities'
|
|
8
8
|
import { updateSettingsSchema } from '../../data/validators'
|
|
@@ -104,7 +104,7 @@ export async function PATCH(req: Request) {
|
|
|
104
104
|
// Optimistic lock: refuse a stale overwrite when two tabs edit the same inbox
|
|
105
105
|
// settings record. Strictly additive — a no-op without the expected-version header.
|
|
106
106
|
try {
|
|
107
|
-
|
|
107
|
+
await enforceCommandOptimisticLockWithGuards(ctx.container, {
|
|
108
108
|
resourceKind: 'inbox_ops.settings',
|
|
109
109
|
resourceId: settings.id,
|
|
110
110
|
current: settings.updatedAt ?? null,
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
2
|
import { type Kysely, sql } from 'kysely'
|
|
3
|
+
import { runWithCacheTenant } from '@open-mercato/cache'
|
|
4
|
+
import {
|
|
5
|
+
buildCollectionTags,
|
|
6
|
+
isCrudCacheEnabled,
|
|
7
|
+
resolveCrudCache,
|
|
8
|
+
} from '@open-mercato/shared/lib/crud/cache'
|
|
3
9
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'
|
|
4
10
|
import { resolveMessageContext } from '../../lib/routeHelpers'
|
|
5
11
|
import { unreadCountResponseSchema } from '../openapi'
|
|
@@ -8,6 +14,16 @@ export const metadata = {
|
|
|
8
14
|
GET: { requireAuth: true },
|
|
9
15
|
}
|
|
10
16
|
|
|
17
|
+
const UNREAD_COUNT_RESOURCE = 'messages.message'
|
|
18
|
+
const UNREAD_COUNT_TTL_MS = 10_000
|
|
19
|
+
|
|
20
|
+
function buildUnreadCountCacheKey(params: {
|
|
21
|
+
userId: string
|
|
22
|
+
orgId: string | null
|
|
23
|
+
}): string {
|
|
24
|
+
return `messages:unread-count:u=${params.userId}:org=${params.orgId ?? 'null'}`
|
|
25
|
+
}
|
|
26
|
+
|
|
11
27
|
function getDb(em: EntityManager): Kysely<any> {
|
|
12
28
|
return em.getKysely<any>()
|
|
13
29
|
}
|
|
@@ -17,6 +33,19 @@ export async function GET(req: Request) {
|
|
|
17
33
|
const em = ctx.container.resolve('em') as EntityManager
|
|
18
34
|
const db = getDb(em) as any
|
|
19
35
|
|
|
36
|
+
const orgId = scope.organizationId ?? null
|
|
37
|
+
const cache = isCrudCacheEnabled() ? resolveCrudCache(ctx.container) : null
|
|
38
|
+
const cacheKey = cache
|
|
39
|
+
? buildUnreadCountCacheKey({ userId: scope.userId, orgId })
|
|
40
|
+
: null
|
|
41
|
+
|
|
42
|
+
if (cache && cacheKey) {
|
|
43
|
+
const cached = await runWithCacheTenant(scope.tenantId, () => cache.get(cacheKey))
|
|
44
|
+
if (typeof cached === 'number') {
|
|
45
|
+
return Response.json({ unreadCount: cached })
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
20
49
|
let query = db
|
|
21
50
|
.selectFrom('message_recipients as r')
|
|
22
51
|
.innerJoin('messages as m', 'm.id', 'r.message_id')
|
|
@@ -38,6 +67,17 @@ export async function GET(req: Request) {
|
|
|
38
67
|
.executeTakeFirst() as { count: string | number } | undefined
|
|
39
68
|
const count = Number(row?.count ?? 0)
|
|
40
69
|
|
|
70
|
+
if (cache && cacheKey) {
|
|
71
|
+
try {
|
|
72
|
+
await runWithCacheTenant(scope.tenantId, () =>
|
|
73
|
+
cache.set(cacheKey, count, {
|
|
74
|
+
ttl: UNREAD_COUNT_TTL_MS,
|
|
75
|
+
tags: buildCollectionTags(UNREAD_COUNT_RESOURCE, scope.tenantId, [orgId]),
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
78
|
+
} catch {}
|
|
79
|
+
}
|
|
80
|
+
|
|
41
81
|
return Response.json({ unreadCount: count })
|
|
42
82
|
}
|
|
43
83
|
|
|
@@ -11,6 +11,7 @@ import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/u
|
|
|
11
11
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
12
12
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
13
13
|
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
14
|
+
import { StatusBadge, type StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'
|
|
14
15
|
|
|
15
16
|
const MUTATION_CONTEXT_ID = 'query_index.status.list:actions'
|
|
16
17
|
|
|
@@ -127,7 +128,7 @@ function createColumns(t: Translator): ColumnDef<Row>[] {
|
|
|
127
128
|
if (!record.vectorEnabled) return <span>—</span>
|
|
128
129
|
const ok = record.vectorCount != null && record.baseCount != null && record.vectorCount === record.baseCount
|
|
129
130
|
const display = formatCount(record.vectorCount)
|
|
130
|
-
const className = ok ? 'text-
|
|
131
|
+
const className = ok ? 'text-status-success-text' : 'text-status-warning-text'
|
|
131
132
|
return <span className={className}>{display}</span>
|
|
132
133
|
},
|
|
133
134
|
meta: { priority: 2 },
|
|
@@ -141,7 +142,7 @@ function createColumns(t: Translator): ColumnDef<Row>[] {
|
|
|
141
142
|
if (!record.fulltextEnabled) return <span>—</span>
|
|
142
143
|
const ok = record.fulltextCount != null && record.baseCount != null && record.fulltextCount === record.baseCount
|
|
143
144
|
const display = formatCount(record.fulltextCount)
|
|
144
|
-
const className = ok ? 'text-
|
|
145
|
+
const className = ok ? 'text-status-success-text' : 'text-status-warning-text'
|
|
145
146
|
return <span className={className}>{display}</span>
|
|
146
147
|
},
|
|
147
148
|
meta: { priority: 2 },
|
|
@@ -159,17 +160,14 @@ function createColumns(t: Translator): ColumnDef<Row>[] {
|
|
|
159
160
|
const label = jobProgress
|
|
160
161
|
? t('query_index.table.status.withProgress', { status: statusText, progress: jobProgress })
|
|
161
162
|
: statusText
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
: ok
|
|
171
|
-
? 'text-green-600'
|
|
172
|
-
: 'text-muted-foreground'
|
|
163
|
+
let variant: StatusBadgeVariant = 'neutral'
|
|
164
|
+
if (job) {
|
|
165
|
+
if (job.status === 'stalled') variant = 'error'
|
|
166
|
+
else if (job.status === 'reindexing' || job.status === 'purging') variant = 'warning'
|
|
167
|
+
else variant = ok ? 'success' : 'neutral'
|
|
168
|
+
} else {
|
|
169
|
+
variant = ok ? 'success' : 'neutral'
|
|
170
|
+
}
|
|
173
171
|
|
|
174
172
|
const lines: string[] = []
|
|
175
173
|
|
|
@@ -203,8 +201,12 @@ function createColumns(t: Translator): ColumnDef<Row>[] {
|
|
|
203
201
|
}
|
|
204
202
|
|
|
205
203
|
return (
|
|
206
|
-
<div className="space-y-1">
|
|
207
|
-
<
|
|
204
|
+
<div className="space-y-1.5">
|
|
205
|
+
<div>
|
|
206
|
+
<StatusBadge variant={variant} dot>
|
|
207
|
+
{label}
|
|
208
|
+
</StatusBadge>
|
|
209
|
+
</div>
|
|
208
210
|
{lines.length > 0 && (
|
|
209
211
|
<div className="text-xs text-muted-foreground">
|
|
210
212
|
{lines.map((line, idx) => (
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import { useRouter } from 'next/navigation'
|
|
4
|
+
import { usePathname, useRouter } from 'next/navigation'
|
|
5
5
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
6
6
|
import { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
|
|
7
|
+
import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
|
|
7
8
|
import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
8
9
|
import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
9
10
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
@@ -19,6 +20,7 @@ export default function ResourcesResourceTypeEditPage({ params }: { params?: { i
|
|
|
19
20
|
const resourceTypeId = params?.id ?? ''
|
|
20
21
|
const t = useT()
|
|
21
22
|
const router = useRouter()
|
|
23
|
+
const pathname = usePathname()
|
|
22
24
|
const [initialValues, setInitialValues] = React.useState<ResourceTypeFormValues | null>(null)
|
|
23
25
|
const [loading, setLoading] = React.useState(true)
|
|
24
26
|
const [error, setError] = React.useState<string | null>(null)
|
|
@@ -108,6 +110,20 @@ export default function ResourcesResourceTypeEditPage({ params }: { params?: { i
|
|
|
108
110
|
router.push('/backend/resources/resource-types')
|
|
109
111
|
}, [resourceCount, resourceTypeId, router, t])
|
|
110
112
|
|
|
113
|
+
// Publish page-load record context to the AppShell-owned `backend:record:current`
|
|
114
|
+
// mount so the enterprise record_locks widget resolves `resources.resourceType` + id
|
|
115
|
+
// explicitly. The resourceKind mirrors the ResourceTypeCrudForm `versionHistory` so the
|
|
116
|
+
// held lock matches the save-time conflict surface for the same resource type.
|
|
117
|
+
useSetCurrentRecordInjectionContext(
|
|
118
|
+
buildRecordInjectionContext({
|
|
119
|
+
resourceKind: 'resources.resourceType',
|
|
120
|
+
resourceId: resourceTypeId || null,
|
|
121
|
+
updatedAt: initialValues?.updatedAt ?? null,
|
|
122
|
+
data: initialValues as Record<string, unknown> | null,
|
|
123
|
+
path: pathname,
|
|
124
|
+
}),
|
|
125
|
+
)
|
|
126
|
+
|
|
111
127
|
if (isNotFound) {
|
|
112
128
|
return (
|
|
113
129
|
<Page>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import { useRouter, useSearchParams } from 'next/navigation'
|
|
4
|
+
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'
|
|
@@ -12,6 +12,7 @@ import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors
|
|
|
12
12
|
import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
13
13
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
14
14
|
import { ActivitiesSection, NotesSection, RecordNotFoundState, type SectionAction, type TagOption } from '@open-mercato/ui/backend/detail'
|
|
15
|
+
import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
|
|
15
16
|
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
16
17
|
import { VersionHistoryAction } from '@open-mercato/ui/backend/version-history'
|
|
17
18
|
import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
|
|
@@ -91,6 +92,7 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
|
|
|
91
92
|
const t = useT()
|
|
92
93
|
const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])
|
|
93
94
|
const router = useRouter()
|
|
95
|
+
const pathname = usePathname()
|
|
94
96
|
const searchParams = useSearchParams()
|
|
95
97
|
const [initialValues, setInitialValues] = React.useState<Record<string, unknown> | null>(null)
|
|
96
98
|
const [isNotFound, setIsNotFound] = React.useState(false)
|
|
@@ -602,6 +604,20 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
|
|
|
602
604
|
? initialValues.name.trim()
|
|
603
605
|
: t('resources.resources.detail.untitled', 'Unnamed resource')
|
|
604
606
|
|
|
607
|
+
// Publish page-load record context to the AppShell-owned `backend:record:current`
|
|
608
|
+
// mount so the enterprise record_locks widget resolves `resources.resource` + id
|
|
609
|
+
// explicitly. The resourceKind mirrors the VersionHistoryAction config / ResourceCrudForm
|
|
610
|
+
// `versionHistory` so the held lock matches the save-time conflict surface for the resource.
|
|
611
|
+
useSetCurrentRecordInjectionContext(
|
|
612
|
+
buildRecordInjectionContext({
|
|
613
|
+
resourceKind: 'resources.resource',
|
|
614
|
+
resourceId: resourceId || null,
|
|
615
|
+
updatedAt: typeof initialValues?.updatedAt === 'string' ? initialValues.updatedAt : null,
|
|
616
|
+
data: initialValues,
|
|
617
|
+
path: pathname,
|
|
618
|
+
}),
|
|
619
|
+
)
|
|
620
|
+
|
|
605
621
|
if (isNotFound) {
|
|
606
622
|
return (
|
|
607
623
|
<Page>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
3
|
+
import { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
|
|
4
|
+
import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
4
5
|
import { mapCommentSummary, type NotesDataAdapter } from '@open-mercato/ui/backend/detail/NotesSection'
|
|
5
6
|
|
|
6
7
|
type Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string
|
|
@@ -61,33 +62,43 @@ export function createResourceNotesAdapter(
|
|
|
61
62
|
)
|
|
62
63
|
return response.result ?? {}
|
|
63
64
|
},
|
|
64
|
-
update: async ({ id, patch }) => {
|
|
65
|
+
update: async ({ id, patch, updatedAt }) => {
|
|
65
66
|
const payload: Record<string, unknown> = { id }
|
|
66
67
|
if (patch.body !== undefined) payload.body = patch.body
|
|
67
68
|
if (patch.appearanceIcon !== undefined) payload.appearanceIcon = patch.appearanceIcon
|
|
68
69
|
if (patch.appearanceColor !== undefined) payload.appearanceColor = patch.appearanceColor
|
|
70
|
+
// Run through the guarded-mutation runner AND send the optimistic-lock header
|
|
71
|
+
// (note's loaded updatedAt) so a stale edit fails with a 409; the shared
|
|
72
|
+
// NotesSection host surfaces it through surfaceRecordConflict instead of
|
|
73
|
+
// silently overwriting.
|
|
69
74
|
await runWrite(
|
|
70
|
-
() =>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
() => withScopedApiRequestHeaders(
|
|
76
|
+
buildOptimisticLockHeader(updatedAt ?? null),
|
|
77
|
+
() => apiCallOrThrow(
|
|
78
|
+
'/api/resources/comments',
|
|
79
|
+
{
|
|
80
|
+
method: 'PUT',
|
|
81
|
+
headers: { 'content-type': 'application/json' },
|
|
82
|
+
body: JSON.stringify(payload),
|
|
83
|
+
},
|
|
84
|
+
{ errorMessage: translator('resources.resources.detail.notes.updateError', 'Failed to update note.') },
|
|
85
|
+
),
|
|
78
86
|
),
|
|
79
87
|
{ operation: 'updateNote', id },
|
|
80
88
|
)
|
|
81
89
|
},
|
|
82
|
-
delete: async ({ id }) => {
|
|
90
|
+
delete: async ({ id, updatedAt }) => {
|
|
83
91
|
await runWrite(
|
|
84
|
-
() =>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
() => withScopedApiRequestHeaders(
|
|
93
|
+
buildOptimisticLockHeader(updatedAt ?? null),
|
|
94
|
+
() => apiCallOrThrow(
|
|
95
|
+
`/api/resources/comments?id=${encodeURIComponent(id)}`,
|
|
96
|
+
{
|
|
97
|
+
method: 'DELETE',
|
|
98
|
+
headers: { 'content-type': 'application/json' },
|
|
99
|
+
},
|
|
100
|
+
{ errorMessage: translator('resources.resources.detail.notes.deleteError', 'Failed to delete note') },
|
|
101
|
+
),
|
|
91
102
|
),
|
|
92
103
|
{ operation: 'deleteNote', id },
|
|
93
104
|
)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"use client"
|
|
4
4
|
|
|
5
5
|
import * as React from 'react'
|
|
6
|
-
import { useSearchParams, useRouter } from 'next/navigation'
|
|
6
|
+
import { useSearchParams, useRouter, usePathname } from 'next/navigation'
|
|
7
7
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
8
8
|
import {
|
|
9
9
|
CustomDataSection,
|
|
@@ -68,6 +68,7 @@ import { ICON_SUGGESTIONS } from '@open-mercato/core/modules/customers/lib/dicti
|
|
|
68
68
|
import { readMarkdownPreferenceCookie, writeMarkdownPreferenceCookie } from '@open-mercato/core/modules/customers/lib/markdownPreference'
|
|
69
69
|
import { InjectionSpot, useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'
|
|
70
70
|
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
71
|
+
import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
|
|
71
72
|
|
|
72
73
|
function formatMessageAmount(amount: number | null | undefined, currency: string | null | undefined): string | null {
|
|
73
74
|
if (typeof amount !== 'number' || !Number.isFinite(amount)) return null
|
|
@@ -1892,6 +1893,7 @@ export default function SalesDocumentDetailPage({
|
|
|
1892
1893
|
const t = useT()
|
|
1893
1894
|
const router = useRouter()
|
|
1894
1895
|
const searchParams = useSearchParams()
|
|
1896
|
+
const pathname = usePathname()
|
|
1895
1897
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
1896
1898
|
const [loading, setLoading] = React.useState(true)
|
|
1897
1899
|
const [isNotFound, setIsNotFound] = React.useState(false)
|
|
@@ -1972,6 +1974,18 @@ export default function SalesDocumentDetailPage({
|
|
|
1972
1974
|
},
|
|
1973
1975
|
[detailInjectionContext, runMutation],
|
|
1974
1976
|
)
|
|
1977
|
+
// Publish page-load record context to the AppShell-owned `backend:record:current`
|
|
1978
|
+
// mount so record_locks gets presence + the action-log base (updatedAt/data) for
|
|
1979
|
+
// this order/quote. Sub-resource sections inherit this context — no second mount.
|
|
1980
|
+
useSetCurrentRecordInjectionContext(
|
|
1981
|
+
buildRecordInjectionContext({
|
|
1982
|
+
resourceKind: `sales.${kind}`,
|
|
1983
|
+
resourceId: record?.id ?? null,
|
|
1984
|
+
updatedAt: record?.updatedAt ?? null,
|
|
1985
|
+
data: record as Record<string, unknown> | null,
|
|
1986
|
+
path: pathname,
|
|
1987
|
+
}),
|
|
1988
|
+
)
|
|
1975
1989
|
const clearCustomerError = React.useCallback(() => setCustomerError(null), [])
|
|
1976
1990
|
const { data: currencyDictionary } = useCurrencyDictionary()
|
|
1977
1991
|
const scopeVersion = useOrganizationScopeVersion()
|
|
@@ -4244,6 +4258,7 @@ export default function SalesDocumentDetailPage({
|
|
|
4244
4258
|
currencyCode={record.currencyCode ?? null}
|
|
4245
4259
|
organizationId={(record as any)?.organizationId ?? (record as any)?.organization_id ?? null}
|
|
4246
4260
|
tenantId={(record as any)?.tenantId ?? (record as any)?.tenant_id ?? null}
|
|
4261
|
+
documentUpdatedAt={record.updatedAt ?? null}
|
|
4247
4262
|
shippingAddressSnapshot={shippingSnapshot ?? null}
|
|
4248
4263
|
onActionChange={handleSectionActionChange}
|
|
4249
4264
|
onAddComment={appendShipmentComment}
|
|
@@ -4300,6 +4315,7 @@ export default function SalesDocumentDetailPage({
|
|
|
4300
4315
|
currencyCode={record.currencyCode ?? null}
|
|
4301
4316
|
organizationId={(record as any)?.organizationId ?? (record as any)?.organization_id ?? null}
|
|
4302
4317
|
tenantId={(record as any)?.tenantId ?? (record as any)?.tenant_id ?? null}
|
|
4318
|
+
documentUpdatedAt={record.updatedAt ?? null}
|
|
4303
4319
|
onActionChange={handleSectionActionChange}
|
|
4304
4320
|
onPaymentsChange={(payments) => setHasPayments(payments.length > 0)}
|
|
4305
4321
|
onTotalsChange={() => {
|
|
@@ -4981,7 +4981,7 @@ const updateQuoteCommand: CommandHandler<
|
|
|
4981
4981
|
if (!quote)
|
|
4982
4982
|
throw new CrudHttpError(404, { error: "Sales quote not found" });
|
|
4983
4983
|
ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
|
|
4984
|
-
enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
4984
|
+
await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
4985
4985
|
const shouldInvalidateSentToken = (quote.status ?? null) === "sent";
|
|
4986
4986
|
if (shouldInvalidateSentToken) {
|
|
4987
4987
|
quote.acceptanceToken = null;
|
|
@@ -5213,6 +5213,7 @@ const updateOrderCommand: CommandHandler<
|
|
|
5213
5213
|
if (!order)
|
|
5214
5214
|
throw new CrudHttpError(404, { error: "Sales order not found" });
|
|
5215
5215
|
ensureOrderScope(ctx, order.organizationId, order.tenantId);
|
|
5216
|
+
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
5216
5217
|
const previousStatus = normalizeStatusValue(order.status);
|
|
5217
5218
|
let statusChangeNote: SalesNote | null = null;
|
|
5218
5219
|
const shouldRecalculateTotals =
|
|
@@ -6055,7 +6056,7 @@ const convertQuoteToOrderCommand: CommandHandler<
|
|
|
6055
6056
|
),
|
|
6056
6057
|
});
|
|
6057
6058
|
ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
|
|
6058
|
-
enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
6059
|
+
await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
6059
6060
|
const snapshot = await loadQuoteSnapshot(em, payload.quoteId);
|
|
6060
6061
|
if (!snapshot)
|
|
6061
6062
|
throw new CrudHttpError(404, {
|
|
@@ -6545,7 +6546,7 @@ const orderLineUpsertCommand: CommandHandler<
|
|
|
6545
6546
|
if (!order)
|
|
6546
6547
|
throw new CrudHttpError(404, { error: "Sales order not found" });
|
|
6547
6548
|
ensureOrderScope(ctx, order.organizationId, order.tenantId);
|
|
6548
|
-
enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
6549
|
+
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
6549
6550
|
|
|
6550
6551
|
const [existingLines, adjustments] = await Promise.all([
|
|
6551
6552
|
em.find(SalesOrderLine, { order }, { orderBy: { lineNumber: "asc" } }),
|
|
@@ -6866,7 +6867,7 @@ const orderLineDeleteCommand: CommandHandler<
|
|
|
6866
6867
|
),
|
|
6867
6868
|
});
|
|
6868
6869
|
ensureOrderScope(ctx, order.organizationId, order.tenantId);
|
|
6869
|
-
enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
6870
|
+
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
6870
6871
|
const shipmentCount = await em.count(SalesShipmentItem, {
|
|
6871
6872
|
orderLine: parsed.id,
|
|
6872
6873
|
shipment: { deletedAt: null },
|
|
@@ -7037,7 +7038,7 @@ const quoteLineUpsertCommand: CommandHandler<
|
|
|
7037
7038
|
if (!quote)
|
|
7038
7039
|
throw new CrudHttpError(404, { error: "Sales quote not found" });
|
|
7039
7040
|
ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
|
|
7040
|
-
enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
7041
|
+
await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
7041
7042
|
const [existingLines, adjustments] = await Promise.all([
|
|
7042
7043
|
em.find(SalesQuoteLine, { quote }, { orderBy: { lineNumber: "asc" } }),
|
|
7043
7044
|
em.find(
|
|
@@ -7349,7 +7350,7 @@ const quoteLineDeleteCommand: CommandHandler<
|
|
|
7349
7350
|
if (!quote)
|
|
7350
7351
|
throw new CrudHttpError(404, { error: "Sales quote not found" });
|
|
7351
7352
|
ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
|
|
7352
|
-
enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
7353
|
+
await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
7353
7354
|
const existingLines = await em.find(
|
|
7354
7355
|
SalesQuoteLine,
|
|
7355
7356
|
{ quote },
|
|
@@ -7503,7 +7504,7 @@ const orderAdjustmentUpsertCommand: CommandHandler<
|
|
|
7503
7504
|
if (!order)
|
|
7504
7505
|
throw new CrudHttpError(404, { error: "Sales order not found" });
|
|
7505
7506
|
ensureOrderScope(ctx, order.organizationId, order.tenantId);
|
|
7506
|
-
enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
7507
|
+
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
7507
7508
|
if (parsed.scope === "line") {
|
|
7508
7509
|
throw new CrudHttpError(400, {
|
|
7509
7510
|
error: "Line-scoped adjustments are not supported yet.",
|
|
@@ -7797,7 +7798,7 @@ const orderAdjustmentDeleteCommand: CommandHandler<
|
|
|
7797
7798
|
if (!order)
|
|
7798
7799
|
throw new CrudHttpError(404, { error: "Sales order not found" });
|
|
7799
7800
|
ensureOrderScope(ctx, order.organizationId, order.tenantId);
|
|
7800
|
-
enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
7801
|
+
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
|
|
7801
7802
|
|
|
7802
7803
|
const [existingLines, adjustments] = await Promise.all([
|
|
7803
7804
|
em.find(SalesOrderLine, { order }, { orderBy: { lineNumber: "asc" } }),
|
|
@@ -7962,7 +7963,7 @@ const quoteAdjustmentUpsertCommand: CommandHandler<
|
|
|
7962
7963
|
if (!quote)
|
|
7963
7964
|
throw new CrudHttpError(404, { error: "Sales quote not found" });
|
|
7964
7965
|
ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
|
|
7965
|
-
enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
7966
|
+
await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
7966
7967
|
if (parsed.scope === "line") {
|
|
7967
7968
|
throw new CrudHttpError(400, {
|
|
7968
7969
|
error: "Line-scoped adjustments are not supported yet.",
|
|
@@ -8254,7 +8255,7 @@ const quoteAdjustmentDeleteCommand: CommandHandler<
|
|
|
8254
8255
|
if (!quote)
|
|
8255
8256
|
throw new CrudHttpError(404, { error: "Sales quote not found" });
|
|
8256
8257
|
ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
|
|
8257
|
-
enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
8258
|
+
await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
8258
8259
|
|
|
8259
8260
|
const [existingLines, adjustments] = await Promise.all([
|
|
8260
8261
|
em.find(SalesQuoteLine, { quote }, { orderBy: { lineNumber: "asc" } }),
|
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
ensureTenantScope,
|
|
31
31
|
extractUndoPayload,
|
|
32
32
|
toNumericString,
|
|
33
|
+
enforceSalesDocumentOptimisticLock,
|
|
34
|
+
SALES_RESOURCE_KIND_ORDER,
|
|
33
35
|
} from './shared'
|
|
34
36
|
import { resolveDictionaryEntryValue } from '../lib/dictionaries'
|
|
35
37
|
import { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'
|
|
@@ -343,6 +345,9 @@ const createPaymentCommand: CommandHandler<
|
|
|
343
345
|
if (order.deletedAt) {
|
|
344
346
|
throw new CrudHttpError(404, { error: 'sales.payments.order_not_found' })
|
|
345
347
|
}
|
|
348
|
+
// Guard the parent order's aggregate version (Gap A): a payment mutation
|
|
349
|
+
// recalculates the order totals, so a stale parent must 409 before we touch it.
|
|
350
|
+
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER)
|
|
346
351
|
if (
|
|
347
352
|
order.currencyCode &&
|
|
348
353
|
input.currencyCode &&
|
|
@@ -730,6 +735,9 @@ const updatePaymentCommand: CommandHandler<
|
|
|
730
735
|
)
|
|
731
736
|
ensureSameScope(payment, resolvedOrganizationId, resolvedTenantId)
|
|
732
737
|
const previousOrder = payment.order as SalesOrder | null
|
|
738
|
+
// Guard the parent order's aggregate version (Gap A): updating a payment
|
|
739
|
+
// recalculates the order totals, so a stale parent must 409 before mutating.
|
|
740
|
+
await enforceSalesDocumentOptimisticLock(ctx, previousOrder, SALES_RESOURCE_KIND_ORDER)
|
|
733
741
|
// Apply payment scalar fields, order/line status changes and the
|
|
734
742
|
// allocations rebuild in one transaction so a mid-write failure cannot
|
|
735
743
|
// leave the payment and its allocations partially committed (#2336).
|
|
@@ -1064,6 +1072,9 @@ const deletePaymentCommand: CommandHandler<
|
|
|
1064
1072
|
)
|
|
1065
1073
|
ensureSameScope(payment, input.organizationId, input.tenantId)
|
|
1066
1074
|
const order = payment.order as SalesOrder | null
|
|
1075
|
+
// Guard the parent order's aggregate version (Gap A): deleting a payment
|
|
1076
|
+
// recalculates the order totals, so a stale parent must 409 before mutating.
|
|
1077
|
+
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER)
|
|
1067
1078
|
const allocations = await findWithDecryption(em, SalesPaymentAllocation, { payment }, {}, { tenantId: payment.tenantId, organizationId: payment.organizationId })
|
|
1068
1079
|
const allocationOrders = allocations
|
|
1069
1080
|
.map((allocation) =>
|
|
@@ -615,7 +615,7 @@ const createReturnCommand: CommandHandler<ReturnCreateInput, { returnId: string
|
|
|
615
615
|
throw new CrudHttpError(404, { error: translate('sales.returns.orderMissing', 'Order not found.') })
|
|
616
616
|
}
|
|
617
617
|
ensureSameScope(order, input.organizationId, input.tenantId)
|
|
618
|
-
enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER)
|
|
618
|
+
await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER)
|
|
619
619
|
|
|
620
620
|
const orderLines = await findWithDecryption(
|
|
621
621
|
tx,
|
|
@@ -890,7 +890,7 @@ const updateReturnCommand: CommandHandler<ReturnUpdateInput, { returnId: string
|
|
|
890
890
|
}
|
|
891
891
|
// Lock on the return's own version — editing header fields (reason / notes /
|
|
892
892
|
// returnedAt) only touches the return, not the order totals.
|
|
893
|
-
enforceSalesDocumentOptimisticLock(ctx, entity, SALES_RESOURCE_KIND_RETURN)
|
|
893
|
+
await enforceSalesDocumentOptimisticLock(ctx, entity, SALES_RESOURCE_KIND_RETURN)
|
|
894
894
|
|
|
895
895
|
if (input.reason !== undefined) entity.reason = input.reason.length ? input.reason : null
|
|
896
896
|
if (input.notes !== undefined) entity.notes = input.notes.length ? input.notes : null
|
|
@@ -1020,7 +1020,7 @@ const deleteReturnCommand: CommandHandler<ReturnDeleteInput, { returnId: string
|
|
|
1020
1020
|
}
|
|
1021
1021
|
ensureSameScope(header, input.organizationId, input.tenantId)
|
|
1022
1022
|
// Lock on the return's own version, captured before any mutation.
|
|
1023
|
-
enforceSalesDocumentOptimisticLock(ctx, header, SALES_RESOURCE_KIND_RETURN)
|
|
1023
|
+
await enforceSalesDocumentOptimisticLock(ctx, header, SALES_RESOURCE_KIND_RETURN)
|
|
1024
1024
|
|
|
1025
1025
|
await reverseReturnEffects(em, salesCalculationService, snapshot)
|
|
1026
1026
|
|
|
@@ -2,7 +2,7 @@ import type { EntityManager } from '@mikro-orm/postgresql'
|
|
|
2
2
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
3
3
|
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
4
4
|
import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
5
|
-
import {
|
|
5
|
+
import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
|
|
6
6
|
export { assertFound } from '@open-mercato/shared/lib/crud/errors'
|
|
7
7
|
export { ensureOrganizationScope, ensureSameScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'
|
|
8
8
|
export { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
|
|
@@ -28,14 +28,20 @@ export const SALES_RESOURCE_KIND_RETURN = 'sales.return'
|
|
|
28
28
|
*
|
|
29
29
|
* Strictly additive: when the client sends no header the check is a no-op, so
|
|
30
30
|
* existing API consumers are unaffected. Respects `OM_OPTIMISTIC_LOCK`.
|
|
31
|
+
*
|
|
32
|
+
* Routes through the async DI-aware seam `enforceCommandOptimisticLockWithGuards`
|
|
33
|
+
* (Phase 0 / S1): the OSS `updated_at` floor runs first (identical behavior when
|
|
34
|
+
* `record_locks` is disabled — floor only), then the optional enterprise
|
|
35
|
+
* `record_locks` enrichment is awaited so the aggregate check observes the
|
|
36
|
+
* action-log diff when the resource is enabled. Callers MUST `await` it.
|
|
31
37
|
*/
|
|
32
|
-
export function enforceSalesDocumentOptimisticLock(
|
|
38
|
+
export async function enforceSalesDocumentOptimisticLock(
|
|
33
39
|
ctx: CommandRuntimeContext,
|
|
34
40
|
document: { id: string; updatedAt?: Date | string | null } | null | undefined,
|
|
35
41
|
resourceKind: string,
|
|
36
|
-
): void {
|
|
42
|
+
): Promise<void> {
|
|
37
43
|
if (!document) return
|
|
38
|
-
|
|
44
|
+
await enforceCommandOptimisticLockWithGuards(ctx.container, {
|
|
39
45
|
resourceKind,
|
|
40
46
|
resourceId: document.id,
|
|
41
47
|
current: document.updatedAt ?? null,
|