@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
|
@@ -26,6 +26,7 @@ import { Switch } from '@open-mercato/ui/primitives/switch'
|
|
|
26
26
|
import { RowActions } from '@open-mercato/ui/backend/RowActions'
|
|
27
27
|
import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
|
|
28
28
|
import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
29
|
+
import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
|
|
29
30
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
30
31
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
31
32
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
@@ -167,6 +168,7 @@ export default function SyncRunsDashboardPage() {
|
|
|
167
168
|
params.set('pageSize', '20')
|
|
168
169
|
if (filterValues.status) params.set('status', filterValues.status as string)
|
|
169
170
|
if (filterValues.direction) params.set('direction', filterValues.direction as string)
|
|
171
|
+
if (search.trim()) params.set('search', search.trim())
|
|
170
172
|
const fallback: ResponsePayload = { items: [], total: 0, page, totalPages: 1 }
|
|
171
173
|
const call = await apiCall<ResponsePayload>(
|
|
172
174
|
`/api/data_sync/runs?${params.toString()}`,
|
|
@@ -188,7 +190,7 @@ export default function SyncRunsDashboardPage() {
|
|
|
188
190
|
}
|
|
189
191
|
load()
|
|
190
192
|
return () => { cancelled = true }
|
|
191
|
-
}, [page, filterValues, reloadToken, scopeVersion, t])
|
|
193
|
+
}, [page, filterValues, search, reloadToken, scopeVersion, t])
|
|
192
194
|
|
|
193
195
|
React.useEffect(() => {
|
|
194
196
|
let cancelled = false
|
|
@@ -401,21 +403,27 @@ export default function SyncRunsDashboardPage() {
|
|
|
401
403
|
setIsSavingSchedule(true)
|
|
402
404
|
try {
|
|
403
405
|
const call = await runMutation({
|
|
404
|
-
//
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
406
|
+
// Keyed upsert (POST). When the editor holds an existing schedule's
|
|
407
|
+
// `updatedAt`, the lock header version-checks the resolved row on the
|
|
408
|
+
// server; a brand-new schedule has a null `updatedAt`, so the header is
|
|
409
|
+
// empty and the create path stays unaffected.
|
|
410
|
+
operation: () => withScopedApiRequestHeaders(
|
|
411
|
+
buildOptimisticLockHeader(scheduleEditor.updatedAt),
|
|
412
|
+
() => apiCall<SyncScheduleRecord>('/api/data_sync/schedules', {
|
|
413
|
+
method: 'POST',
|
|
414
|
+
headers: { 'Content-Type': 'application/json' },
|
|
415
|
+
body: JSON.stringify({
|
|
416
|
+
integrationId: selectedIntegration.integrationId,
|
|
417
|
+
entityType: selectedEntityType,
|
|
418
|
+
direction: selectedDirection,
|
|
419
|
+
scheduleType: scheduleEditor.scheduleType,
|
|
420
|
+
scheduleValue: scheduleEditor.scheduleValue.trim(),
|
|
421
|
+
timezone: scheduleEditor.timezone.trim() || DEFAULT_TIMEZONE,
|
|
422
|
+
fullSync: scheduleEditor.fullSync,
|
|
423
|
+
isEnabled: scheduleEditor.isEnabled,
|
|
424
|
+
}),
|
|
425
|
+
}, { fallback: null }),
|
|
426
|
+
),
|
|
419
427
|
mutationPayload: {
|
|
420
428
|
integrationId: selectedIntegration.integrationId,
|
|
421
429
|
entityType: selectedEntityType,
|
|
@@ -434,6 +442,16 @@ export default function SyncRunsDashboardPage() {
|
|
|
434
442
|
})
|
|
435
443
|
|
|
436
444
|
if (!call.ok || !call.result) {
|
|
445
|
+
const conflictError = Object.assign(
|
|
446
|
+
new Error((call.result as { error?: string } | null)?.error ?? t('data_sync.dashboard.schedule.error', 'Failed to save recurring schedule')),
|
|
447
|
+
{
|
|
448
|
+
status: call.status,
|
|
449
|
+
...(call.result && typeof call.result === 'object' ? call.result : {}),
|
|
450
|
+
},
|
|
451
|
+
)
|
|
452
|
+
if (surfaceRecordConflict(conflictError, t)) {
|
|
453
|
+
return
|
|
454
|
+
}
|
|
437
455
|
flash((call.result as { error?: string } | null)?.error ?? t('data_sync.dashboard.schedule.error', 'Failed to save recurring schedule'), 'error')
|
|
438
456
|
return
|
|
439
457
|
}
|
|
@@ -965,6 +983,7 @@ export default function SyncRunsDashboardPage() {
|
|
|
965
983
|
onFiltersClear={handleFiltersClear}
|
|
966
984
|
searchValue={search}
|
|
967
985
|
onSearchChange={(value) => { setSearch(value); setPage(1) }}
|
|
986
|
+
searchPlaceholder={t('data_sync.dashboard.searchPlaceholder')}
|
|
968
987
|
perspective={{ tableId: 'data_sync.runs' }}
|
|
969
988
|
onRowClick={(row) => {
|
|
970
989
|
router.push(`/backend/data-sync/runs/${encodeURIComponent(row.id)}`)
|
|
@@ -28,6 +28,7 @@ export const listSyncRunsQuerySchema = z.object({
|
|
|
28
28
|
entityType: z.string().optional(),
|
|
29
29
|
direction: z.enum(['import', 'export']).optional(),
|
|
30
30
|
status: z.enum(['pending', 'running', 'completed', 'failed', 'cancelled', 'paused']).optional(),
|
|
31
|
+
search: z.string().trim().min(1).max(200).optional(),
|
|
31
32
|
page: z.coerce.number().int().min(1).default(1),
|
|
32
33
|
pageSize: z.coerce.number().int().min(1).max(100).default(20),
|
|
33
34
|
})
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"data_sync.dashboard.schedule.timezone": "Timezone",
|
|
48
48
|
"data_sync.dashboard.schedule.title": "Recurring schedule",
|
|
49
49
|
"data_sync.dashboard.schedule.type": "Schedule type",
|
|
50
|
+
"data_sync.dashboard.searchPlaceholder": "Nach Integration, Entitätstyp, Status oder Lauf-ID suchen",
|
|
50
51
|
"data_sync.dashboard.start.batchSize": "Batch Size",
|
|
51
52
|
"data_sync.dashboard.start.description": "Pick a sync target, launch an ad-hoc run, or save a recurring schedule for the same entity and direction from this page.",
|
|
52
53
|
"data_sync.dashboard.start.error": "Failed to start sync run",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"data_sync.dashboard.schedule.timezone": "Timezone",
|
|
48
48
|
"data_sync.dashboard.schedule.title": "Recurring schedule",
|
|
49
49
|
"data_sync.dashboard.schedule.type": "Schedule type",
|
|
50
|
+
"data_sync.dashboard.searchPlaceholder": "Search by integration, entity type, status, or run ID",
|
|
50
51
|
"data_sync.dashboard.start.batchSize": "Batch Size",
|
|
51
52
|
"data_sync.dashboard.start.description": "Pick a sync target, launch an ad-hoc run, or save a recurring schedule for the same entity and direction from this page.",
|
|
52
53
|
"data_sync.dashboard.start.error": "Failed to start sync run",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"data_sync.dashboard.schedule.timezone": "Timezone",
|
|
48
48
|
"data_sync.dashboard.schedule.title": "Recurring schedule",
|
|
49
49
|
"data_sync.dashboard.schedule.type": "Schedule type",
|
|
50
|
+
"data_sync.dashboard.searchPlaceholder": "Buscar por integración, tipo de entidad, estado o ID de ejecución",
|
|
50
51
|
"data_sync.dashboard.start.batchSize": "Batch Size",
|
|
51
52
|
"data_sync.dashboard.start.description": "Pick a sync target, launch an ad-hoc run, or save a recurring schedule for the same entity and direction from this page.",
|
|
52
53
|
"data_sync.dashboard.start.error": "Failed to start sync run",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"data_sync.dashboard.schedule.timezone": "Timezone",
|
|
48
48
|
"data_sync.dashboard.schedule.title": "Recurring schedule",
|
|
49
49
|
"data_sync.dashboard.schedule.type": "Schedule type",
|
|
50
|
+
"data_sync.dashboard.searchPlaceholder": "Szukaj po integracji, typie encji, statusie lub ID przebiegu",
|
|
50
51
|
"data_sync.dashboard.start.batchSize": "Batch Size",
|
|
51
52
|
"data_sync.dashboard.start.description": "Pick a sync target, launch an ad-hoc run, or save a recurring schedule for the same entity and direction from this page.",
|
|
52
53
|
"data_sync.dashboard.start.error": "Failed to start sync run",
|
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
|
|
2
2
|
import { findAndCountWithDecryption, findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
3
3
|
import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
|
|
4
|
+
import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
|
|
4
5
|
import { SyncCursor, SyncRun } from '../data/entities'
|
|
5
6
|
|
|
7
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
8
|
+
|
|
9
|
+
function buildRunSearchFilter(search: string): FilterQuery<SyncRun>[] | null {
|
|
10
|
+
const trimmed = search.trim()
|
|
11
|
+
if (!trimmed) return null
|
|
12
|
+
const pattern = `%${escapeLikePattern(trimmed)}%`
|
|
13
|
+
const conditions: FilterQuery<SyncRun>[] = [
|
|
14
|
+
{ integrationId: { $ilike: pattern } },
|
|
15
|
+
{ entityType: { $ilike: pattern } },
|
|
16
|
+
{ status: { $ilike: pattern } },
|
|
17
|
+
]
|
|
18
|
+
if (UUID_PATTERN.test(trimmed)) {
|
|
19
|
+
conditions.push({ id: trimmed })
|
|
20
|
+
}
|
|
21
|
+
return conditions
|
|
22
|
+
}
|
|
23
|
+
|
|
6
24
|
type SyncScope = {
|
|
7
25
|
organizationId: string
|
|
8
26
|
tenantId: string
|
|
@@ -89,6 +107,7 @@ export function createSyncRunService(em: EntityManager) {
|
|
|
89
107
|
entityType?: string
|
|
90
108
|
direction?: 'import' | 'export'
|
|
91
109
|
status?: string
|
|
110
|
+
search?: string
|
|
92
111
|
page: number
|
|
93
112
|
pageSize: number
|
|
94
113
|
}, scope: SyncScope): Promise<{ items: SyncRun[]; total: number }> {
|
|
@@ -102,6 +121,10 @@ export function createSyncRunService(em: EntityManager) {
|
|
|
102
121
|
if (query.entityType) where.entityType = query.entityType
|
|
103
122
|
if (query.direction) where.direction = query.direction
|
|
104
123
|
if (query.status) where.status = query.status as SyncRun['status']
|
|
124
|
+
if (query.search) {
|
|
125
|
+
const searchConditions = buildRunSearchFilter(query.search)
|
|
126
|
+
if (searchConditions) where.$or = searchConditions
|
|
127
|
+
}
|
|
105
128
|
|
|
106
129
|
const [items, total] = await findAndCountWithDecryption(
|
|
107
130
|
em,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import type { AwilixContainer } from 'awilix'
|
|
2
3
|
import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
|
|
3
4
|
import { findAndCountWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
4
|
-
import {
|
|
5
|
+
import { enforceCommandOptimisticLockWithGuards, enforceRecordGoneIsConflict } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
|
|
5
6
|
import { SyncSchedule } from '../data/entities'
|
|
6
7
|
|
|
7
8
|
type SyncScope = {
|
|
@@ -131,18 +132,28 @@ export function createSyncScheduleService(em: EntityManager, schedulerService?:
|
|
|
131
132
|
fullSync: boolean
|
|
132
133
|
isEnabled: boolean
|
|
133
134
|
expectedUpdatedAt?: string | null
|
|
134
|
-
}, scope: SyncScope): Promise<SyncSchedule> {
|
|
135
|
+
}, scope: SyncScope, container?: AwilixContainer): Promise<SyncSchedule> {
|
|
135
136
|
const existing = input.id
|
|
136
137
|
? await getById(input.id, scope)
|
|
137
138
|
: await getByKey(input.integrationId, input.entityType, input.direction, scope)
|
|
138
139
|
|
|
139
|
-
if (existing) {
|
|
140
|
-
|
|
140
|
+
if (existing && container) {
|
|
141
|
+
await enforceCommandOptimisticLockWithGuards(container, {
|
|
141
142
|
resourceKind: 'data_sync.schedule',
|
|
142
143
|
resourceId: existing.id,
|
|
143
144
|
current: existing.updatedAt ?? null,
|
|
144
145
|
expected: input.expectedUpdatedAt ?? null,
|
|
145
146
|
})
|
|
147
|
+
} else if (!existing && input.expectedUpdatedAt) {
|
|
148
|
+
// Concurrent delete-then-edit race: the client edited a schedule that was
|
|
149
|
+
// removed before this keyed upsert ran. Surface the unified conflict
|
|
150
|
+
// instead of silently re-creating it. No-op when no expected version was
|
|
151
|
+
// sent (a genuine create) or when OM_OPTIMISTIC_LOCK is off.
|
|
152
|
+
enforceRecordGoneIsConflict({
|
|
153
|
+
resourceKind: 'data_sync.schedule',
|
|
154
|
+
resourceId: input.id ?? `${input.integrationId}:${input.entityType}:${input.direction}`,
|
|
155
|
+
expected: input.expectedUpdatedAt,
|
|
156
|
+
})
|
|
146
157
|
}
|
|
147
158
|
|
|
148
159
|
const row = existing ?? em.create(SyncSchedule, {
|
|
@@ -200,10 +211,24 @@ export function createSyncScheduleService(em: EntityManager, schedulerService?:
|
|
|
200
211
|
return row
|
|
201
212
|
},
|
|
202
213
|
|
|
203
|
-
async deleteSchedule(
|
|
214
|
+
async deleteSchedule(
|
|
215
|
+
id: string,
|
|
216
|
+
scope: SyncScope,
|
|
217
|
+
container?: AwilixContainer,
|
|
218
|
+
expectedUpdatedAt?: string | null,
|
|
219
|
+
): Promise<boolean> {
|
|
204
220
|
const row = await getById(id, scope)
|
|
205
221
|
if (!row) return false
|
|
206
222
|
|
|
223
|
+
if (container) {
|
|
224
|
+
await enforceCommandOptimisticLockWithGuards(container, {
|
|
225
|
+
resourceKind: 'data_sync.schedule',
|
|
226
|
+
resourceId: row.id,
|
|
227
|
+
current: row.updatedAt ?? null,
|
|
228
|
+
expected: expectedUpdatedAt ?? null,
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
207
232
|
const scheduledJobId = row.scheduledJobId ?? row.id
|
|
208
233
|
await requireScheduler().unregister(scheduledJobId)
|
|
209
234
|
|
|
@@ -4,7 +4,7 @@ import { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictiona
|
|
|
4
4
|
import { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'
|
|
5
5
|
import { updateDictionaryEntrySchema } from '@open-mercato/core/modules/dictionaries/data/validators'
|
|
6
6
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
7
|
-
import {
|
|
7
|
+
import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
|
|
8
8
|
import {
|
|
9
9
|
runCrudMutationGuardAfterSuccess,
|
|
10
10
|
validateCrudMutationGuard,
|
|
@@ -76,7 +76,7 @@ export async function PATCH(req: Request, ctx: { params?: { dictionaryId?: strin
|
|
|
76
76
|
})
|
|
77
77
|
const dictionary = await loadDictionary(context, dictionaryId)
|
|
78
78
|
const entry = await loadEntry(context, dictionary, entryId)
|
|
79
|
-
|
|
79
|
+
await enforceCommandOptimisticLockWithGuards(context.container, {
|
|
80
80
|
resourceKind: 'dictionaries.entry',
|
|
81
81
|
resourceId: entry.id,
|
|
82
82
|
current: entry.updatedAt ?? null,
|
|
@@ -178,7 +178,7 @@ export async function DELETE(req: Request, ctx: { params?: { dictionaryId?: stri
|
|
|
178
178
|
})
|
|
179
179
|
const dictionary = await loadDictionary(context, dictionaryId)
|
|
180
180
|
const entry = await loadEntry(context, dictionary, entryId)
|
|
181
|
-
|
|
181
|
+
await enforceCommandOptimisticLockWithGuards(context.container, {
|
|
182
182
|
resourceKind: 'dictionaries.entry',
|
|
183
183
|
resourceId: entry.id,
|
|
184
184
|
current: entry.updatedAt ?? null,
|
|
@@ -3,7 +3,7 @@ import { z } from 'zod'
|
|
|
3
3
|
import { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'
|
|
4
4
|
import { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'
|
|
5
5
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
6
|
-
import {
|
|
6
|
+
import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
|
|
7
7
|
import {
|
|
8
8
|
runCrudMutationGuardAfterSuccess,
|
|
9
9
|
validateCrudMutationGuard,
|
|
@@ -115,7 +115,7 @@ export async function PATCH(req: Request, ctx: { params?: { dictionaryId?: strin
|
|
|
115
115
|
const payload = updateSchema.parse(await req.json().catch(() => ({})))
|
|
116
116
|
const dictionary = await loadDictionary(context, dictionaryId)
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
await enforceCommandOptimisticLockWithGuards(context.container, {
|
|
119
119
|
resourceKind: 'dictionaries.dictionary',
|
|
120
120
|
resourceId: dictionary.id,
|
|
121
121
|
current: dictionary.updatedAt ?? null,
|
|
@@ -236,7 +236,7 @@ export async function DELETE(req: Request, ctx: { params?: { dictionaryId?: stri
|
|
|
236
236
|
const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })
|
|
237
237
|
const dictionary = await loadDictionary(context, dictionaryId)
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
await enforceCommandOptimisticLockWithGuards(context.container, {
|
|
240
240
|
resourceKind: 'dictionaries.dictionary',
|
|
241
241
|
resourceId: dictionary.id,
|
|
242
242
|
current: dictionary.updatedAt ?? null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import { useSearchParams } from 'next/navigation'
|
|
4
|
+
import { useSearchParams, usePathname } from 'next/navigation'
|
|
5
5
|
import { Book, Plus, Pencil, Trash2 } from 'lucide-react'
|
|
6
6
|
import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
|
|
7
7
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
@@ -18,6 +18,7 @@ import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
|
18
18
|
import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
|
|
19
19
|
import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
20
20
|
import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
|
|
21
|
+
import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
|
|
21
22
|
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
22
23
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
23
24
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
@@ -58,6 +59,7 @@ export function DictionariesManager() {
|
|
|
58
59
|
const t = useT()
|
|
59
60
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
60
61
|
const searchParams = useSearchParams()
|
|
62
|
+
const pathname = usePathname()
|
|
61
63
|
const [items, setItems] = React.useState<DictionarySummary[]>([])
|
|
62
64
|
const [selectedId, setSelectedId] = React.useState<string | null>(null)
|
|
63
65
|
const [loading, setLoading] = React.useState(true)
|
|
@@ -366,6 +368,24 @@ export function DictionariesManager() {
|
|
|
366
368
|
|
|
367
369
|
const selectedDictionary = items.find((item) => item.id === selectedId) ?? null
|
|
368
370
|
|
|
371
|
+
// Publish the open dictionary's record context to the AppShell-owned
|
|
372
|
+
// `backend:record:current` mount so the enterprise record_locks widget resolves
|
|
373
|
+
// `dictionaries.dictionary` + id while the manager is open. Editing here is
|
|
374
|
+
// inline/dialog-driven (not a `[id]` detail route or a CrudForm with a stable
|
|
375
|
+
// `injectionSpotId`), so mounting presence on the manager keyed to the selected
|
|
376
|
+
// dictionary is the simplest place to hold the lock — it mirrors the
|
|
377
|
+
// `dictionaries.dictionary` resourceKind the dictionary PATCH route guards on.
|
|
378
|
+
// Cleared automatically when no dictionary is selected or on unmount.
|
|
379
|
+
useSetCurrentRecordInjectionContext(
|
|
380
|
+
buildRecordInjectionContext({
|
|
381
|
+
resourceKind: 'dictionaries.dictionary',
|
|
382
|
+
resourceId: selectedDictionary?.id ?? null,
|
|
383
|
+
updatedAt: selectedDictionary?.updatedAt ?? null,
|
|
384
|
+
data: (selectedDictionary ?? null) as Record<string, unknown> | null,
|
|
385
|
+
path: pathname,
|
|
386
|
+
}),
|
|
387
|
+
)
|
|
388
|
+
|
|
369
389
|
return (
|
|
370
390
|
<div className="grid gap-6 lg:grid-cols-[320px_1fr]">
|
|
371
391
|
<div className="rounded-lg border bg-card p-4 shadow-sm">
|
|
@@ -276,6 +276,8 @@ export function DictionaryEntriesEditor({ dictionaryId, dictionaryName, readOnly
|
|
|
276
276
|
await invalidateDictionaryEntries(queryClient, dictionaryId)
|
|
277
277
|
flash(t('dictionaries.config.entries.success.delete', 'Dictionary entry deleted.'), 'success')
|
|
278
278
|
} catch (err) {
|
|
279
|
+
// Route a concurrent-edit 409 through the single conflict surface (matching
|
|
280
|
+
// the entry save path); fall back to a flash for any other delete failure.
|
|
279
281
|
if (surfaceRecordConflict(err, t)) {
|
|
280
282
|
return
|
|
281
283
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
import * as React from 'react'
|
|
3
|
+
import { usePathname } from 'next/navigation'
|
|
3
4
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
5
|
import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
|
|
5
6
|
import { E } from '#generated/entities.ids.generated'
|
|
@@ -17,6 +18,7 @@ import { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
|
17
18
|
import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
|
|
18
19
|
import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
19
20
|
import { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
|
|
21
|
+
import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
|
|
20
22
|
|
|
21
23
|
type TreeResponse = {
|
|
22
24
|
items: OrganizationTreeNode[]
|
|
@@ -46,6 +48,7 @@ const TREE_PADDING = 12
|
|
|
46
48
|
export default function EditOrganizationPage({ params }: { params?: { id?: string } }) {
|
|
47
49
|
const orgId = params?.id
|
|
48
50
|
const t = useT()
|
|
51
|
+
const pathname = usePathname()
|
|
49
52
|
const [initialValues, setInitialValues] = React.useState<Record<string, unknown> | null>(null)
|
|
50
53
|
const [pathLabel, setPathLabel] = React.useState<string>('')
|
|
51
54
|
const [tenantId, setTenantId] = React.useState<string | null>(null)
|
|
@@ -270,6 +273,20 @@ export default function EditOrganizationPage({ params }: { params?: { id?: strin
|
|
|
270
273
|
{ id: 'custom', title: t('directory.organizations.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },
|
|
271
274
|
]), [detailFields, t])
|
|
272
275
|
|
|
276
|
+
// Publish page-load record context to the AppShell-owned `backend:record:current`
|
|
277
|
+
// mount so the enterprise record_locks widget resolves `directory.organization` + id
|
|
278
|
+
// explicitly. The resourceKind mirrors the CrudForm `versionHistory` so the held
|
|
279
|
+
// lock matches the save-time conflict surface for the same organization.
|
|
280
|
+
useSetCurrentRecordInjectionContext(
|
|
281
|
+
buildRecordInjectionContext({
|
|
282
|
+
resourceKind: 'directory.organization',
|
|
283
|
+
resourceId: orgId || null,
|
|
284
|
+
updatedAt: typeof initialValues?.updatedAt === 'string' ? initialValues.updatedAt : null,
|
|
285
|
+
data: initialValues,
|
|
286
|
+
path: pathname,
|
|
287
|
+
}),
|
|
288
|
+
)
|
|
289
|
+
|
|
273
290
|
if (!orgId) {
|
|
274
291
|
return (
|
|
275
292
|
<Page>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
import * as React from 'react'
|
|
3
|
+
import { usePathname } from 'next/navigation'
|
|
3
4
|
import { E } from '#generated/entities.ids.generated'
|
|
4
5
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
5
6
|
import { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'
|
|
@@ -9,6 +10,7 @@ import { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
|
9
10
|
import { readApiResultOrThrow, apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
|
|
10
11
|
import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
11
12
|
import { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
|
|
13
|
+
import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
|
|
12
14
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
13
15
|
import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
|
|
14
16
|
|
|
@@ -21,6 +23,7 @@ type TenantFormValues = {
|
|
|
21
23
|
|
|
22
24
|
export default function EditTenantPage({ params }: { params?: { id?: string } }) {
|
|
23
25
|
const tenantId = params?.id
|
|
26
|
+
const pathname = usePathname()
|
|
24
27
|
const [initial, setInitial] = React.useState<TenantFormValues | null>(null)
|
|
25
28
|
const [loading, setLoading] = React.useState(true)
|
|
26
29
|
const [error, setError] = React.useState<string | null>(null)
|
|
@@ -84,6 +87,20 @@ export default function EditTenantPage({ params }: { params?: { id?: string } })
|
|
|
84
87
|
return () => { cancelled = true }
|
|
85
88
|
}, [tenantId, t])
|
|
86
89
|
|
|
90
|
+
// Publish page-load record context to the AppShell-owned `backend:record:current`
|
|
91
|
+
// mount so the enterprise record_locks widget resolves `directory.tenant` + id
|
|
92
|
+
// explicitly. The resourceKind mirrors the CrudForm `versionHistory` so the held
|
|
93
|
+
// lock matches the save-time conflict surface for the same tenant.
|
|
94
|
+
useSetCurrentRecordInjectionContext(
|
|
95
|
+
buildRecordInjectionContext({
|
|
96
|
+
resourceKind: 'directory.tenant',
|
|
97
|
+
resourceId: tenantId || null,
|
|
98
|
+
updatedAt: initial?.updatedAt ?? null,
|
|
99
|
+
data: initial as Record<string, unknown> | null,
|
|
100
|
+
path: pathname,
|
|
101
|
+
}),
|
|
102
|
+
)
|
|
103
|
+
|
|
87
104
|
if (!tenantId) return null
|
|
88
105
|
|
|
89
106
|
if (isNotFound) {
|
|
@@ -8,7 +8,7 @@ import { upsertCustomEntitySchema } from '@open-mercato/core/modules/entities/da
|
|
|
8
8
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
9
9
|
import { isSystemEntitySelectable } from '@open-mercato/shared/lib/entities/system-entities'
|
|
10
10
|
import { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'
|
|
11
|
-
import {
|
|
11
|
+
import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
|
|
12
12
|
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
13
13
|
import {
|
|
14
14
|
beginEntitiesMutationGuard,
|
|
@@ -132,7 +132,7 @@ export async function POST(req: Request) {
|
|
|
132
132
|
let ent = await em.findOne(CustomEntity, where)
|
|
133
133
|
if (ent) {
|
|
134
134
|
try {
|
|
135
|
-
|
|
135
|
+
await enforceCommandOptimisticLockWithGuards(container, {
|
|
136
136
|
resourceKind: CUSTOM_ENTITY_DEFINITION_RESOURCE_KIND,
|
|
137
137
|
resourceId: ent.id,
|
|
138
138
|
current: ent.updatedAt ?? null,
|
|
@@ -11,7 +11,7 @@ import { parseBooleanToken, parseBooleanWithDefault } from '@open-mercato/shared
|
|
|
11
11
|
import { setRecordCustomFields } from '../lib/helpers'
|
|
12
12
|
import { CustomFieldValue } from '../data/entities'
|
|
13
13
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
14
|
-
import {
|
|
14
|
+
import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
|
|
15
15
|
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
16
16
|
import { getModules } from '@open-mercato/shared/lib/i18n/server'
|
|
17
17
|
import { assertEntityAclForRequest } from '../lib/entityAcl'
|
|
@@ -107,6 +107,8 @@ const listRecordsQuerySchema = z
|
|
|
107
107
|
pageSize: z.coerce.number().int().min(1).max(100).optional(),
|
|
108
108
|
sortField: z.string().optional(),
|
|
109
109
|
sortDir: z.enum(['asc', 'desc']).optional(),
|
|
110
|
+
search: z.string().optional(),
|
|
111
|
+
searchFields: z.string().optional(),
|
|
110
112
|
withDeleted: z.coerce.boolean().optional(),
|
|
111
113
|
format: z.enum(['csv', 'json', 'xml', 'markdown']).optional(),
|
|
112
114
|
exportScope: z.enum(['full']).optional(),
|
|
@@ -145,10 +147,15 @@ export async function GET(req: Request) {
|
|
|
145
147
|
const sortField = url.searchParams.get('sortField') || 'id'
|
|
146
148
|
const sortDir = (url.searchParams.get('sortDir') || 'asc').toLowerCase() === 'desc' ? 'desc' : 'asc'
|
|
147
149
|
const withDeleted = parseBooleanWithDefault(url.searchParams.get('withDeleted'), false)
|
|
150
|
+
const searchTerm = (url.searchParams.get('search') || '').trim()
|
|
151
|
+
const searchFields = (url.searchParams.get('searchFields') || '')
|
|
152
|
+
.split(',')
|
|
153
|
+
.map((field) => field.trim())
|
|
154
|
+
.filter(Boolean)
|
|
148
155
|
|
|
149
156
|
const qpEntries: Array<[string, string]> = []
|
|
150
157
|
for (const [key, val] of url.searchParams.entries()) {
|
|
151
|
-
if (['entityId','page','pageSize','sortField','sortDir','withDeleted','format','exportScope','export_scope','all','full'].includes(key)) continue
|
|
158
|
+
if (['entityId','page','pageSize','sortField','sortDir','withDeleted','format','exportScope','export_scope','all','full','search','searchFields'].includes(key)) continue
|
|
152
159
|
qpEntries.push([key, val])
|
|
153
160
|
}
|
|
154
161
|
|
|
@@ -246,6 +253,15 @@ export async function GET(req: Request) {
|
|
|
246
253
|
// even before the first record exists.
|
|
247
254
|
if (isCustomEntity) qopts.forceCustomEntityStorage = true
|
|
248
255
|
for (const [k, v] of qpEntries) buildFilter(k, v, isCustomEntity)
|
|
256
|
+
// Server-side full-result search: match the term against the requested fields
|
|
257
|
+
// (defaults to `id`) before pagination so totals/exports stay consistent with
|
|
258
|
+
// the active search instead of filtering only the current client page (#3229).
|
|
259
|
+
if (searchTerm) {
|
|
260
|
+
const fields = searchFields.length ? searchFields : ['id']
|
|
261
|
+
const pattern = `%${searchTerm}%`
|
|
262
|
+
const orClauses = fields.map((field) => ({ [field]: { $ilike: pattern } }))
|
|
263
|
+
;(filtersObj as any).$or = orClauses
|
|
264
|
+
}
|
|
249
265
|
const res = await qe.query(entityId as any, qopts)
|
|
250
266
|
const rawItems = res.items || []
|
|
251
267
|
const viewPageItems = rawItems.map(mapRow)
|
|
@@ -444,7 +460,8 @@ export async function PUT(req: Request) {
|
|
|
444
460
|
const { entityId, recordId, values } = parsed.data
|
|
445
461
|
|
|
446
462
|
try {
|
|
447
|
-
const
|
|
463
|
+
const container = await createRequestContainer()
|
|
464
|
+
const { resolve } = container
|
|
448
465
|
const de = resolve('dataEngine') as any
|
|
449
466
|
const em = resolve('em') as any
|
|
450
467
|
const rbac = resolve('rbacService') as RbacService
|
|
@@ -494,7 +511,7 @@ export async function PUT(req: Request) {
|
|
|
494
511
|
entityId: rid,
|
|
495
512
|
organizationId: targetOrgId,
|
|
496
513
|
})
|
|
497
|
-
|
|
514
|
+
await enforceCommandOptimisticLockWithGuards(container, {
|
|
498
515
|
resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,
|
|
499
516
|
resourceId: rid,
|
|
500
517
|
current: currentUpdatedAt,
|
|
@@ -542,7 +559,8 @@ export async function DELETE(req: Request) {
|
|
|
542
559
|
const { entityId, recordId } = parsed.data
|
|
543
560
|
|
|
544
561
|
try {
|
|
545
|
-
const
|
|
562
|
+
const container = await createRequestContainer()
|
|
563
|
+
const { resolve } = container
|
|
546
564
|
const de = resolve('dataEngine') as any
|
|
547
565
|
const em = resolve('em') as any
|
|
548
566
|
const rbac = resolve('rbacService') as RbacService
|
|
@@ -560,7 +578,7 @@ export async function DELETE(req: Request) {
|
|
|
560
578
|
entityId: recordId,
|
|
561
579
|
organizationId: targetOrgId,
|
|
562
580
|
})
|
|
563
|
-
|
|
581
|
+
await enforceCommandOptimisticLockWithGuards(container, {
|
|
564
582
|
resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,
|
|
565
583
|
resourceId: recordId,
|
|
566
584
|
current: currentUpdatedAt,
|
|
@@ -97,7 +97,16 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
|
|
|
97
97
|
keyExtras: [scopeVersion],
|
|
98
98
|
})
|
|
99
99
|
|
|
100
|
-
//
|
|
100
|
+
// Fields searched server-side: every visible column plus the base `id`.
|
|
101
|
+
const searchableFields = React.useMemo(() => {
|
|
102
|
+
const fields = (columns || [])
|
|
103
|
+
.map((col) => (col as any).accessorKey)
|
|
104
|
+
.filter((key): key is string => typeof key === 'string' && key.length > 0)
|
|
105
|
+
return Array.from(new Set(['id', ...fields]))
|
|
106
|
+
}, [columns])
|
|
107
|
+
|
|
108
|
+
// Fetch records whenever paging/sorting/filters/search change. Search is applied
|
|
109
|
+
// server-side (before pagination) so totals and exports stay consistent (#3229).
|
|
101
110
|
React.useEffect(() => {
|
|
102
111
|
let cancelled = false
|
|
103
112
|
const run = async () => {
|
|
@@ -107,6 +116,11 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
|
|
|
107
116
|
params.set('entityId', entityId)
|
|
108
117
|
params.set('page', String(page))
|
|
109
118
|
params.set('pageSize', String(pageSize))
|
|
119
|
+
const trimmedSearch = search.trim()
|
|
120
|
+
if (trimmedSearch) {
|
|
121
|
+
params.set('search', trimmedSearch)
|
|
122
|
+
if (searchableFields.length) params.set('searchFields', searchableFields.join(','))
|
|
123
|
+
}
|
|
110
124
|
const s = sorting?.[0]
|
|
111
125
|
if (s?.id) {
|
|
112
126
|
params.set('sortField', String(s.id))
|
|
@@ -154,7 +168,7 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
|
|
|
154
168
|
}
|
|
155
169
|
if (entityId) run()
|
|
156
170
|
return () => { cancelled = true }
|
|
157
|
-
}, [entityId, page, pageSize, sorting, filterValues, scopeVersion])
|
|
171
|
+
}, [entityId, page, pageSize, sorting, filterValues, scopeVersion, search, searchableFields])
|
|
158
172
|
|
|
159
173
|
// Build columns from custom field definitions only (no data round-trip)
|
|
160
174
|
React.useEffect(() => {
|
|
@@ -175,15 +189,9 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
|
|
|
175
189
|
setColumns(cols)
|
|
176
190
|
}, [cfDefs])
|
|
177
191
|
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const q = search.trim().toLowerCase()
|
|
182
|
-
return (rawData || []).filter((row: any) => {
|
|
183
|
-
const values = Object.values(row || {})
|
|
184
|
-
return values.some((v) => normalizeCell(v).toLowerCase().includes(q))
|
|
185
|
-
})
|
|
186
|
-
}, [rawData, search])
|
|
192
|
+
// Search is server-side (see fetch effect); render the fetched page as-is so
|
|
193
|
+
// pagination totals and exports stay consistent with the active search (#3229).
|
|
194
|
+
const data = rawData
|
|
187
195
|
|
|
188
196
|
const viewExportColumns = React.useMemo(() => {
|
|
189
197
|
return (columns || [])
|
|
@@ -213,8 +221,13 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
|
|
|
213
221
|
qp.set('sortField', String(sort.id))
|
|
214
222
|
qp.set('sortDir', sort.desc ? 'desc' : 'asc')
|
|
215
223
|
}
|
|
224
|
+
const trimmedSearch = search.trim()
|
|
225
|
+
if (trimmedSearch) {
|
|
226
|
+
qp.set('search', trimmedSearch)
|
|
227
|
+
if (searchableFields.length) qp.set('searchFields', searchableFields.join(','))
|
|
228
|
+
}
|
|
216
229
|
return `/api/entities/records?${qp.toString()}`
|
|
217
|
-
}, [entityId, sorting])
|
|
230
|
+
}, [entityId, sorting, search, searchableFields])
|
|
218
231
|
|
|
219
232
|
const exportConfig = React.useMemo(() => {
|
|
220
233
|
const safeEntityId = entityId.replace(/[^a-z0-9_-]/gi, '_') || 'records'
|