@open-mercato/core 0.6.6-develop.6205.1.109e4b6a84 → 0.6.6-develop.6227.1.2695efff30
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/currencies/backend/currencies/[id]/page.js +33 -8
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +63 -27
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +35 -14
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js +104 -41
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +57 -8
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +5 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +30 -14
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +29 -19
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +166 -64
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
- package/dist/modules/customers/components/AddressFormatSettings.js +31 -16
- package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
- package/dist/modules/customers/components/DictionarySettings.js +57 -31
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/PipelineSettings.js +156 -76
- package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/AgendaList.js +10 -4
- package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +18 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/MonthGrid.js +5 -2
- package/dist/modules/customers/components/calendar/MonthGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +6 -0
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js +2 -0
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/lib/calendar/labels.js +33 -0
- package/dist/modules/customers/lib/calendar/labels.js.map +7 -0
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +32 -11
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
- package/dist/modules/data_sync/api/mappings/[id]/route.js +59 -0
- package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/mappings/route.js +44 -0
- package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
- package/dist/modules/data_sync/api/run.js +31 -0
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +31 -0
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/retry.js +31 -0
- package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +59 -3
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +33 -4
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +54 -2
- package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
- package/dist/modules/directory/backend/directory/branding/page.js +15 -9
- package/dist/modules/directory/backend/directory/branding/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js +28 -9
- package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/page.js +29 -13
- package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
- package/dist/modules/entities/api/definitions.batch.js +15 -0
- package/dist/modules/entities/api/definitions.batch.js.map +2 -2
- package/dist/modules/entities/api/definitions.js +26 -0
- package/dist/modules/entities/api/definitions.js.map +2 -2
- package/dist/modules/entities/api/definitions.mutation-guard.js +57 -0
- package/dist/modules/entities/api/definitions.mutation-guard.js.map +7 -0
- package/dist/modules/entities/api/definitions.restore.js +15 -0
- package/dist/modules/entities/api/definitions.restore.js.map +2 -2
- package/dist/modules/entities/api/entities.js +31 -3
- package/dist/modules/entities/api/entities.js.map +2 -2
- package/dist/modules/entities/api/records.js +18 -0
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +28 -10
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +38 -1
- package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/emails/[id]/route.js +30 -0
- package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js +30 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js +31 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js +31 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +68 -39
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +14 -4
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
- package/dist/modules/payment_gateways/api/cancel/route.js +37 -0
- package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/capture/route.js +37 -0
- package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/guards.js +31 -0
- package/dist/modules/payment_gateways/api/guards.js.map +7 -0
- package/dist/modules/payment_gateways/api/refund/route.js +37 -0
- package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/sessions/route.js +37 -0
- package/dist/modules/payment_gateways/api/sessions/route.js.map +2 -2
- package/dist/modules/planner/api/availability-date-specific.js +11 -1
- package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js +20 -3
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
- package/dist/modules/planner/commands/availability-date-specific.js +16 -0
- package/dist/modules/planner/commands/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +109 -40
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/query_index/api/purge.js +35 -3
- package/dist/modules/query_index/api/purge.js.map +2 -2
- package/dist/modules/query_index/api/reindex.js +41 -3
- package/dist/modules/query_index/api/reindex.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +57 -24
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/shipping_carriers/api/shipments/route.js +31 -0
- package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js +55 -0
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js.map +7 -0
- package/dist/modules/shipping_carriers/data/validators.js +6 -1
- package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +28 -8
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipping-service.js +37 -7
- package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
- package/dist/modules/shipping_carriers/workers/status-poller.js +1 -1
- package/dist/modules/shipping_carriers/workers/status-poller.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +49 -20
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +40 -10
- package/src/modules/currencies/backend/currencies/page.tsx +68 -29
- package/src/modules/currencies/backend/exchange-rates/page.tsx +40 -15
- package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +110 -41
- package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +69 -7
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +16 -8
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +32 -14
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +32 -20
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +171 -64
- package/src/modules/customers/components/AddressFormatSettings.tsx +39 -19
- package/src/modules/customers/components/DictionarySettings.tsx +63 -29
- package/src/modules/customers/components/PipelineSettings.tsx +165 -80
- package/src/modules/customers/components/calendar/AgendaList.tsx +13 -8
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +16 -2
- package/src/modules/customers/components/calendar/MonthGrid.tsx +5 -2
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +6 -0
- package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +2 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +7 -0
- package/src/modules/customers/i18n/de.json +11 -7
- package/src/modules/customers/i18n/en.json +6 -2
- package/src/modules/customers/i18n/es.json +11 -7
- package/src/modules/customers/i18n/pl.json +11 -7
- package/src/modules/customers/lib/calendar/labels.ts +42 -0
- package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +39 -11
- package/src/modules/data_sync/api/mappings/[id]/route.ts +63 -0
- package/src/modules/data_sync/api/mappings/route.ts +48 -0
- package/src/modules/data_sync/api/run.ts +33 -0
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +34 -0
- package/src/modules/data_sync/api/runs/[id]/retry.ts +33 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +64 -2
- package/src/modules/data_sync/api/schedules/route.ts +36 -4
- package/src/modules/directory/api/organization-branding/route.ts +61 -0
- package/src/modules/directory/backend/directory/branding/page.tsx +16 -10
- package/src/modules/directory/backend/directory/organizations/page.tsx +35 -8
- package/src/modules/directory/backend/directory/tenants/page.tsx +37 -13
- package/src/modules/entities/api/definitions.batch.ts +17 -0
- package/src/modules/entities/api/definitions.mutation-guard.ts +80 -0
- package/src/modules/entities/api/definitions.restore.ts +17 -0
- package/src/modules/entities/api/definitions.ts +30 -0
- package/src/modules/entities/api/entities.ts +35 -3
- package/src/modules/entities/api/records.ts +20 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +33 -10
- package/src/modules/feature_toggles/api/overrides/route.ts +44 -1
- package/src/modules/inbox_ops/api/emails/[id]/route.ts +32 -0
- package/src/modules/inbox_ops/api/proposals/[id]/accept-all/route.ts +32 -0
- package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.ts +33 -0
- package/src/modules/inbox_ops/api/proposals/[id]/categorize/route.ts +33 -0
- package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +80 -42
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +21 -4
- package/src/modules/payment_gateways/api/cancel/route.ts +39 -0
- package/src/modules/payment_gateways/api/capture/route.ts +39 -0
- package/src/modules/payment_gateways/api/guards.ts +59 -0
- package/src/modules/payment_gateways/api/refund/route.ts +39 -0
- package/src/modules/payment_gateways/api/sessions/route.ts +40 -0
- package/src/modules/planner/api/availability-date-specific.ts +10 -0
- package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +26 -5
- package/src/modules/planner/commands/availability-date-specific.ts +24 -0
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +122 -41
- package/src/modules/query_index/api/purge.ts +37 -3
- package/src/modules/query_index/api/reindex.ts +43 -3
- package/src/modules/query_index/components/QueryIndexesTable.tsx +66 -24
- package/src/modules/shipping_carriers/api/shipments/route.ts +31 -0
- package/src/modules/shipping_carriers/api/tracking/refresh/route.ts +53 -0
- package/src/modules/shipping_carriers/data/validators.ts +5 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +29 -8
- package/src/modules/shipping_carriers/lib/shipping-service.ts +43 -7
- package/src/modules/shipping_carriers/workers/status-poller.ts +1 -1
- package/src/modules/translations/components/TranslationManager.tsx +65 -21
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
17
17
|
import { apiCall, apiCallOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
|
|
18
18
|
import { createCrud, deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
19
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
19
20
|
import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
20
21
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
21
22
|
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
30
31
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
31
32
|
import { normalizeCrudServerError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
33
|
+
import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
|
|
32
34
|
import { parseAvailabilityRuleWindow } from '@open-mercato/core/modules/planner/lib/availabilitySchedule'
|
|
33
35
|
import { deleteAvailabilityRuleSet } from '@open-mercato/core/modules/planner/lib/deleteAvailabilityRuleSet'
|
|
34
36
|
import { CrudForm, type CrudField } from '@open-mercato/ui/backend/CrudForm'
|
|
@@ -108,6 +110,30 @@ function withOptimisticLockForRuleSet<T>(ruleSet: AvailabilityRuleSet | null | u
|
|
|
108
110
|
return withScopedApiRequestHeaders(buildOptimisticLockHeader(ruleSet?.updatedAt ?? ruleSet?.updated_at ?? null), mutation)
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
/**
|
|
114
|
+
* The date-specific replace endpoint mutates a subject/date aggregate that has
|
|
115
|
+
* no single parent row, so its lock version is the latest `updated_at` of the
|
|
116
|
+
* one-off rules being replaced (mirrors the command-side derivation). Returns
|
|
117
|
+
* null when no existing rules back the date so the header is omitted (additive).
|
|
118
|
+
*/
|
|
119
|
+
function resolveDateSpecificLockToken(rules: AvailabilityRule[]): string | null {
|
|
120
|
+
let latestMs = Number.NEGATIVE_INFINITY
|
|
121
|
+
let latestIso: string | null = null
|
|
122
|
+
for (const rule of rules) {
|
|
123
|
+
const raw = rule.updatedAt ?? rule.updated_at ?? null
|
|
124
|
+
if (!raw) continue
|
|
125
|
+
const ms = Date.parse(raw)
|
|
126
|
+
if (!Number.isFinite(ms) || ms <= latestMs) continue
|
|
127
|
+
latestMs = ms
|
|
128
|
+
latestIso = raw
|
|
129
|
+
}
|
|
130
|
+
return latestIso
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function withOptimisticLockForDateSpecific<T>(rules: AvailabilityRule[], mutation: () => Promise<T>): Promise<T> {
|
|
134
|
+
return withScopedApiRequestHeaders(buildOptimisticLockHeader(resolveDateSpecificLockToken(rules)), mutation)
|
|
135
|
+
}
|
|
136
|
+
|
|
111
137
|
const DAY_LABELS = [
|
|
112
138
|
{ code: 'SU', short: 'S', nameKey: 'schedule.weekday.sunday', fallback: 'Sunday' },
|
|
113
139
|
{ code: 'MO', short: 'M', nameKey: 'schedule.weekday.monday', fallback: 'Monday' },
|
|
@@ -381,6 +407,23 @@ export function AvailabilityRulesEditor({
|
|
|
381
407
|
}: AvailabilityRulesEditorProps) {
|
|
382
408
|
const t = useT()
|
|
383
409
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
410
|
+
const mutationContextId = 'planner-availability-rules-editor'
|
|
411
|
+
const { runMutation, retryLastMutation } = useGuardedMutation<{
|
|
412
|
+
formId: string
|
|
413
|
+
resourceKind: string
|
|
414
|
+
retryLastMutation: () => Promise<boolean>
|
|
415
|
+
}>({
|
|
416
|
+
contextId: mutationContextId,
|
|
417
|
+
blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),
|
|
418
|
+
})
|
|
419
|
+
const mutationContext = React.useMemo(
|
|
420
|
+
() => ({
|
|
421
|
+
formId: mutationContextId,
|
|
422
|
+
resourceKind: 'planner.availability',
|
|
423
|
+
retryLastMutation,
|
|
424
|
+
}),
|
|
425
|
+
[mutationContextId, retryLastMutation],
|
|
426
|
+
)
|
|
384
427
|
const isReadOnly = Boolean(readOnly)
|
|
385
428
|
const canManageUnavailability = allowUnavailability ?? true
|
|
386
429
|
const dialogRef = React.useRef<HTMLDivElement | null>(null)
|
|
@@ -818,16 +861,20 @@ export function AvailabilityRulesEditor({
|
|
|
818
861
|
setIsWeeklyAutoSaving(options?.silentSuccess === true)
|
|
819
862
|
try {
|
|
820
863
|
const windows = buildWeeklyPayload(normalizeWeeklyWindows(weeklyWindowsRef.current))
|
|
821
|
-
await
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
864
|
+
await runMutation({
|
|
865
|
+
operation: () => apiCallOrThrow('/api/planner/availability-weekly', {
|
|
866
|
+
method: 'POST',
|
|
867
|
+
headers: { 'Content-Type': 'application/json' },
|
|
868
|
+
body: JSON.stringify({
|
|
869
|
+
subjectType: subjectForRules,
|
|
870
|
+
subjectId: subjectIdForRules,
|
|
871
|
+
timezone,
|
|
872
|
+
windows,
|
|
873
|
+
}),
|
|
874
|
+
}, { errorMessage: listLabels.saveWeeklyError }),
|
|
875
|
+
context: mutationContext,
|
|
876
|
+
mutationPayload: { action: 'save-weekly', subjectType: subjectForRules, subjectId: subjectIdForRules },
|
|
877
|
+
})
|
|
831
878
|
lastSavedWeeklyKeyRef.current = weeklyKey
|
|
832
879
|
weeklyDirtyRef.current = false
|
|
833
880
|
if (!options?.silentSuccess) {
|
|
@@ -856,6 +903,8 @@ export function AvailabilityRulesEditor({
|
|
|
856
903
|
weeklyHasErrors,
|
|
857
904
|
weeklyKey,
|
|
858
905
|
isReadOnly,
|
|
906
|
+
mutationContext,
|
|
907
|
+
runMutation,
|
|
859
908
|
])
|
|
860
909
|
|
|
861
910
|
React.useEffect(() => {
|
|
@@ -929,7 +978,11 @@ export function AvailabilityRulesEditor({
|
|
|
929
978
|
)))
|
|
930
979
|
}
|
|
931
980
|
if (updates.length) {
|
|
932
|
-
await
|
|
981
|
+
await runMutation({
|
|
982
|
+
operation: () => Promise.all(updates),
|
|
983
|
+
context: mutationContext,
|
|
984
|
+
mutationPayload: { action: 'update-timezone', timezone: trimmedTimezone },
|
|
985
|
+
})
|
|
933
986
|
await refreshAvailability()
|
|
934
987
|
await refreshRuleSetRules()
|
|
935
988
|
}
|
|
@@ -951,6 +1004,8 @@ export function AvailabilityRulesEditor({
|
|
|
951
1004
|
subjectId,
|
|
952
1005
|
subjectType,
|
|
953
1006
|
isReadOnly,
|
|
1007
|
+
mutationContext,
|
|
1008
|
+
runMutation,
|
|
954
1009
|
])
|
|
955
1010
|
|
|
956
1011
|
React.useEffect(() => {
|
|
@@ -990,14 +1045,18 @@ export function AvailabilityRulesEditor({
|
|
|
990
1045
|
kind: rule.kind ?? 'availability',
|
|
991
1046
|
note: rule.note ?? null,
|
|
992
1047
|
}))
|
|
993
|
-
await
|
|
1048
|
+
await runMutation({
|
|
1049
|
+
operation: () => Promise.all(creations),
|
|
1050
|
+
context: mutationContext,
|
|
1051
|
+
mutationPayload: { action: 'customize', count: creations.length },
|
|
1052
|
+
})
|
|
994
1053
|
setCustomOverridesEnabled(true)
|
|
995
1054
|
await refreshAvailability()
|
|
996
1055
|
} catch (error) {
|
|
997
1056
|
const message = error instanceof Error ? error.message : listLabels.saveWeeklyError
|
|
998
1057
|
flash(message, 'error')
|
|
999
1058
|
}
|
|
1000
|
-
}, [effectiveRulesetId, listLabels.saveWeeklyError, refreshAvailability, rulesetRules, subjectId, subjectType, isReadOnly])
|
|
1059
|
+
}, [effectiveRulesetId, listLabels.saveWeeklyError, refreshAvailability, rulesetRules, subjectId, subjectType, isReadOnly, mutationContext, runMutation])
|
|
1001
1060
|
|
|
1002
1061
|
const handleResetToRuleSet = React.useCallback(async () => {
|
|
1003
1062
|
if (isReadOnly) return
|
|
@@ -1012,21 +1071,25 @@ export function AvailabilityRulesEditor({
|
|
|
1012
1071
|
try {
|
|
1013
1072
|
const idsToDelete = selectCustomRuleIdsToDelete('reset', availabilityRules)
|
|
1014
1073
|
const idsToDeleteSet = new Set(idsToDelete)
|
|
1015
|
-
await
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
rule
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1074
|
+
await runMutation({
|
|
1075
|
+
operation: () => Promise.all(
|
|
1076
|
+
availabilityRules
|
|
1077
|
+
.filter((rule) => idsToDeleteSet.has(rule.id))
|
|
1078
|
+
.map((rule) => withOptimisticLockForRule(
|
|
1079
|
+
rule,
|
|
1080
|
+
() => deleteCrud('planner/availability', rule.id, { errorMessage: listLabels.saveWeeklyError }),
|
|
1081
|
+
)),
|
|
1082
|
+
),
|
|
1083
|
+
context: mutationContext,
|
|
1084
|
+
mutationPayload: { action: 'reset-to-ruleset', ids: idsToDelete },
|
|
1085
|
+
})
|
|
1023
1086
|
setCustomOverridesEnabled(false)
|
|
1024
1087
|
await refreshAvailability()
|
|
1025
1088
|
} catch (error) {
|
|
1026
1089
|
const message = error instanceof Error ? error.message : listLabels.saveWeeklyError
|
|
1027
1090
|
flash(message, 'error')
|
|
1028
1091
|
}
|
|
1029
|
-
}, [availabilityRules, confirm, effectiveRulesetId, listLabels.ruleSetConfirm, listLabels.saveWeeklyError, refreshAvailability, isReadOnly])
|
|
1092
|
+
}, [availabilityRules, confirm, effectiveRulesetId, listLabels.ruleSetConfirm, listLabels.saveWeeklyError, refreshAvailability, isReadOnly, mutationContext, runMutation])
|
|
1030
1093
|
|
|
1031
1094
|
const handleRuleSetChange = React.useCallback(async (nextId: string | null) => {
|
|
1032
1095
|
if (isReadOnly) return
|
|
@@ -1035,14 +1098,18 @@ export function AvailabilityRulesEditor({
|
|
|
1035
1098
|
const idsToDelete = selectCustomRuleIdsToDelete('switch', availabilityRules)
|
|
1036
1099
|
if (idsToDelete.length) {
|
|
1037
1100
|
const idsToDeleteSet = new Set(idsToDelete)
|
|
1038
|
-
await
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
rule
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1101
|
+
await runMutation({
|
|
1102
|
+
operation: () => Promise.all(
|
|
1103
|
+
availabilityRules
|
|
1104
|
+
.filter((rule) => idsToDeleteSet.has(rule.id))
|
|
1105
|
+
.map((rule) => withOptimisticLockForRule(
|
|
1106
|
+
rule,
|
|
1107
|
+
() => deleteCrud('planner/availability', rule.id, { errorMessage: listLabels.saveWeeklyError }),
|
|
1108
|
+
)),
|
|
1109
|
+
),
|
|
1110
|
+
context: mutationContext,
|
|
1111
|
+
mutationPayload: { action: 'switch-ruleset', ids: idsToDelete },
|
|
1112
|
+
})
|
|
1046
1113
|
}
|
|
1047
1114
|
setSelectedRulesetId(nextId)
|
|
1048
1115
|
setCustomOverridesEnabled(false)
|
|
@@ -1060,6 +1127,8 @@ export function AvailabilityRulesEditor({
|
|
|
1060
1127
|
onRulesetChange,
|
|
1061
1128
|
refreshAvailability,
|
|
1062
1129
|
isReadOnly,
|
|
1130
|
+
mutationContext,
|
|
1131
|
+
runMutation,
|
|
1063
1132
|
])
|
|
1064
1133
|
|
|
1065
1134
|
const handleDeleteRuleSet = React.useCallback(async () => {
|
|
@@ -1076,9 +1145,13 @@ export function AvailabilityRulesEditor({
|
|
|
1076
1145
|
deleteRuleSet: async (id) => {
|
|
1077
1146
|
// Version-check the schedule delete (the list page already does; the editor
|
|
1078
1147
|
// previously sent no header → last-write-wins on delete). #2055 round-5.
|
|
1079
|
-
await
|
|
1080
|
-
|
|
1081
|
-
|
|
1148
|
+
await runMutation({
|
|
1149
|
+
operation: () => withOptimisticLockForRuleSet(selected, () => (
|
|
1150
|
+
deleteCrud('planner/availability-rule-sets', id, { errorMessage: listLabels.ruleSetDeleteError })
|
|
1151
|
+
)),
|
|
1152
|
+
context: mutationContext,
|
|
1153
|
+
mutationPayload: { action: 'delete-ruleset', id },
|
|
1154
|
+
})
|
|
1082
1155
|
},
|
|
1083
1156
|
clearAssignment: async () => {
|
|
1084
1157
|
setCustomOverridesEnabled(false)
|
|
@@ -1105,6 +1178,8 @@ export function AvailabilityRulesEditor({
|
|
|
1105
1178
|
refreshRuleSets,
|
|
1106
1179
|
ruleSets,
|
|
1107
1180
|
rulesetId,
|
|
1181
|
+
mutationContext,
|
|
1182
|
+
runMutation,
|
|
1108
1183
|
])
|
|
1109
1184
|
|
|
1110
1185
|
const ruleSetFormSchema = React.useMemo(
|
|
@@ -1299,11 +1374,11 @@ export function AvailabilityRulesEditor({
|
|
|
1299
1374
|
unavailabilityReasonEntryId: editorUnavailable ? reasonEntryId : null,
|
|
1300
1375
|
unavailabilityReasonValue: editorUnavailable ? reasonValue : null,
|
|
1301
1376
|
}
|
|
1302
|
-
await apiCallOrThrow('/api/planner/availability-date-specific', {
|
|
1377
|
+
await withOptimisticLockForDateSpecific(editorRules, () => apiCallOrThrow('/api/planner/availability-date-specific', {
|
|
1303
1378
|
method: 'POST',
|
|
1304
1379
|
headers: { 'Content-Type': 'application/json' },
|
|
1305
1380
|
body: JSON.stringify(payload),
|
|
1306
|
-
}, { errorMessage: listLabels.saveDateError })
|
|
1381
|
+
}, { errorMessage: listLabels.saveDateError }))
|
|
1307
1382
|
} else {
|
|
1308
1383
|
const rulesToDelete = editorRules
|
|
1309
1384
|
const uniqueRulesById = new Map(rulesToDelete.map((rule) => [rule.id, rule]))
|
|
@@ -1338,6 +1413,7 @@ export function AvailabilityRulesEditor({
|
|
|
1338
1413
|
await refreshAvailability()
|
|
1339
1414
|
await refreshRuleSetRules()
|
|
1340
1415
|
} catch (error) {
|
|
1416
|
+
if (surfaceRecordConflict(error, t)) return
|
|
1341
1417
|
const message = error instanceof Error ? error.message : listLabels.saveDateError
|
|
1342
1418
|
flash(message, 'error')
|
|
1343
1419
|
}
|
|
@@ -1364,6 +1440,7 @@ export function AvailabilityRulesEditor({
|
|
|
1364
1440
|
usingRuleSet,
|
|
1365
1441
|
canManageUnavailability,
|
|
1366
1442
|
isReadOnly,
|
|
1443
|
+
t,
|
|
1367
1444
|
])
|
|
1368
1445
|
|
|
1369
1446
|
const handleSlotClick = React.useCallback((slot: ScheduleSlot) => {
|
|
@@ -1658,12 +1735,16 @@ export function AvailabilityRulesEditor({
|
|
|
1658
1735
|
variant="outline"
|
|
1659
1736
|
size="icon"
|
|
1660
1737
|
onClick={async () => {
|
|
1661
|
-
await
|
|
1662
|
-
|
|
1663
|
-
rule
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1738
|
+
await runMutation({
|
|
1739
|
+
operation: () => Promise.all(
|
|
1740
|
+
rules.map((rule) => withOptimisticLockForRule(
|
|
1741
|
+
rule,
|
|
1742
|
+
() => deleteCrud('planner/availability', rule.id),
|
|
1743
|
+
)),
|
|
1744
|
+
),
|
|
1745
|
+
context: mutationContext,
|
|
1746
|
+
mutationPayload: { action: 'delete-date-rules', count: rules.length },
|
|
1747
|
+
})
|
|
1667
1748
|
await refreshAvailability()
|
|
1668
1749
|
await refreshRuleSetRules()
|
|
1669
1750
|
}}
|
|
@@ -4,6 +4,10 @@ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
|
4
4
|
import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
5
5
|
import { queryIndexTag, queryIndexErrorSchema, queryIndexOkSchema, queryIndexPurgeRequestSchema } from './openapi'
|
|
6
6
|
import { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'
|
|
7
|
+
import {
|
|
8
|
+
runCrudMutationGuardAfterSuccess,
|
|
9
|
+
validateCrudMutationGuard,
|
|
10
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
7
11
|
|
|
8
12
|
export const metadata = {
|
|
9
13
|
POST: { requireAuth: true, requireFeatures: ['query_index.purge'] },
|
|
@@ -16,12 +20,29 @@ export async function POST(req: Request) {
|
|
|
16
20
|
const entityType = String(body?.entityType || '')
|
|
17
21
|
if (!entityType) return NextResponse.json({ error: 'Missing entityType' }, { status: 400 })
|
|
18
22
|
|
|
19
|
-
const
|
|
23
|
+
const container = await createRequestContainer()
|
|
20
24
|
let em: any | null = null
|
|
21
25
|
try {
|
|
22
|
-
em = resolve('em')
|
|
26
|
+
em = container.resolve('em')
|
|
23
27
|
} catch {}
|
|
24
|
-
const bus = resolve('eventBus') as any
|
|
28
|
+
const bus = container.resolve('eventBus') as any
|
|
29
|
+
|
|
30
|
+
const guardUserId = typeof auth.sub === 'string' ? auth.sub : ''
|
|
31
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
32
|
+
tenantId: auth.tenantId,
|
|
33
|
+
organizationId: auth.orgId,
|
|
34
|
+
userId: guardUserId,
|
|
35
|
+
resourceKind: 'query_index',
|
|
36
|
+
resourceId: entityType,
|
|
37
|
+
operation: 'custom',
|
|
38
|
+
requestMethod: req.method,
|
|
39
|
+
requestHeaders: req.headers,
|
|
40
|
+
mutationPayload: { entityType },
|
|
41
|
+
})
|
|
42
|
+
if (guardResult && !guardResult.ok) {
|
|
43
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
44
|
+
}
|
|
45
|
+
|
|
25
46
|
await recordIndexerLog(
|
|
26
47
|
{ em: em ?? undefined },
|
|
27
48
|
{
|
|
@@ -50,6 +71,19 @@ export async function POST(req: Request) {
|
|
|
50
71
|
organizationId: auth.orgId ?? null,
|
|
51
72
|
},
|
|
52
73
|
).catch(() => undefined)
|
|
74
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
75
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
76
|
+
tenantId: auth.tenantId,
|
|
77
|
+
organizationId: auth.orgId,
|
|
78
|
+
userId: guardUserId,
|
|
79
|
+
resourceKind: 'query_index',
|
|
80
|
+
resourceId: entityType,
|
|
81
|
+
operation: 'custom',
|
|
82
|
+
requestMethod: req.method,
|
|
83
|
+
requestHeaders: req.headers,
|
|
84
|
+
metadata: guardResult.metadata ?? null,
|
|
85
|
+
})
|
|
86
|
+
}
|
|
53
87
|
} catch (error) {
|
|
54
88
|
await recordIndexerLog(
|
|
55
89
|
{ em: em ?? undefined },
|
|
@@ -5,6 +5,10 @@ import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib
|
|
|
5
5
|
import { queryIndexTag, queryIndexErrorSchema, queryIndexOkSchema, queryIndexReindexRequestSchema } from './openapi'
|
|
6
6
|
import { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'
|
|
7
7
|
import { isValidEntityIdShape } from '@open-mercato/shared/lib/query/engine'
|
|
8
|
+
import {
|
|
9
|
+
runCrudMutationGuardAfterSuccess,
|
|
10
|
+
validateCrudMutationGuard,
|
|
11
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
8
12
|
|
|
9
13
|
export const metadata = {
|
|
10
14
|
POST: { requireAuth: true, requireFeatures: ['query_index.reindex'] },
|
|
@@ -31,16 +35,39 @@ export async function POST(req: Request) {
|
|
|
31
35
|
return NextResponse.json({ error: 'partitionIndex must be < partitionCount' }, { status: 400 })
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
const
|
|
38
|
+
const container = await createRequestContainer()
|
|
35
39
|
let em: any | null = null
|
|
36
40
|
try {
|
|
37
|
-
em = resolve('em')
|
|
41
|
+
em = container.resolve('em')
|
|
38
42
|
} catch {}
|
|
39
|
-
const bus = resolve('eventBus') as any
|
|
43
|
+
const bus = container.resolve('eventBus') as any
|
|
40
44
|
const partitions = partitionIndex !== undefined
|
|
41
45
|
? [partitionIndex]
|
|
42
46
|
: Array.from({ length: partitionCount }, (_, idx) => idx)
|
|
43
47
|
const firstPartition = partitions[0] ?? 0
|
|
48
|
+
|
|
49
|
+
const guardUserId = typeof auth.sub === 'string' ? auth.sub : ''
|
|
50
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
51
|
+
tenantId: auth.tenantId,
|
|
52
|
+
organizationId: auth.orgId,
|
|
53
|
+
userId: guardUserId,
|
|
54
|
+
resourceKind: 'query_index',
|
|
55
|
+
resourceId: entityType,
|
|
56
|
+
operation: 'custom',
|
|
57
|
+
requestMethod: req.method,
|
|
58
|
+
requestHeaders: req.headers,
|
|
59
|
+
mutationPayload: {
|
|
60
|
+
entityType,
|
|
61
|
+
force,
|
|
62
|
+
batchSize: batchSize ?? null,
|
|
63
|
+
partitionCount,
|
|
64
|
+
partitionIndex: partitionIndex ?? null,
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
if (guardResult && !guardResult.ok) {
|
|
68
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
69
|
+
}
|
|
70
|
+
|
|
44
71
|
await recordIndexerLog(
|
|
45
72
|
{ em: em ?? undefined },
|
|
46
73
|
{
|
|
@@ -102,6 +129,19 @@ export async function POST(req: Request) {
|
|
|
102
129
|
},
|
|
103
130
|
},
|
|
104
131
|
).catch(() => undefined)
|
|
132
|
+
if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
|
|
133
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
134
|
+
tenantId: auth.tenantId,
|
|
135
|
+
organizationId: auth.orgId,
|
|
136
|
+
userId: guardUserId,
|
|
137
|
+
resourceKind: 'query_index',
|
|
138
|
+
resourceId: entityType,
|
|
139
|
+
operation: 'custom',
|
|
140
|
+
requestMethod: req.method,
|
|
141
|
+
requestHeaders: req.headers,
|
|
142
|
+
metadata: guardResult.metadata ?? null,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
105
145
|
} catch (error) {
|
|
106
146
|
await recordIndexerLog(
|
|
107
147
|
{ em: em ?? undefined },
|
|
@@ -10,6 +10,9 @@ import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
|
10
10
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
11
11
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
12
12
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
13
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
14
|
+
|
|
15
|
+
const MUTATION_CONTEXT_ID = 'query_index.status.list:actions'
|
|
13
16
|
|
|
14
17
|
type Translator = (key: string, params?: Record<string, string | number>) => string
|
|
15
18
|
|
|
@@ -227,6 +230,15 @@ export default function QueryIndexesTable() {
|
|
|
227
230
|
const t = useT()
|
|
228
231
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
229
232
|
const columns = React.useMemo(() => createColumns(t), [t])
|
|
233
|
+
const { runMutation, retryLastMutation } = useGuardedMutation<{
|
|
234
|
+
formId: string
|
|
235
|
+
resourceKind: string
|
|
236
|
+
resourceId: string
|
|
237
|
+
retryLastMutation: () => Promise<boolean>
|
|
238
|
+
}>({
|
|
239
|
+
contextId: MUTATION_CONTEXT_ID,
|
|
240
|
+
blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),
|
|
241
|
+
})
|
|
230
242
|
|
|
231
243
|
const { data, isLoading } = useQuery<Resp>({
|
|
232
244
|
queryKey: ['query-index-status', scopeVersion, refreshSeq],
|
|
@@ -257,11 +269,21 @@ export default function QueryIndexesTable() {
|
|
|
257
269
|
action === 'purge' ? t('query_index.table.actions.purge') : t('query_index.table.actions.reindex')
|
|
258
270
|
const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })
|
|
259
271
|
try {
|
|
260
|
-
await
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
272
|
+
await runMutation({
|
|
273
|
+
operation: () =>
|
|
274
|
+
apiCallOrThrow(`/api/query_index/${action}`, {
|
|
275
|
+
method: 'POST',
|
|
276
|
+
headers: { 'content-type': 'application/json' },
|
|
277
|
+
body: JSON.stringify(body),
|
|
278
|
+
}, { errorMessage }),
|
|
279
|
+
context: {
|
|
280
|
+
formId: MUTATION_CONTEXT_ID,
|
|
281
|
+
resourceKind: 'query_index',
|
|
282
|
+
resourceId: entityId,
|
|
283
|
+
retryLastMutation,
|
|
284
|
+
},
|
|
285
|
+
mutationPayload: { action, entityType: entityId, force: Boolean(opts?.force) },
|
|
286
|
+
})
|
|
265
287
|
} catch (err) {
|
|
266
288
|
// Expected operational failures (e.g. a 503 when a search backend is not
|
|
267
289
|
// configured) — surface a flash toast, not an alert or an error-level
|
|
@@ -272,7 +294,7 @@ export default function QueryIndexesTable() {
|
|
|
272
294
|
}
|
|
273
295
|
qc.invalidateQueries({ queryKey: ['query-index-status'] })
|
|
274
296
|
},
|
|
275
|
-
[qc, t],
|
|
297
|
+
[qc, t, runMutation, retryLastMutation],
|
|
276
298
|
)
|
|
277
299
|
|
|
278
300
|
const triggerVector = React.useCallback(
|
|
@@ -290,14 +312,24 @@ export default function QueryIndexesTable() {
|
|
|
290
312
|
: t('query_index.table.actions.vectorReindex')
|
|
291
313
|
const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })
|
|
292
314
|
try {
|
|
293
|
-
await
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
315
|
+
await runMutation({
|
|
316
|
+
operation: () =>
|
|
317
|
+
apiCallOrThrow('/api/search/embeddings/reindex', {
|
|
318
|
+
method: 'POST',
|
|
319
|
+
headers: { 'content-type': 'application/json' },
|
|
320
|
+
body: JSON.stringify({
|
|
321
|
+
entityId,
|
|
322
|
+
purgeFirst: action === 'purge',
|
|
323
|
+
}),
|
|
324
|
+
}, { errorMessage }),
|
|
325
|
+
context: {
|
|
326
|
+
formId: MUTATION_CONTEXT_ID,
|
|
327
|
+
resourceKind: 'query_index.vector',
|
|
328
|
+
resourceId: entityId,
|
|
329
|
+
retryLastMutation,
|
|
330
|
+
},
|
|
331
|
+
mutationPayload: { action, entityId, purgeFirst: action === 'purge' },
|
|
332
|
+
})
|
|
301
333
|
} catch (err) {
|
|
302
334
|
const message = err instanceof Error && err.message ? err.message : errorMessage
|
|
303
335
|
console.warn('[query_index] vector action failed', message)
|
|
@@ -305,7 +337,7 @@ export default function QueryIndexesTable() {
|
|
|
305
337
|
}
|
|
306
338
|
qc.invalidateQueries({ queryKey: ['query-index-status'] })
|
|
307
339
|
},
|
|
308
|
-
[confirm, qc, t],
|
|
340
|
+
[confirm, qc, t, runMutation, retryLastMutation],
|
|
309
341
|
)
|
|
310
342
|
|
|
311
343
|
const triggerFulltext = React.useCallback(
|
|
@@ -323,14 +355,24 @@ export default function QueryIndexesTable() {
|
|
|
323
355
|
: t('query_index.table.actions.fulltextReindex')
|
|
324
356
|
const errorMessage = t('query_index.table.errors.actionFailed', { action: actionLabel })
|
|
325
357
|
try {
|
|
326
|
-
await
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
358
|
+
await runMutation({
|
|
359
|
+
operation: () =>
|
|
360
|
+
apiCallOrThrow('/api/search/reindex', {
|
|
361
|
+
method: 'POST',
|
|
362
|
+
headers: { 'content-type': 'application/json' },
|
|
363
|
+
body: JSON.stringify({
|
|
364
|
+
action: action === 'purge' ? 'clear' : 'reindex',
|
|
365
|
+
entityId,
|
|
366
|
+
}),
|
|
367
|
+
}, { errorMessage }),
|
|
368
|
+
context: {
|
|
369
|
+
formId: MUTATION_CONTEXT_ID,
|
|
370
|
+
resourceKind: 'query_index.fulltext',
|
|
371
|
+
resourceId: entityId,
|
|
372
|
+
retryLastMutation,
|
|
373
|
+
},
|
|
374
|
+
mutationPayload: { action, entityId },
|
|
375
|
+
})
|
|
334
376
|
} catch (err) {
|
|
335
377
|
const message = err instanceof Error && err.message ? err.message : errorMessage
|
|
336
378
|
console.warn('[query_index] fulltext action failed', message)
|
|
@@ -338,7 +380,7 @@ export default function QueryIndexesTable() {
|
|
|
338
380
|
}
|
|
339
381
|
qc.invalidateQueries({ queryKey: ['query-index-status'] })
|
|
340
382
|
},
|
|
341
|
-
[confirm, qc, t],
|
|
383
|
+
[confirm, qc, t, runMutation, retryLastMutation],
|
|
342
384
|
)
|
|
343
385
|
|
|
344
386
|
return (
|
|
@@ -2,6 +2,10 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
3
3
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
4
|
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
5
|
+
import {
|
|
6
|
+
runCrudMutationGuardAfterSuccess,
|
|
7
|
+
validateCrudMutationGuard,
|
|
8
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
5
9
|
import type { ShippingCarrierService } from '../../lib/shipping-service'
|
|
6
10
|
import { createShipmentSchema } from '../../data/validators'
|
|
7
11
|
import { shippingCarriersTag } from '../openapi'
|
|
@@ -23,12 +27,39 @@ export async function POST(req: Request) {
|
|
|
23
27
|
}
|
|
24
28
|
const container = await createRequestContainer()
|
|
25
29
|
const service = container.resolve('shippingCarrierService') as ShippingCarrierService
|
|
30
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
31
|
+
tenantId: auth.tenantId,
|
|
32
|
+
organizationId: auth.orgId,
|
|
33
|
+
userId: auth.sub ?? '',
|
|
34
|
+
resourceKind: 'shipping_carriers.shipment',
|
|
35
|
+
resourceId: parsed.data.orderId,
|
|
36
|
+
operation: 'create',
|
|
37
|
+
requestMethod: req.method,
|
|
38
|
+
requestHeaders: req.headers,
|
|
39
|
+
mutationPayload: parsed.data as unknown as Record<string, unknown>,
|
|
40
|
+
})
|
|
41
|
+
if (guardResult && !guardResult.ok) {
|
|
42
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
43
|
+
}
|
|
26
44
|
try {
|
|
27
45
|
const shipment = await service.createShipment({
|
|
28
46
|
...parsed.data,
|
|
29
47
|
organizationId: auth.orgId as string,
|
|
30
48
|
tenantId: auth.tenantId,
|
|
31
49
|
})
|
|
50
|
+
if (guardResult?.shouldRunAfterSuccess) {
|
|
51
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
52
|
+
tenantId: auth.tenantId,
|
|
53
|
+
organizationId: auth.orgId,
|
|
54
|
+
userId: auth.sub ?? '',
|
|
55
|
+
resourceKind: 'shipping_carriers.shipment',
|
|
56
|
+
resourceId: shipment.id,
|
|
57
|
+
operation: 'create',
|
|
58
|
+
requestMethod: req.method,
|
|
59
|
+
requestHeaders: req.headers,
|
|
60
|
+
metadata: guardResult.metadata ?? null,
|
|
61
|
+
})
|
|
62
|
+
}
|
|
32
63
|
return NextResponse.json({
|
|
33
64
|
shipmentId: shipment.id,
|
|
34
65
|
carrierShipmentId: shipment.carrierShipmentId,
|