@open-mercato/core 0.6.6-develop.6204.1.30b1f58642 → 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/dictionaries/components/DictionariesManager.js +7 -4
- package/dist/modules/dictionaries/components/DictionariesManager.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/dictionaries/components/DictionariesManager.tsx +7 -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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
5
5
|
import { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
6
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
6
7
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
7
8
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
8
9
|
import { Input } from '@open-mercato/ui/primitives/input'
|
|
@@ -35,6 +36,8 @@ const emptySettings: NotificationDeliveryConfig = {
|
|
|
35
36
|
},
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
const SETTINGS_CONTEXT_ID = 'notifications-settings'
|
|
40
|
+
|
|
38
41
|
export function NotificationSettingsPageClient() {
|
|
39
42
|
const t = useT()
|
|
40
43
|
const [settings, setSettings] = React.useState<NotificationDeliveryConfig | null>(null)
|
|
@@ -42,6 +45,15 @@ export function NotificationSettingsPageClient() {
|
|
|
42
45
|
const [saving, setSaving] = React.useState(false)
|
|
43
46
|
const [error, setError] = React.useState<string | null>(null)
|
|
44
47
|
|
|
48
|
+
const { runMutation, retryLastMutation } = useGuardedMutation<{
|
|
49
|
+
formId: string
|
|
50
|
+
resourceKind: string
|
|
51
|
+
retryLastMutation: () => Promise<boolean>
|
|
52
|
+
}>({
|
|
53
|
+
contextId: SETTINGS_CONTEXT_ID,
|
|
54
|
+
blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),
|
|
55
|
+
})
|
|
56
|
+
|
|
45
57
|
const fetchSettings = React.useCallback(async () => {
|
|
46
58
|
setLoading(true)
|
|
47
59
|
setError(null)
|
|
@@ -96,10 +108,15 @@ export function NotificationSettingsPageClient() {
|
|
|
96
108
|
if (!settings) return
|
|
97
109
|
setSaving(true)
|
|
98
110
|
try {
|
|
99
|
-
const response = await
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
111
|
+
const response = await runMutation({
|
|
112
|
+
operation: () =>
|
|
113
|
+
apiCall<SettingsResponse>('/api/notifications/settings', {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: { 'Content-Type': 'application/json' },
|
|
116
|
+
body: JSON.stringify(settings),
|
|
117
|
+
}),
|
|
118
|
+
context: { formId: SETTINGS_CONTEXT_ID, resourceKind: 'notifications.settings', retryLastMutation },
|
|
119
|
+
mutationPayload: { settings },
|
|
103
120
|
})
|
|
104
121
|
if (!response.ok) {
|
|
105
122
|
const message = response.result?.error || t('notifications.settings.saveError', 'Failed to save notification settings')
|
|
@@ -5,6 +5,13 @@ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
|
5
5
|
import { cancelSchema } from '../../data/validators'
|
|
6
6
|
import type { PaymentGatewayService } from '../../lib/gateway-service'
|
|
7
7
|
import { paymentGatewaysTag } from '../openapi'
|
|
8
|
+
import {
|
|
9
|
+
resolveUserFeatures,
|
|
10
|
+
runPaymentGatewayMutationGuardAfterSuccess,
|
|
11
|
+
runPaymentGatewayMutationGuards,
|
|
12
|
+
} from '../guards'
|
|
13
|
+
|
|
14
|
+
const gatewayTransactionResourceKind = 'payment_gateways.gateway_transaction'
|
|
8
15
|
|
|
9
16
|
export const metadata = {
|
|
10
17
|
path: '/payment_gateways/cancel',
|
|
@@ -24,6 +31,28 @@ export async function POST(req: Request) {
|
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
const container = await createRequestContainer()
|
|
34
|
+
const guardResult = await runPaymentGatewayMutationGuards(
|
|
35
|
+
container,
|
|
36
|
+
{
|
|
37
|
+
tenantId: auth.tenantId,
|
|
38
|
+
organizationId: auth.orgId,
|
|
39
|
+
userId: auth.sub ?? '',
|
|
40
|
+
resourceKind: gatewayTransactionResourceKind,
|
|
41
|
+
resourceId: parsed.data.transactionId,
|
|
42
|
+
operation: 'update',
|
|
43
|
+
requestMethod: req.method,
|
|
44
|
+
requestHeaders: req.headers,
|
|
45
|
+
mutationPayload: parsed.data as Record<string, unknown>,
|
|
46
|
+
},
|
|
47
|
+
resolveUserFeatures(auth),
|
|
48
|
+
)
|
|
49
|
+
if (!guardResult.ok) {
|
|
50
|
+
return NextResponse.json(
|
|
51
|
+
guardResult.errorBody ?? { error: 'Operation blocked by guard' },
|
|
52
|
+
{ status: guardResult.errorStatus ?? 422 },
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
27
56
|
const service = container.resolve('paymentGatewayService') as PaymentGatewayService
|
|
28
57
|
|
|
29
58
|
try {
|
|
@@ -32,6 +61,16 @@ export async function POST(req: Request) {
|
|
|
32
61
|
parsed.data.reason,
|
|
33
62
|
{ organizationId: auth.orgId as string, tenantId: auth.tenantId },
|
|
34
63
|
)
|
|
64
|
+
await runPaymentGatewayMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
|
|
65
|
+
tenantId: auth.tenantId,
|
|
66
|
+
organizationId: auth.orgId,
|
|
67
|
+
userId: auth.sub ?? '',
|
|
68
|
+
resourceKind: gatewayTransactionResourceKind,
|
|
69
|
+
resourceId: parsed.data.transactionId,
|
|
70
|
+
operation: 'update',
|
|
71
|
+
requestMethod: req.method,
|
|
72
|
+
requestHeaders: req.headers,
|
|
73
|
+
})
|
|
35
74
|
return NextResponse.json(result)
|
|
36
75
|
} catch (err: unknown) {
|
|
37
76
|
const message = err instanceof Error ? err.message : 'Cancel failed'
|
|
@@ -5,6 +5,13 @@ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
|
5
5
|
import { captureSchema } from '../../data/validators'
|
|
6
6
|
import type { PaymentGatewayService } from '../../lib/gateway-service'
|
|
7
7
|
import { paymentGatewaysTag } from '../openapi'
|
|
8
|
+
import {
|
|
9
|
+
resolveUserFeatures,
|
|
10
|
+
runPaymentGatewayMutationGuardAfterSuccess,
|
|
11
|
+
runPaymentGatewayMutationGuards,
|
|
12
|
+
} from '../guards'
|
|
13
|
+
|
|
14
|
+
const gatewayTransactionResourceKind = 'payment_gateways.gateway_transaction'
|
|
8
15
|
|
|
9
16
|
export const metadata = {
|
|
10
17
|
path: '/payment_gateways/capture',
|
|
@@ -24,6 +31,28 @@ export async function POST(req: Request) {
|
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
const container = await createRequestContainer()
|
|
34
|
+
const guardResult = await runPaymentGatewayMutationGuards(
|
|
35
|
+
container,
|
|
36
|
+
{
|
|
37
|
+
tenantId: auth.tenantId,
|
|
38
|
+
organizationId: auth.orgId,
|
|
39
|
+
userId: auth.sub ?? '',
|
|
40
|
+
resourceKind: gatewayTransactionResourceKind,
|
|
41
|
+
resourceId: parsed.data.transactionId,
|
|
42
|
+
operation: 'update',
|
|
43
|
+
requestMethod: req.method,
|
|
44
|
+
requestHeaders: req.headers,
|
|
45
|
+
mutationPayload: parsed.data as Record<string, unknown>,
|
|
46
|
+
},
|
|
47
|
+
resolveUserFeatures(auth),
|
|
48
|
+
)
|
|
49
|
+
if (!guardResult.ok) {
|
|
50
|
+
return NextResponse.json(
|
|
51
|
+
guardResult.errorBody ?? { error: 'Operation blocked by guard' },
|
|
52
|
+
{ status: guardResult.errorStatus ?? 422 },
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
27
56
|
const service = container.resolve('paymentGatewayService') as PaymentGatewayService
|
|
28
57
|
|
|
29
58
|
try {
|
|
@@ -32,6 +61,16 @@ export async function POST(req: Request) {
|
|
|
32
61
|
parsed.data.amount,
|
|
33
62
|
{ organizationId: auth.orgId as string, tenantId: auth.tenantId },
|
|
34
63
|
)
|
|
64
|
+
await runPaymentGatewayMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
|
|
65
|
+
tenantId: auth.tenantId,
|
|
66
|
+
organizationId: auth.orgId,
|
|
67
|
+
userId: auth.sub ?? '',
|
|
68
|
+
resourceKind: gatewayTransactionResourceKind,
|
|
69
|
+
resourceId: parsed.data.transactionId,
|
|
70
|
+
operation: 'update',
|
|
71
|
+
requestMethod: req.method,
|
|
72
|
+
requestHeaders: req.headers,
|
|
73
|
+
})
|
|
35
74
|
return NextResponse.json(result)
|
|
36
75
|
} catch (err: unknown) {
|
|
37
76
|
const message = err instanceof Error ? err.message : 'Capture failed'
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { AwilixContainer } from 'awilix'
|
|
2
|
+
import {
|
|
3
|
+
bridgeLegacyGuard,
|
|
4
|
+
runMutationGuards,
|
|
5
|
+
type MutationGuard,
|
|
6
|
+
type MutationGuardInput,
|
|
7
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard-registry'
|
|
8
|
+
|
|
9
|
+
type GuardAfterCallback = {
|
|
10
|
+
guard: MutationGuard
|
|
11
|
+
metadata: Record<string, unknown> | null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function resolveUserFeatures(auth: unknown): string[] {
|
|
15
|
+
const features = (auth as { features?: unknown })?.features
|
|
16
|
+
if (!Array.isArray(features)) return []
|
|
17
|
+
return features.filter((value): value is string => typeof value === 'string')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function runPaymentGatewayMutationGuards(
|
|
21
|
+
container: AwilixContainer,
|
|
22
|
+
input: MutationGuardInput,
|
|
23
|
+
userFeatures: string[],
|
|
24
|
+
): Promise<{
|
|
25
|
+
ok: boolean
|
|
26
|
+
errorBody?: Record<string, unknown>
|
|
27
|
+
errorStatus?: number
|
|
28
|
+
modifiedPayload?: Record<string, unknown>
|
|
29
|
+
afterSuccessCallbacks: GuardAfterCallback[]
|
|
30
|
+
}> {
|
|
31
|
+
const legacyGuard = bridgeLegacyGuard(container)
|
|
32
|
+
if (!legacyGuard) {
|
|
33
|
+
return { ok: true, afterSuccessCallbacks: [] }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return runMutationGuards([legacyGuard], input, { userFeatures })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function runPaymentGatewayMutationGuardAfterSuccess(
|
|
40
|
+
callbacks: GuardAfterCallback[],
|
|
41
|
+
input: {
|
|
42
|
+
tenantId: string
|
|
43
|
+
organizationId: string | null
|
|
44
|
+
userId: string
|
|
45
|
+
resourceKind: string
|
|
46
|
+
resourceId: string
|
|
47
|
+
operation: 'create' | 'update' | 'delete'
|
|
48
|
+
requestMethod: string
|
|
49
|
+
requestHeaders: Headers
|
|
50
|
+
},
|
|
51
|
+
): Promise<void> {
|
|
52
|
+
for (const callback of callbacks) {
|
|
53
|
+
if (!callback.guard.afterSuccess) continue
|
|
54
|
+
await callback.guard.afterSuccess({
|
|
55
|
+
...input,
|
|
56
|
+
metadata: callback.metadata ?? null,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -5,6 +5,13 @@ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
|
5
5
|
import { refundSchema } from '../../data/validators'
|
|
6
6
|
import type { PaymentGatewayService } from '../../lib/gateway-service'
|
|
7
7
|
import { paymentGatewaysTag } from '../openapi'
|
|
8
|
+
import {
|
|
9
|
+
resolveUserFeatures,
|
|
10
|
+
runPaymentGatewayMutationGuardAfterSuccess,
|
|
11
|
+
runPaymentGatewayMutationGuards,
|
|
12
|
+
} from '../guards'
|
|
13
|
+
|
|
14
|
+
const gatewayTransactionResourceKind = 'payment_gateways.gateway_transaction'
|
|
8
15
|
|
|
9
16
|
export const metadata = {
|
|
10
17
|
path: '/payment_gateways/refund',
|
|
@@ -24,6 +31,28 @@ export async function POST(req: Request) {
|
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
const container = await createRequestContainer()
|
|
34
|
+
const guardResult = await runPaymentGatewayMutationGuards(
|
|
35
|
+
container,
|
|
36
|
+
{
|
|
37
|
+
tenantId: auth.tenantId,
|
|
38
|
+
organizationId: auth.orgId,
|
|
39
|
+
userId: auth.sub ?? '',
|
|
40
|
+
resourceKind: gatewayTransactionResourceKind,
|
|
41
|
+
resourceId: parsed.data.transactionId,
|
|
42
|
+
operation: 'update',
|
|
43
|
+
requestMethod: req.method,
|
|
44
|
+
requestHeaders: req.headers,
|
|
45
|
+
mutationPayload: parsed.data as Record<string, unknown>,
|
|
46
|
+
},
|
|
47
|
+
resolveUserFeatures(auth),
|
|
48
|
+
)
|
|
49
|
+
if (!guardResult.ok) {
|
|
50
|
+
return NextResponse.json(
|
|
51
|
+
guardResult.errorBody ?? { error: 'Operation blocked by guard' },
|
|
52
|
+
{ status: guardResult.errorStatus ?? 422 },
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
27
56
|
const service = container.resolve('paymentGatewayService') as PaymentGatewayService
|
|
28
57
|
|
|
29
58
|
try {
|
|
@@ -33,6 +62,16 @@ export async function POST(req: Request) {
|
|
|
33
62
|
parsed.data.reason,
|
|
34
63
|
{ organizationId: auth.orgId as string, tenantId: auth.tenantId },
|
|
35
64
|
)
|
|
65
|
+
await runPaymentGatewayMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
|
|
66
|
+
tenantId: auth.tenantId,
|
|
67
|
+
organizationId: auth.orgId,
|
|
68
|
+
userId: auth.sub ?? '',
|
|
69
|
+
resourceKind: gatewayTransactionResourceKind,
|
|
70
|
+
resourceId: parsed.data.transactionId,
|
|
71
|
+
operation: 'update',
|
|
72
|
+
requestMethod: req.method,
|
|
73
|
+
requestHeaders: req.headers,
|
|
74
|
+
})
|
|
36
75
|
return NextResponse.json(result)
|
|
37
76
|
} catch (err: unknown) {
|
|
38
77
|
const message = err instanceof Error ? err.message : 'Refund failed'
|
|
@@ -5,6 +5,13 @@ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
|
5
5
|
import { createSessionSchema } from '../../data/validators'
|
|
6
6
|
import type { PaymentGatewayService } from '../../lib/gateway-service'
|
|
7
7
|
import { paymentGatewaysTag } from '../openapi'
|
|
8
|
+
import {
|
|
9
|
+
resolveUserFeatures,
|
|
10
|
+
runPaymentGatewayMutationGuardAfterSuccess,
|
|
11
|
+
runPaymentGatewayMutationGuards,
|
|
12
|
+
} from '../guards'
|
|
13
|
+
|
|
14
|
+
const gatewayTransactionResourceKind = 'payment_gateways.gateway_transaction'
|
|
8
15
|
|
|
9
16
|
export const metadata = {
|
|
10
17
|
path: '/payment_gateways/sessions',
|
|
@@ -24,6 +31,28 @@ export async function POST(req: Request) {
|
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
const container = await createRequestContainer()
|
|
34
|
+
const guardResult = await runPaymentGatewayMutationGuards(
|
|
35
|
+
container,
|
|
36
|
+
{
|
|
37
|
+
tenantId: auth.tenantId,
|
|
38
|
+
organizationId: auth.orgId,
|
|
39
|
+
userId: auth.sub ?? '',
|
|
40
|
+
resourceKind: gatewayTransactionResourceKind,
|
|
41
|
+
resourceId: null,
|
|
42
|
+
operation: 'create',
|
|
43
|
+
requestMethod: req.method,
|
|
44
|
+
requestHeaders: req.headers,
|
|
45
|
+
mutationPayload: parsed.data as Record<string, unknown>,
|
|
46
|
+
},
|
|
47
|
+
resolveUserFeatures(auth),
|
|
48
|
+
)
|
|
49
|
+
if (!guardResult.ok) {
|
|
50
|
+
return NextResponse.json(
|
|
51
|
+
guardResult.errorBody ?? { error: 'Operation blocked by guard' },
|
|
52
|
+
{ status: guardResult.errorStatus ?? 422 },
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
27
56
|
const service = container.resolve('paymentGatewayService') as PaymentGatewayService
|
|
28
57
|
|
|
29
58
|
try {
|
|
@@ -46,6 +75,17 @@ export async function POST(req: Request) {
|
|
|
46
75
|
const redirectUrl = session.redirectUrl
|
|
47
76
|
?? (session.clientSession?.type === 'redirect' ? session.clientSession.redirectUrl : null)
|
|
48
77
|
|
|
78
|
+
await runPaymentGatewayMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
|
|
79
|
+
tenantId: auth.tenantId,
|
|
80
|
+
organizationId: auth.orgId,
|
|
81
|
+
userId: auth.sub ?? '',
|
|
82
|
+
resourceKind: gatewayTransactionResourceKind,
|
|
83
|
+
resourceId: transaction.id,
|
|
84
|
+
operation: 'create',
|
|
85
|
+
requestMethod: req.method,
|
|
86
|
+
requestHeaders: req.headers,
|
|
87
|
+
})
|
|
88
|
+
|
|
49
89
|
return NextResponse.json({
|
|
50
90
|
transactionId: transaction.id,
|
|
51
91
|
sessionId: session.sessionId,
|
|
@@ -161,6 +161,16 @@ export const openApi = {
|
|
|
161
161
|
{ status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },
|
|
162
162
|
{ status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
|
|
163
163
|
{ status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },
|
|
164
|
+
{
|
|
165
|
+
status: 409,
|
|
166
|
+
description: 'Conflict — the subject/date availability was modified by another edit',
|
|
167
|
+
schema: z.object({
|
|
168
|
+
error: z.string(),
|
|
169
|
+
code: z.string(),
|
|
170
|
+
currentUpdatedAt: z.string(),
|
|
171
|
+
expectedUpdatedAt: z.string(),
|
|
172
|
+
}),
|
|
173
|
+
},
|
|
164
174
|
],
|
|
165
175
|
},
|
|
166
176
|
},
|
|
@@ -11,6 +11,7 @@ import { RowActions } from '@open-mercato/ui/backend/RowActions'
|
|
|
11
11
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
12
12
|
import { readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
|
|
13
13
|
import { deleteCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
14
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
14
15
|
import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
15
16
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
16
17
|
import { normalizeCrudServerError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
@@ -41,6 +42,23 @@ export default function PlannerAvailabilityRuleSetsPage() {
|
|
|
41
42
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
42
43
|
const router = useRouter()
|
|
43
44
|
const scopeVersion = useOrganizationScopeVersion()
|
|
45
|
+
const mutationContextId = 'planner-availability-rule-sets'
|
|
46
|
+
const { runMutation, retryLastMutation } = useGuardedMutation<{
|
|
47
|
+
formId: string
|
|
48
|
+
resourceKind: string
|
|
49
|
+
retryLastMutation: () => Promise<boolean>
|
|
50
|
+
}>({
|
|
51
|
+
contextId: mutationContextId,
|
|
52
|
+
blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),
|
|
53
|
+
})
|
|
54
|
+
const mutationContext = React.useMemo(
|
|
55
|
+
() => ({
|
|
56
|
+
formId: mutationContextId,
|
|
57
|
+
resourceKind: 'planner.availability_rule_set',
|
|
58
|
+
retryLastMutation,
|
|
59
|
+
}),
|
|
60
|
+
[mutationContextId, retryLastMutation],
|
|
61
|
+
)
|
|
44
62
|
const [rows, setRows] = React.useState<RuleSetRow[]>([])
|
|
45
63
|
const [page, setPage] = React.useState(1)
|
|
46
64
|
const [total, setTotal] = React.useState(0)
|
|
@@ -127,10 +145,13 @@ export default function PlannerAvailabilityRuleSetsPage() {
|
|
|
127
145
|
})
|
|
128
146
|
if (!confirmed) return
|
|
129
147
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
148
|
+
await runMutation({
|
|
149
|
+
operation: () => withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () => (
|
|
150
|
+
deleteCrud('planner/availability-rule-sets', entry.id, { errorMessage: labels.errors.delete })
|
|
151
|
+
)),
|
|
152
|
+
context: mutationContext,
|
|
153
|
+
mutationPayload: { action: 'delete', id: entry.id },
|
|
154
|
+
})
|
|
134
155
|
flash(labels.messages.deleted, 'success')
|
|
135
156
|
handleRefresh()
|
|
136
157
|
} catch (error) {
|
|
@@ -138,7 +159,7 @@ export default function PlannerAvailabilityRuleSetsPage() {
|
|
|
138
159
|
const normalized = normalizeCrudServerError(error)
|
|
139
160
|
flash(normalized.message ?? labels.errors.delete, 'error')
|
|
140
161
|
}
|
|
141
|
-
}, [confirm, handleRefresh, labels.actions.deleteConfirm, labels.errors.delete, labels.messages.deleted])
|
|
162
|
+
}, [confirm, handleRefresh, labels.actions.deleteConfirm, labels.errors.delete, labels.messages.deleted, mutationContext, runMutation])
|
|
142
163
|
|
|
143
164
|
const columns = React.useMemo<ColumnDef<RuleSetRow>[]>(() => [
|
|
144
165
|
{
|
|
@@ -9,10 +9,28 @@ import {
|
|
|
9
9
|
type PlannerAvailabilityDateSpecificReplaceInput,
|
|
10
10
|
} from '../data/validators'
|
|
11
11
|
import { ensureOrganizationScope, ensureTenantScope, extractUndoPayload } from './shared'
|
|
12
|
+
import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
|
|
12
13
|
import type { PlannerAvailabilityKind, PlannerAvailabilitySubjectType } from '../data/entities'
|
|
13
14
|
|
|
14
15
|
const AVAILABILITY_RULE_RESOURCE_KIND = 'planner.availability.rule'
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* The date-specific aggregate (the one-off rules for a subject/date) has no
|
|
19
|
+
* single parent row, so its optimistic-lock version is the latest `updated_at`
|
|
20
|
+
* of the rules being replaced. The editor sends that token via the extension
|
|
21
|
+
* header; an empty set means "nothing to clobber" → no current version → the
|
|
22
|
+
* guard is a no-op (strictly additive, fail-open).
|
|
23
|
+
*/
|
|
24
|
+
function resolveAggregateLockVersion(rules: PlannerAvailabilityRule[]): Date | null {
|
|
25
|
+
let latest: Date | null = null
|
|
26
|
+
for (const rule of rules) {
|
|
27
|
+
const value = rule.updatedAt
|
|
28
|
+
if (!value || Number.isNaN(value.getTime())) continue
|
|
29
|
+
if (!latest || value.getTime() > latest.getTime()) latest = value
|
|
30
|
+
}
|
|
31
|
+
return latest
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
type AvailabilityRuleSnapshot = {
|
|
17
35
|
id: string
|
|
18
36
|
tenantId: string
|
|
@@ -207,6 +225,12 @@ const replaceDateSpecificAvailabilityCommand: CommandHandler<PlannerAvailability
|
|
|
207
225
|
if (window.repeat !== 'once') return false
|
|
208
226
|
return dates.has(formatDateKey(window.startAt))
|
|
209
227
|
})
|
|
228
|
+
enforceCommandOptimisticLock({
|
|
229
|
+
resourceKind: AVAILABILITY_RULE_RESOURCE_KIND,
|
|
230
|
+
resourceId: `${parsed.subjectType}:${parsed.subjectId}`,
|
|
231
|
+
current: resolveAggregateLockVersion(toDelete),
|
|
232
|
+
request: ctx.request ?? null,
|
|
233
|
+
})
|
|
210
234
|
toDelete.forEach((rule) => {
|
|
211
235
|
rule.deletedAt = now
|
|
212
236
|
rule.updatedAt = now
|