@open-mercato/core 0.9.0 → 0.9.1-develop.7459.1.99e3accd30
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/generated/entities/customer_dictionary_entry/index.js +2 -0
- package/dist/generated/entities/customer_dictionary_entry/index.js.map +2 -2
- package/dist/generated/entities/customer_interaction/index.js +2 -0
- package/dist/generated/entities/customer_interaction/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +1 -1
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/productDeleteChildren.js +389 -0
- package/dist/modules/catalog/commands/productDeleteChildren.js.map +7 -0
- package/dist/modules/catalog/commands/products.js +52 -17
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +47 -11
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +3 -3
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +5 -1
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
- package/dist/modules/communication_channels/lib/inbound-message-origin.js +1 -1
- package/dist/modules/communication_channels/lib/inbound-message-origin.js.map +2 -2
- package/dist/modules/communication_channels/lib/oauth-state.js +4 -1
- package/dist/modules/communication_channels/lib/oauth-state.js.map +2 -2
- package/dist/modules/communication_channels/lib/use-connect-channel.js +10 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js.map +2 -2
- package/dist/modules/customers/api/activity-types/route.js +151 -0
- package/dist/modules/customers/api/activity-types/route.js.map +7 -0
- package/dist/modules/customers/api/deals/[id]/people/route.js +58 -5
- package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +13 -5
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -5
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +3 -0
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/tasks/route.js +1 -0
- package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +18 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +0 -23
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -17
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/calendar-event-types.js +548 -0
- package/dist/modules/customers/calendar-event-types.js.map +7 -0
- package/dist/modules/customers/commands/dictionaries.js +70 -10
- package/dist/modules/customers/commands/dictionaries.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +187 -22
- package/dist/modules/customers/commands/interactions.js.map +3 -3
- package/dist/modules/customers/components/ActivityTypeEditor.js +288 -0
- package/dist/modules/customers/components/ActivityTypeEditor.js.map +7 -0
- package/dist/modules/customers/components/DictionarySettings.js +3 -1
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -297
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +3 -3
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +9 -74
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/TimeGrid.js +20 -16
- package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js +218 -0
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/LocationField.js +7 -3
- package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +5 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PriorityField.js +3 -1
- package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RepeatField.js +9 -1
- package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +15 -7
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +3 -1
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +28 -13
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +3 -3
- package/dist/modules/customers/components/calendar/editor/inputs.js +14 -6
- package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js +54 -0
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js +82 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js.map +7 -0
- package/dist/modules/customers/components/calendar/types.js +1 -0
- package/dist/modules/customers/components/calendar/types.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +78 -343
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/dist/modules/customers/components/detail/CreatePersonDialog.js +12 -11
- package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +7 -3
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealPeopleSection.js +206 -0
- package/dist/modules/customers/components/detail/DealPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js +342 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/PersonCard.js +5 -2
- package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
- package/dist/modules/customers/components/linking/adapters/personAdapter.js +3 -0
- package/dist/modules/customers/components/linking/adapters/personAdapter.js.map +2 -2
- package/dist/modules/customers/data/entities.js +6 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +39 -10
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/di.js +20 -1
- package/dist/modules/customers/di.js.map +2 -2
- package/dist/modules/customers/extension-points.js +13 -0
- package/dist/modules/customers/extension-points.js.map +2 -2
- package/dist/modules/customers/lib/calendar/conflicts.js +59 -16
- package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
- package/dist/modules/customers/lib/calendar/editorPayload.js +34 -16
- package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js +197 -0
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js.map +7 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js +67 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js.map +7 -0
- package/dist/modules/customers/lib/calendar/mapItem.js +5 -2
- package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
- package/dist/modules/customers/lib/calendar/recurrence.js +62 -1
- package/dist/modules/customers/lib/calendar/recurrence.js.map +2 -2
- package/dist/modules/customers/lib/calendar/timezone.js +111 -0
- package/dist/modules/customers/lib/calendar/timezone.js.map +7 -0
- package/dist/modules/customers/lib/interactionReadModel.js +1 -0
- package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +1 -0
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js.map +7 -0
- package/dist/modules/dictionaries/components/DictionaryTable.js +60 -51
- package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
- package/dist/modules/messages/subscribers/message-notification.js +13 -1
- package/dist/modules/messages/subscribers/message-notification.js.map +2 -2
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +201 -53
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +37 -11
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
- package/dist/modules/planner/lib/availabilityMerge.js +101 -38
- package/dist/modules/planner/lib/availabilityMerge.js.map +3 -3
- package/dist/modules/planner/services/plannerAvailabilityService.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +159 -81
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.js +7 -0
- package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +12 -7
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +28 -25
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/generated/entities/customer_dictionary_entry/index.ts +1 -0
- package/generated/entities/customer_interaction/index.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +9 -8
- package/src/modules/auth/i18n/de.json +3 -0
- package/src/modules/auth/i18n/en.json +3 -0
- package/src/modules/auth/i18n/es.json +3 -0
- package/src/modules/auth/i18n/ko.json +3 -0
- package/src/modules/auth/i18n/pl.json +3 -0
- package/src/modules/catalog/commands/categories.ts +1 -1
- package/src/modules/catalog/commands/productDeleteChildren.ts +557 -0
- package/src/modules/catalog/commands/products.ts +58 -17
- package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +46 -10
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +6 -1
- package/src/modules/communication_channels/i18n/de.json +4 -0
- package/src/modules/communication_channels/i18n/en.json +4 -0
- package/src/modules/communication_channels/i18n/es.json +4 -0
- package/src/modules/communication_channels/i18n/ko.json +4 -0
- package/src/modules/communication_channels/i18n/pl.json +4 -0
- package/src/modules/communication_channels/lib/inbound-message-origin.ts +2 -6
- package/src/modules/communication_channels/lib/oauth-state.ts +4 -1
- package/src/modules/communication_channels/lib/use-connect-channel.ts +10 -0
- package/src/modules/customers/api/activity-types/route.ts +154 -0
- package/src/modules/customers/api/deals/[id]/people/route.ts +82 -3
- package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -1
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +18 -2
- package/src/modules/customers/api/interactions/route.ts +4 -0
- package/src/modules/customers/api/interactions/tasks/route.ts +1 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +20 -0
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +0 -29
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -17
- package/src/modules/customers/calendar-event-types.ts +674 -0
- package/src/modules/customers/commands/dictionaries.ts +84 -8
- package/src/modules/customers/commands/interactions.ts +244 -21
- package/src/modules/customers/components/ActivityTypeEditor.tsx +239 -0
- package/src/modules/customers/components/DictionarySettings.tsx +5 -3
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +347 -218
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +18 -96
- package/src/modules/customers/components/calendar/TimeGrid.tsx +20 -18
- package/src/modules/customers/components/calendar/editor/EventTypePanel.tsx +230 -0
- package/src/modules/customers/components/calendar/editor/LocationField.tsx +7 -1
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/PriorityField.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/RepeatField.tsx +10 -0
- package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +16 -4
- package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/hooks.ts +29 -13
- package/src/modules/customers/components/calendar/editor/inputs.tsx +17 -5
- package/src/modules/customers/components/calendar/editor/useCalendarCustomFields.ts +49 -0
- package/src/modules/customers/components/calendar/editor/useEventTypeCatalog.ts +100 -0
- package/src/modules/customers/components/calendar/types.ts +1 -0
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +94 -388
- package/src/modules/customers/components/detail/CreatePersonDialog.tsx +39 -19
- package/src/modules/customers/components/detail/DealForm.tsx +7 -3
- package/src/modules/customers/components/detail/DealPeopleSection.tsx +256 -0
- package/src/modules/customers/components/detail/LinkedPeopleSection.tsx +474 -0
- package/src/modules/customers/components/detail/PersonCard.tsx +12 -4
- package/src/modules/customers/components/detail/types.ts +1 -0
- package/src/modules/customers/components/linking/adapters/personAdapter.tsx +4 -0
- package/src/modules/customers/data/entities.ts +8 -1
- package/src/modules/customers/data/validators.ts +42 -7
- package/src/modules/customers/di.ts +21 -0
- package/src/modules/customers/extension-points.ts +13 -0
- package/src/modules/customers/i18n/de.json +104 -8
- package/src/modules/customers/i18n/en.json +104 -8
- package/src/modules/customers/i18n/es.json +104 -8
- package/src/modules/customers/i18n/ko.json +104 -8
- package/src/modules/customers/i18n/pl.json +104 -8
- package/src/modules/customers/lib/calendar/conflicts.ts +106 -27
- package/src/modules/customers/lib/calendar/editorPayload.ts +39 -19
- package/src/modules/customers/lib/calendar/eventTypeResolver.ts +258 -0
- package/src/modules/customers/lib/calendar/interactionApplicability.ts +99 -0
- package/src/modules/customers/lib/calendar/mapItem.ts +6 -2
- package/src/modules/customers/lib/calendar/recurrence.ts +65 -1
- package/src/modules/customers/lib/calendar/timezone.ts +117 -0
- package/src/modules/customers/lib/interactionReadModel.ts +1 -0
- package/src/modules/customers/lib/todoCompatibility.ts +2 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +34 -0
- package/src/modules/customers/migrations/Migration20260928175058_customers.ts +15 -0
- package/src/modules/customers/migrations/Migration20260930110235_customers.ts +15 -0
- package/src/modules/dictionaries/components/DictionaryTable.tsx +63 -56
- package/src/modules/messages/subscribers/message-notification.ts +13 -3
- package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +243 -62
- package/src/modules/payment_gateways/lib/gateway-service.ts +39 -12
- package/src/modules/planner/lib/availabilityMerge.ts +106 -41
- package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
- package/src/modules/sales/components/documents/LineItemDialog.tsx +194 -111
- package/src/modules/sales/components/documents/lineItemUtils.ts +18 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
- package/src/modules/sync_excel/lib/adapters/customers.ts +33 -27
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { loadCustomFieldSnapshot } from '@open-mercato/shared/lib/commands/customFieldSnapshots'
|
|
3
|
+
import { setCustomFieldsIfAny } from '@open-mercato/shared/lib/commands/helpers'
|
|
4
|
+
import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
5
|
+
import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
|
|
6
|
+
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
7
|
+
import { E } from '#generated/entities.ids.generated'
|
|
8
|
+
import {
|
|
9
|
+
CatalogOffer,
|
|
10
|
+
CatalogOptionSchemaTemplate,
|
|
11
|
+
CatalogPriceKind,
|
|
12
|
+
CatalogProduct,
|
|
13
|
+
CatalogProductPrice,
|
|
14
|
+
CatalogProductUnitConversion,
|
|
15
|
+
CatalogProductVariant,
|
|
16
|
+
} from '../data/entities'
|
|
17
|
+
import type { CatalogGtinType, CatalogProductOptionSchema } from '../data/types'
|
|
18
|
+
import { cloneJson, emitCatalogQueryIndexEvent } from './shared'
|
|
19
|
+
|
|
20
|
+
type VariantDimensions = NonNullable<CatalogProductVariant['dimensions']>
|
|
21
|
+
|
|
22
|
+
export type ProductDeleteVariantSnapshot = {
|
|
23
|
+
id: string
|
|
24
|
+
productId: string
|
|
25
|
+
name: string | null
|
|
26
|
+
sku: string | null
|
|
27
|
+
barcode: string | null
|
|
28
|
+
gtinType: CatalogGtinType | null
|
|
29
|
+
hsCode: string | null
|
|
30
|
+
statusEntryId: string | null
|
|
31
|
+
isDefault: boolean
|
|
32
|
+
isActive: boolean
|
|
33
|
+
weightValue: string | null
|
|
34
|
+
weightUnit: string | null
|
|
35
|
+
dimensions: VariantDimensions | null
|
|
36
|
+
metadata: Record<string, unknown> | null
|
|
37
|
+
taxRateId: string | null
|
|
38
|
+
taxRate: string | null
|
|
39
|
+
optionValues: Record<string, string> | null
|
|
40
|
+
defaultMediaId: string | null
|
|
41
|
+
defaultMediaUrl: string | null
|
|
42
|
+
customFieldsetCode: string | null
|
|
43
|
+
createdAt: string
|
|
44
|
+
updatedAt: string
|
|
45
|
+
deletedAt: string | null
|
|
46
|
+
custom: Record<string, unknown> | null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type ProductDeletePriceSnapshot = {
|
|
50
|
+
id: string
|
|
51
|
+
variantId: string | null
|
|
52
|
+
productId: string | null
|
|
53
|
+
offerId: string | null
|
|
54
|
+
priceKindId: string
|
|
55
|
+
currencyCode: string
|
|
56
|
+
kind: string
|
|
57
|
+
minQuantity: number
|
|
58
|
+
maxQuantity: number | null
|
|
59
|
+
unitPriceNet: string | null
|
|
60
|
+
unitPriceGross: string | null
|
|
61
|
+
taxRate: string | null
|
|
62
|
+
taxAmount: string | null
|
|
63
|
+
channelId: string | null
|
|
64
|
+
userId: string | null
|
|
65
|
+
userGroupId: string | null
|
|
66
|
+
customerId: string | null
|
|
67
|
+
customerGroupId: string | null
|
|
68
|
+
metadata: Record<string, unknown> | null
|
|
69
|
+
startsAt: string | null
|
|
70
|
+
endsAt: string | null
|
|
71
|
+
createdAt: string
|
|
72
|
+
updatedAt: string
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type ProductDeleteUnitConversionSnapshot = {
|
|
76
|
+
id: string
|
|
77
|
+
productId: string
|
|
78
|
+
unitCode: string
|
|
79
|
+
toBaseFactor: string
|
|
80
|
+
sortOrder: number
|
|
81
|
+
isActive: boolean
|
|
82
|
+
metadata: Record<string, unknown> | null
|
|
83
|
+
createdAt: string
|
|
84
|
+
updatedAt: string
|
|
85
|
+
deletedAt: string | null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type ProductDeleteOptionSchemaSnapshot = {
|
|
89
|
+
id: string
|
|
90
|
+
name: string
|
|
91
|
+
code: string
|
|
92
|
+
description: string | null
|
|
93
|
+
schema: CatalogProductOptionSchema
|
|
94
|
+
metadata: Record<string, unknown> | null
|
|
95
|
+
isActive: boolean
|
|
96
|
+
createdAt: string
|
|
97
|
+
updatedAt: string
|
|
98
|
+
deletedAt: string | null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type ProductDeleteChildrenSnapshot = {
|
|
102
|
+
variants: ProductDeleteVariantSnapshot[]
|
|
103
|
+
prices: ProductDeletePriceSnapshot[]
|
|
104
|
+
unitConversions: ProductDeleteUnitConversionSnapshot[]
|
|
105
|
+
optionSchemaTemplate: ProductDeleteOptionSchemaSnapshot | null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export type ProductDeleteOwner = {
|
|
109
|
+
id: string
|
|
110
|
+
organizationId: string
|
|
111
|
+
tenantId: string
|
|
112
|
+
optionSchemaId: string | null
|
|
113
|
+
offerIds: string[]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export type ProductDeleteChildrenRestorePlan = ProductDeleteChildrenSnapshot
|
|
117
|
+
|
|
118
|
+
type EntityRef = { id: string } | string | null | undefined
|
|
119
|
+
|
|
120
|
+
function refId(ref: EntityRef): string | null {
|
|
121
|
+
if (!ref) return null
|
|
122
|
+
return typeof ref === 'string' ? ref : ref.id
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function toIso(value: Date | null | undefined): string | null {
|
|
126
|
+
return value ? value.toISOString() : null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function toDate(value: string | null | undefined): Date | null {
|
|
130
|
+
return value ? new Date(value) : null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function isProductOwnedOptionSchemaTemplate(
|
|
134
|
+
template: CatalogOptionSchemaTemplate | string | null | undefined,
|
|
135
|
+
): template is CatalogOptionSchemaTemplate {
|
|
136
|
+
if (!template || typeof template === 'string') return false
|
|
137
|
+
const metadata = template.metadata
|
|
138
|
+
if (!metadata || typeof metadata !== 'object') return false
|
|
139
|
+
const source = (metadata as Record<string, unknown>).source
|
|
140
|
+
return source === 'product'
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function captureProductDeleteChildren(
|
|
144
|
+
em: EntityManager,
|
|
145
|
+
owner: Pick<ProductDeleteOwner, 'id' | 'organizationId' | 'tenantId' | 'optionSchemaId'>,
|
|
146
|
+
): Promise<ProductDeleteChildrenSnapshot> {
|
|
147
|
+
const scope = { tenantId: owner.tenantId, organizationId: owner.organizationId }
|
|
148
|
+
const variantRecords = await findWithDecryption(em, CatalogProductVariant, { product: owner.id }, {}, scope)
|
|
149
|
+
const variantIds = variantRecords.map((variant) => variant.id)
|
|
150
|
+
const priceRecords = await findWithDecryption(
|
|
151
|
+
em,
|
|
152
|
+
CatalogProductPrice,
|
|
153
|
+
variantIds.length ? { $or: [{ variant: { $in: variantIds } }, { product: owner.id }] } : { product: owner.id },
|
|
154
|
+
{},
|
|
155
|
+
scope,
|
|
156
|
+
)
|
|
157
|
+
const conversionRecords = await findWithDecryption(em, CatalogProductUnitConversion, { product: owner.id }, {}, scope)
|
|
158
|
+
const template = owner.optionSchemaId
|
|
159
|
+
? await findOneWithDecryption(em, CatalogOptionSchemaTemplate, { id: owner.optionSchemaId }, {}, scope)
|
|
160
|
+
: null
|
|
161
|
+
const variants: ProductDeleteVariantSnapshot[] = []
|
|
162
|
+
for (const variant of variantRecords) {
|
|
163
|
+
const custom = await loadCustomFieldSnapshot(em, {
|
|
164
|
+
entityId: E.catalog.catalog_product_variant,
|
|
165
|
+
recordId: variant.id,
|
|
166
|
+
tenantId: variant.tenantId,
|
|
167
|
+
organizationId: variant.organizationId,
|
|
168
|
+
})
|
|
169
|
+
variants.push({
|
|
170
|
+
id: variant.id,
|
|
171
|
+
productId: refId(variant.product) ?? owner.id,
|
|
172
|
+
name: variant.name ?? null,
|
|
173
|
+
sku: variant.sku ?? null,
|
|
174
|
+
barcode: variant.barcode ?? null,
|
|
175
|
+
gtinType: variant.gtinType ?? null,
|
|
176
|
+
hsCode: variant.hsCode ?? null,
|
|
177
|
+
statusEntryId: variant.statusEntryId ?? null,
|
|
178
|
+
isDefault: variant.isDefault,
|
|
179
|
+
isActive: variant.isActive,
|
|
180
|
+
weightValue: variant.weightValue ?? null,
|
|
181
|
+
weightUnit: variant.weightUnit ?? null,
|
|
182
|
+
dimensions: variant.dimensions ? cloneJson(variant.dimensions) : null,
|
|
183
|
+
metadata: variant.metadata ? cloneJson(variant.metadata) : null,
|
|
184
|
+
taxRateId: variant.taxRateId ?? null,
|
|
185
|
+
taxRate: variant.taxRate ?? null,
|
|
186
|
+
optionValues: variant.optionValues ? cloneJson(variant.optionValues) : null,
|
|
187
|
+
defaultMediaId: variant.defaultMediaId ?? null,
|
|
188
|
+
defaultMediaUrl: variant.defaultMediaUrl ?? null,
|
|
189
|
+
customFieldsetCode: variant.customFieldsetCode ?? null,
|
|
190
|
+
createdAt: variant.createdAt.toISOString(),
|
|
191
|
+
updatedAt: variant.updatedAt.toISOString(),
|
|
192
|
+
deletedAt: toIso(variant.deletedAt),
|
|
193
|
+
custom: Object.keys(custom).length ? custom : null,
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
const prices: ProductDeletePriceSnapshot[] = []
|
|
197
|
+
for (const price of priceRecords) {
|
|
198
|
+
const priceKindId = refId(price.priceKind)
|
|
199
|
+
if (!priceKindId) throw new Error(`[internal] Catalog price ${price.id} has no price kind`)
|
|
200
|
+
prices.push({
|
|
201
|
+
id: price.id,
|
|
202
|
+
variantId: refId(price.variant),
|
|
203
|
+
productId: refId(price.product),
|
|
204
|
+
offerId: refId(price.offer),
|
|
205
|
+
priceKindId,
|
|
206
|
+
currencyCode: price.currencyCode,
|
|
207
|
+
kind: price.kind,
|
|
208
|
+
minQuantity: price.minQuantity,
|
|
209
|
+
maxQuantity: price.maxQuantity ?? null,
|
|
210
|
+
unitPriceNet: price.unitPriceNet ?? null,
|
|
211
|
+
unitPriceGross: price.unitPriceGross ?? null,
|
|
212
|
+
taxRate: price.taxRate ?? null,
|
|
213
|
+
taxAmount: price.taxAmount ?? null,
|
|
214
|
+
channelId: price.channelId ?? null,
|
|
215
|
+
userId: price.userId ?? null,
|
|
216
|
+
userGroupId: price.userGroupId ?? null,
|
|
217
|
+
customerId: price.customerId ?? null,
|
|
218
|
+
customerGroupId: price.customerGroupId ?? null,
|
|
219
|
+
metadata: price.metadata ? cloneJson(price.metadata) : null,
|
|
220
|
+
startsAt: toIso(price.startsAt),
|
|
221
|
+
endsAt: toIso(price.endsAt),
|
|
222
|
+
createdAt: price.createdAt.toISOString(),
|
|
223
|
+
updatedAt: price.updatedAt.toISOString(),
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
const unitConversions: ProductDeleteUnitConversionSnapshot[] = conversionRecords.map((conversion) => ({
|
|
227
|
+
id: conversion.id,
|
|
228
|
+
productId: refId(conversion.product) ?? owner.id,
|
|
229
|
+
unitCode: conversion.unitCode,
|
|
230
|
+
toBaseFactor: conversion.toBaseFactor,
|
|
231
|
+
sortOrder: conversion.sortOrder,
|
|
232
|
+
isActive: conversion.isActive,
|
|
233
|
+
metadata: conversion.metadata ? cloneJson(conversion.metadata) : null,
|
|
234
|
+
createdAt: conversion.createdAt.toISOString(),
|
|
235
|
+
updatedAt: conversion.updatedAt.toISOString(),
|
|
236
|
+
deletedAt: toIso(conversion.deletedAt),
|
|
237
|
+
}))
|
|
238
|
+
return {
|
|
239
|
+
variants,
|
|
240
|
+
prices,
|
|
241
|
+
unitConversions,
|
|
242
|
+
optionSchemaTemplate: isProductOwnedOptionSchemaTemplate(template)
|
|
243
|
+
? {
|
|
244
|
+
id: template.id,
|
|
245
|
+
name: template.name,
|
|
246
|
+
code: template.code,
|
|
247
|
+
description: template.description ?? null,
|
|
248
|
+
schema: cloneJson(template.schema),
|
|
249
|
+
metadata: template.metadata ? cloneJson(template.metadata) : null,
|
|
250
|
+
isActive: template.isActive,
|
|
251
|
+
createdAt: template.createdAt.toISOString(),
|
|
252
|
+
updatedAt: template.updatedAt.toISOString(),
|
|
253
|
+
deletedAt: toIso(template.deletedAt),
|
|
254
|
+
}
|
|
255
|
+
: null,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function restoreConflict(kind: string, id: string): Error {
|
|
260
|
+
return new Error(`[internal] Cannot restore catalog product ${kind} ${id}: it does not belong to the restored product`)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function sameScope(row: { tenantId: string; organizationId: string }, owner: ProductDeleteOwner): boolean {
|
|
264
|
+
return row.tenantId === owner.tenantId && row.organizationId === owner.organizationId
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function asList<T>(value: T[] | null | undefined): T[] {
|
|
268
|
+
return Array.isArray(value) ? value : []
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function planProductDeleteChildrenRestore(
|
|
272
|
+
em: EntityManager,
|
|
273
|
+
owner: ProductDeleteOwner,
|
|
274
|
+
children: ProductDeleteChildrenSnapshot | null | undefined,
|
|
275
|
+
): Promise<ProductDeleteChildrenRestorePlan | null> {
|
|
276
|
+
if (!children) return null
|
|
277
|
+
const scope = { tenantId: owner.tenantId, organizationId: owner.organizationId }
|
|
278
|
+
const variantSnapshots = asList(children.variants)
|
|
279
|
+
const priceSnapshots = asList(children.prices)
|
|
280
|
+
const conversionSnapshots = asList(children.unitConversions)
|
|
281
|
+
const templateSnapshot =
|
|
282
|
+
children.optionSchemaTemplate && children.optionSchemaTemplate.id === owner.optionSchemaId
|
|
283
|
+
? children.optionSchemaTemplate
|
|
284
|
+
: null
|
|
285
|
+
const ownVariantIds = new Set(variantSnapshots.map((variant) => variant.id))
|
|
286
|
+
const offerIds = new Set(owner.offerIds)
|
|
287
|
+
|
|
288
|
+
const priceOwned = (price: { variantId: string | null; productId: string | null }) =>
|
|
289
|
+
(price.variantId !== null && ownVariantIds.has(price.variantId)) || price.productId === owner.id
|
|
290
|
+
|
|
291
|
+
for (const variant of variantSnapshots) {
|
|
292
|
+
if (variant.productId !== owner.id) throw restoreConflict('variant', variant.id)
|
|
293
|
+
}
|
|
294
|
+
for (const conversion of conversionSnapshots) {
|
|
295
|
+
if (conversion.productId !== owner.id) throw restoreConflict('unit conversion', conversion.id)
|
|
296
|
+
}
|
|
297
|
+
for (const price of priceSnapshots) {
|
|
298
|
+
if (!priceOwned(price)) throw restoreConflict('price', price.id)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const foreignVariantIds = new Set<string>()
|
|
302
|
+
const foreignProductIds = new Set<string>()
|
|
303
|
+
const trackVariant = (id: string | null) => {
|
|
304
|
+
if (id && !ownVariantIds.has(id)) foreignVariantIds.add(id)
|
|
305
|
+
}
|
|
306
|
+
const trackProduct = (id: string | null) => {
|
|
307
|
+
if (id && id !== owner.id) foreignProductIds.add(id)
|
|
308
|
+
}
|
|
309
|
+
for (const price of priceSnapshots) {
|
|
310
|
+
trackVariant(price.variantId)
|
|
311
|
+
trackProduct(price.productId)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const existingVariants = variantSnapshots.length
|
|
315
|
+
? await findWithDecryption(em, CatalogProductVariant, { id: { $in: variantSnapshots.map((entry) => entry.id) } }, {}, scope)
|
|
316
|
+
: []
|
|
317
|
+
const existingPrices = priceSnapshots.length
|
|
318
|
+
? await findWithDecryption(em, CatalogProductPrice, { id: { $in: priceSnapshots.map((entry) => entry.id) } }, {}, scope)
|
|
319
|
+
: []
|
|
320
|
+
const existingConversions = conversionSnapshots.length
|
|
321
|
+
? await findWithDecryption(
|
|
322
|
+
em,
|
|
323
|
+
CatalogProductUnitConversion,
|
|
324
|
+
{ id: { $in: conversionSnapshots.map((entry) => entry.id) } },
|
|
325
|
+
{},
|
|
326
|
+
scope,
|
|
327
|
+
)
|
|
328
|
+
: []
|
|
329
|
+
const existingTemplate = templateSnapshot
|
|
330
|
+
? await findOneWithDecryption(em, CatalogOptionSchemaTemplate, { id: templateSnapshot.id }, {}, scope)
|
|
331
|
+
: null
|
|
332
|
+
const foreignVariants = foreignVariantIds.size
|
|
333
|
+
? await findWithDecryption(
|
|
334
|
+
em,
|
|
335
|
+
CatalogProductVariant,
|
|
336
|
+
{ id: { $in: Array.from(foreignVariantIds) }, tenantId: owner.tenantId, organizationId: owner.organizationId },
|
|
337
|
+
{},
|
|
338
|
+
scope,
|
|
339
|
+
)
|
|
340
|
+
: []
|
|
341
|
+
const foreignProducts = foreignProductIds.size
|
|
342
|
+
? await findWithDecryption(
|
|
343
|
+
em,
|
|
344
|
+
CatalogProduct,
|
|
345
|
+
{ id: { $in: Array.from(foreignProductIds) }, tenantId: owner.tenantId, organizationId: owner.organizationId },
|
|
346
|
+
{},
|
|
347
|
+
scope,
|
|
348
|
+
)
|
|
349
|
+
: []
|
|
350
|
+
|
|
351
|
+
for (const variant of existingVariants) {
|
|
352
|
+
if (!sameScope(variant, owner) || refId(variant.product) !== owner.id) throw restoreConflict('variant', variant.id)
|
|
353
|
+
}
|
|
354
|
+
for (const conversion of existingConversions) {
|
|
355
|
+
if (!sameScope(conversion, owner) || refId(conversion.product) !== owner.id) {
|
|
356
|
+
throw restoreConflict('unit conversion', conversion.id)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
for (const price of existingPrices) {
|
|
360
|
+
if (!sameScope(price, owner) || !priceOwned({ variantId: refId(price.variant), productId: refId(price.product) })) {
|
|
361
|
+
throw restoreConflict('price', price.id)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (existingTemplate && !sameScope(existingTemplate, owner)) {
|
|
365
|
+
throw restoreConflict('option schema', existingTemplate.id)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const existingVariantIds = new Set(existingVariants.map((entry) => entry.id))
|
|
369
|
+
const existingPriceIds = new Set(existingPrices.map((entry) => entry.id))
|
|
370
|
+
const existingConversionIds = new Set(existingConversions.map((entry) => entry.id))
|
|
371
|
+
const availableForeignVariantIds = new Set(foreignVariants.map((entry) => entry.id))
|
|
372
|
+
const availableForeignProductIds = new Set(foreignProducts.map((entry) => entry.id))
|
|
373
|
+
const variantAvailable = (id: string | null) =>
|
|
374
|
+
id === null || ownVariantIds.has(id) || availableForeignVariantIds.has(id)
|
|
375
|
+
const productAvailable = (id: string | null) =>
|
|
376
|
+
id === null || id === owner.id || availableForeignProductIds.has(id)
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
optionSchemaTemplate: templateSnapshot && !existingTemplate ? templateSnapshot : null,
|
|
380
|
+
unitConversions: conversionSnapshots.filter((entry) => !existingConversionIds.has(entry.id)),
|
|
381
|
+
variants: variantSnapshots.filter((entry) => !existingVariantIds.has(entry.id)),
|
|
382
|
+
prices: priceSnapshots
|
|
383
|
+
.filter(
|
|
384
|
+
(entry) =>
|
|
385
|
+
!existingPriceIds.has(entry.id) && variantAvailable(entry.variantId) && productAvailable(entry.productId),
|
|
386
|
+
)
|
|
387
|
+
.map((entry) => ({ ...entry, offerId: entry.offerId && offerIds.has(entry.offerId) ? entry.offerId : null })),
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function restoreProductOptionSchemaTemplate(
|
|
392
|
+
em: EntityManager,
|
|
393
|
+
owner: ProductDeleteOwner,
|
|
394
|
+
plan: ProductDeleteChildrenRestorePlan | null,
|
|
395
|
+
): void {
|
|
396
|
+
const snapshot = plan?.optionSchemaTemplate
|
|
397
|
+
if (!snapshot) return
|
|
398
|
+
const template = em.create(CatalogOptionSchemaTemplate, {
|
|
399
|
+
id: snapshot.id,
|
|
400
|
+
organizationId: owner.organizationId,
|
|
401
|
+
tenantId: owner.tenantId,
|
|
402
|
+
name: snapshot.name,
|
|
403
|
+
code: snapshot.code,
|
|
404
|
+
description: snapshot.description ?? null,
|
|
405
|
+
schema: cloneJson(snapshot.schema),
|
|
406
|
+
metadata: snapshot.metadata ? cloneJson(snapshot.metadata) : null,
|
|
407
|
+
isActive: snapshot.isActive,
|
|
408
|
+
createdAt: new Date(snapshot.createdAt),
|
|
409
|
+
updatedAt: new Date(snapshot.updatedAt),
|
|
410
|
+
deletedAt: toDate(snapshot.deletedAt),
|
|
411
|
+
})
|
|
412
|
+
em.persist(template)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Child rows are created on a fork that shares the undo transaction instead of on the
|
|
417
|
+
* EntityManager holding the restored product: the tenant encryption subscriber walks the
|
|
418
|
+
* loaded graph of every created entity, which is quadratic once a product has hundreds of
|
|
419
|
+
* prices attached to already-managed variants.
|
|
420
|
+
*/
|
|
421
|
+
export function buildProductDeleteChildrenRestorePhases(
|
|
422
|
+
em: EntityManager,
|
|
423
|
+
owner: ProductDeleteOwner,
|
|
424
|
+
plan: ProductDeleteChildrenRestorePlan | null,
|
|
425
|
+
): Array<() => Promise<void>> {
|
|
426
|
+
if (!plan) return []
|
|
427
|
+
const scope = { organizationId: owner.organizationId, tenantId: owner.tenantId }
|
|
428
|
+
let childEm: EntityManager | null = null
|
|
429
|
+
const resolveChildEm = (): EntityManager => {
|
|
430
|
+
childEm ??= em.fork({ keepTransactionContext: true })
|
|
431
|
+
return childEm
|
|
432
|
+
}
|
|
433
|
+
return [
|
|
434
|
+
async () => {
|
|
435
|
+
const target = resolveChildEm()
|
|
436
|
+
for (const snapshot of plan.unitConversions) {
|
|
437
|
+
target.persist(
|
|
438
|
+
target.create(CatalogProductUnitConversion, {
|
|
439
|
+
id: snapshot.id,
|
|
440
|
+
...scope,
|
|
441
|
+
product: target.getReference(CatalogProduct, snapshot.productId),
|
|
442
|
+
unitCode: snapshot.unitCode,
|
|
443
|
+
toBaseFactor: snapshot.toBaseFactor,
|
|
444
|
+
sortOrder: snapshot.sortOrder,
|
|
445
|
+
isActive: snapshot.isActive,
|
|
446
|
+
metadata: snapshot.metadata ? cloneJson(snapshot.metadata) : null,
|
|
447
|
+
createdAt: new Date(snapshot.createdAt),
|
|
448
|
+
updatedAt: new Date(snapshot.updatedAt),
|
|
449
|
+
deletedAt: toDate(snapshot.deletedAt),
|
|
450
|
+
}),
|
|
451
|
+
)
|
|
452
|
+
}
|
|
453
|
+
await target.flush()
|
|
454
|
+
},
|
|
455
|
+
async () => {
|
|
456
|
+
const target = resolveChildEm()
|
|
457
|
+
for (const snapshot of plan.variants) {
|
|
458
|
+
target.persist(
|
|
459
|
+
target.create(CatalogProductVariant, {
|
|
460
|
+
id: snapshot.id,
|
|
461
|
+
...scope,
|
|
462
|
+
product: target.getReference(CatalogProduct, snapshot.productId),
|
|
463
|
+
name: snapshot.name ?? null,
|
|
464
|
+
sku: snapshot.sku ?? null,
|
|
465
|
+
barcode: snapshot.barcode ?? null,
|
|
466
|
+
gtinType: snapshot.gtinType ?? null,
|
|
467
|
+
hsCode: snapshot.hsCode ?? null,
|
|
468
|
+
statusEntryId: snapshot.statusEntryId ?? null,
|
|
469
|
+
isDefault: snapshot.isDefault,
|
|
470
|
+
isActive: snapshot.isActive,
|
|
471
|
+
weightValue: snapshot.weightValue ?? null,
|
|
472
|
+
weightUnit: snapshot.weightUnit ?? null,
|
|
473
|
+
dimensions: snapshot.dimensions ? cloneJson(snapshot.dimensions) : null,
|
|
474
|
+
metadata: snapshot.metadata ? cloneJson(snapshot.metadata) : null,
|
|
475
|
+
taxRateId: snapshot.taxRateId ?? null,
|
|
476
|
+
taxRate: snapshot.taxRate ?? null,
|
|
477
|
+
optionValues: snapshot.optionValues ? cloneJson(snapshot.optionValues) : null,
|
|
478
|
+
defaultMediaId: snapshot.defaultMediaId ?? null,
|
|
479
|
+
defaultMediaUrl: snapshot.defaultMediaUrl ?? null,
|
|
480
|
+
customFieldsetCode: snapshot.customFieldsetCode ?? null,
|
|
481
|
+
createdAt: new Date(snapshot.createdAt),
|
|
482
|
+
updatedAt: new Date(snapshot.updatedAt),
|
|
483
|
+
deletedAt: toDate(snapshot.deletedAt),
|
|
484
|
+
}),
|
|
485
|
+
)
|
|
486
|
+
}
|
|
487
|
+
await target.flush()
|
|
488
|
+
},
|
|
489
|
+
async () => {
|
|
490
|
+
const target = resolveChildEm()
|
|
491
|
+
for (const snapshot of plan.prices) {
|
|
492
|
+
target.persist(
|
|
493
|
+
target.create(CatalogProductPrice, {
|
|
494
|
+
id: snapshot.id,
|
|
495
|
+
...scope,
|
|
496
|
+
variant: snapshot.variantId ? target.getReference(CatalogProductVariant, snapshot.variantId) : null,
|
|
497
|
+
product: snapshot.productId ? target.getReference(CatalogProduct, snapshot.productId) : null,
|
|
498
|
+
offer: snapshot.offerId ? target.getReference(CatalogOffer, snapshot.offerId) : null,
|
|
499
|
+
priceKind: target.getReference(CatalogPriceKind, snapshot.priceKindId),
|
|
500
|
+
currencyCode: snapshot.currencyCode,
|
|
501
|
+
kind: snapshot.kind,
|
|
502
|
+
minQuantity: snapshot.minQuantity,
|
|
503
|
+
maxQuantity: snapshot.maxQuantity ?? null,
|
|
504
|
+
unitPriceNet: snapshot.unitPriceNet ?? null,
|
|
505
|
+
unitPriceGross: snapshot.unitPriceGross ?? null,
|
|
506
|
+
taxRate: snapshot.taxRate ?? null,
|
|
507
|
+
taxAmount: snapshot.taxAmount ?? null,
|
|
508
|
+
channelId: snapshot.channelId ?? null,
|
|
509
|
+
userId: snapshot.userId ?? null,
|
|
510
|
+
userGroupId: snapshot.userGroupId ?? null,
|
|
511
|
+
customerId: snapshot.customerId ?? null,
|
|
512
|
+
customerGroupId: snapshot.customerGroupId ?? null,
|
|
513
|
+
metadata: snapshot.metadata ? cloneJson(snapshot.metadata) : null,
|
|
514
|
+
startsAt: toDate(snapshot.startsAt),
|
|
515
|
+
endsAt: toDate(snapshot.endsAt),
|
|
516
|
+
createdAt: new Date(snapshot.createdAt),
|
|
517
|
+
updatedAt: new Date(snapshot.updatedAt),
|
|
518
|
+
}),
|
|
519
|
+
)
|
|
520
|
+
}
|
|
521
|
+
await target.flush()
|
|
522
|
+
},
|
|
523
|
+
]
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export async function emitProductDeleteChildrenRestoreSideEffects(opts: {
|
|
527
|
+
ctx: CommandRuntimeContext
|
|
528
|
+
dataEngine: DataEngine
|
|
529
|
+
owner: ProductDeleteOwner
|
|
530
|
+
children: ProductDeleteChildrenSnapshot | null | undefined
|
|
531
|
+
plan: ProductDeleteChildrenRestorePlan | null
|
|
532
|
+
}): Promise<void> {
|
|
533
|
+
const { ctx, dataEngine, owner, children, plan } = opts
|
|
534
|
+
const variants = asList(children?.variants)
|
|
535
|
+
const createdVariantIds = new Set(asList(plan?.variants).map((variant) => variant.id))
|
|
536
|
+
for (const variant of variants) {
|
|
537
|
+
if (!variant.custom || !Object.keys(variant.custom).length) continue
|
|
538
|
+
await setCustomFieldsIfAny({
|
|
539
|
+
dataEngine,
|
|
540
|
+
entityId: E.catalog.catalog_product_variant,
|
|
541
|
+
recordId: variant.id,
|
|
542
|
+
organizationId: owner.organizationId,
|
|
543
|
+
tenantId: owner.tenantId,
|
|
544
|
+
values: variant.custom,
|
|
545
|
+
})
|
|
546
|
+
}
|
|
547
|
+
for (const variant of variants) {
|
|
548
|
+
await emitCatalogQueryIndexEvent(ctx, {
|
|
549
|
+
entityType: E.catalog.catalog_product_variant,
|
|
550
|
+
recordId: variant.id,
|
|
551
|
+
organizationId: owner.organizationId,
|
|
552
|
+
tenantId: owner.tenantId,
|
|
553
|
+
action: 'created',
|
|
554
|
+
...(createdVariantIds.has(variant.id) ? {} : { coverageBaseDelta: 0 }),
|
|
555
|
+
})
|
|
556
|
+
}
|
|
557
|
+
}
|
|
@@ -76,6 +76,16 @@ import {
|
|
|
76
76
|
findWithDecryption,
|
|
77
77
|
findOneWithDecryption,
|
|
78
78
|
} from "@open-mercato/shared/lib/encryption/find";
|
|
79
|
+
import {
|
|
80
|
+
buildProductDeleteChildrenRestorePhases,
|
|
81
|
+
captureProductDeleteChildren,
|
|
82
|
+
emitProductDeleteChildrenRestoreSideEffects,
|
|
83
|
+
isProductOwnedOptionSchemaTemplate,
|
|
84
|
+
planProductDeleteChildrenRestore,
|
|
85
|
+
restoreProductOptionSchemaTemplate,
|
|
86
|
+
type ProductDeleteChildrenSnapshot,
|
|
87
|
+
type ProductDeleteOwner,
|
|
88
|
+
} from "./productDeleteChildren";
|
|
79
89
|
import { canonicalizeUnitCode } from "../lib/unitCodes";
|
|
80
90
|
import {
|
|
81
91
|
resolveCanonicalUnitCode,
|
|
@@ -255,8 +265,22 @@ function resolveUnitPriceInput(
|
|
|
255
265
|
type ProductUndoPayload = {
|
|
256
266
|
before?: ProductSnapshot | null;
|
|
257
267
|
after?: ProductSnapshot | null;
|
|
268
|
+
children?: ProductDeleteChildrenSnapshot | null;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
type ProductDeleteSnapshots = {
|
|
272
|
+
before?: ProductSnapshot;
|
|
273
|
+
children?: ProductDeleteChildrenSnapshot;
|
|
258
274
|
};
|
|
259
275
|
|
|
276
|
+
const PRODUCT_DELETE_CACHE_ALIASES: readonly string[] = [
|
|
277
|
+
"catalog.product",
|
|
278
|
+
"catalog.variant",
|
|
279
|
+
"catalog.price",
|
|
280
|
+
"catalog.product.unit.conversion",
|
|
281
|
+
"catalog.optionschema",
|
|
282
|
+
];
|
|
283
|
+
|
|
260
284
|
const productCrudEvents: CrudEventsConfig<CatalogProduct> = {
|
|
261
285
|
module: "catalog",
|
|
262
286
|
entity: "product",
|
|
@@ -1055,16 +1079,6 @@ async function emitProductVariantCleanupSideEffects(opts: {
|
|
|
1055
1079
|
}
|
|
1056
1080
|
}
|
|
1057
1081
|
|
|
1058
|
-
function isProductOwnedOptionSchemaTemplate(
|
|
1059
|
-
template: CatalogOptionSchemaTemplate | string | null | undefined,
|
|
1060
|
-
): template is CatalogOptionSchemaTemplate {
|
|
1061
|
-
if (!template || typeof template === "string") return false;
|
|
1062
|
-
const metadata = template.metadata;
|
|
1063
|
-
if (!metadata || typeof metadata !== "object") return false;
|
|
1064
|
-
const source = (metadata as Record<string, unknown>).source;
|
|
1065
|
-
return source === "product";
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
1082
|
async function resolveOptionSchemaTemplateForRemoval(
|
|
1069
1083
|
em: EntityManager,
|
|
1070
1084
|
product: CatalogProduct,
|
|
@@ -2200,11 +2214,14 @@ const deleteProductCommand: CommandHandler<
|
|
|
2200
2214
|
const id = requireId(input, "Product id is required");
|
|
2201
2215
|
const em = ctx.container.resolve("em") as EntityManager;
|
|
2202
2216
|
const snapshot = await loadProductSnapshot(em, id);
|
|
2203
|
-
if (snapshot) {
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2217
|
+
if (!snapshot) return {};
|
|
2218
|
+
ensureTenantScope(ctx, snapshot.tenantId);
|
|
2219
|
+
ensureOrganizationScope(ctx, snapshot.organizationId);
|
|
2220
|
+
const snapshots: ProductDeleteSnapshots = {
|
|
2221
|
+
before: snapshot,
|
|
2222
|
+
children: await captureProductDeleteChildren(em.fork(), snapshot),
|
|
2223
|
+
};
|
|
2224
|
+
return snapshots;
|
|
2208
2225
|
},
|
|
2209
2226
|
async execute(input, ctx) {
|
|
2210
2227
|
const id = requireId(input, "Product id is required");
|
|
@@ -2298,16 +2315,34 @@ const deleteProductCommand: CommandHandler<
|
|
|
2298
2315
|
payload: {
|
|
2299
2316
|
undo: {
|
|
2300
2317
|
before,
|
|
2318
|
+
children: (snapshots as ProductDeleteSnapshots).children ?? null,
|
|
2301
2319
|
} satisfies ProductUndoPayload,
|
|
2302
2320
|
},
|
|
2321
|
+
context: { cacheAliases: [...PRODUCT_DELETE_CACHE_ALIASES] },
|
|
2303
2322
|
};
|
|
2304
2323
|
},
|
|
2305
2324
|
undo: async ({ logEntry, ctx }) => {
|
|
2306
2325
|
const payload = extractUndoPayload<ProductUndoPayload>(logEntry);
|
|
2307
2326
|
const before = payload?.before;
|
|
2308
2327
|
if (!before) return;
|
|
2328
|
+
ensureTenantScope(ctx, before.tenantId);
|
|
2329
|
+
ensureOrganizationScope(ctx, before.organizationId);
|
|
2309
2330
|
const em = (ctx.container.resolve("em") as EntityManager).fork();
|
|
2331
|
+
const children = payload?.children ?? null;
|
|
2332
|
+
const owner: ProductDeleteOwner = {
|
|
2333
|
+
id: before.id,
|
|
2334
|
+
organizationId: before.organizationId,
|
|
2335
|
+
tenantId: before.tenantId,
|
|
2336
|
+
optionSchemaId: before.optionSchemaId ?? null,
|
|
2337
|
+
offerIds: (before.offers ?? []).map((offer) => offer.id),
|
|
2338
|
+
};
|
|
2310
2339
|
let record = await findOneWithDecryption(em, CatalogProduct, { id: before.id });
|
|
2340
|
+
const childrenPlan = await planProductDeleteChildrenRestore(
|
|
2341
|
+
em,
|
|
2342
|
+
owner,
|
|
2343
|
+
children,
|
|
2344
|
+
);
|
|
2345
|
+
restoreProductOptionSchemaTemplate(em, owner, childrenPlan);
|
|
2311
2346
|
if (!record) {
|
|
2312
2347
|
record = em.create(CatalogProduct, {
|
|
2313
2348
|
id: before.id,
|
|
@@ -2346,8 +2381,6 @@ const deleteProductCommand: CommandHandler<
|
|
|
2346
2381
|
});
|
|
2347
2382
|
em.persist(record);
|
|
2348
2383
|
}
|
|
2349
|
-
ensureTenantScope(ctx, before.tenantId);
|
|
2350
|
-
ensureOrganizationScope(ctx, before.organizationId);
|
|
2351
2384
|
applyProductSnapshot(em, record, before);
|
|
2352
2385
|
await withAtomicFlush(
|
|
2353
2386
|
em,
|
|
@@ -2356,6 +2389,7 @@ const deleteProductCommand: CommandHandler<
|
|
|
2356
2389
|
() => restoreOffersFromSnapshot(em, record, before.offers),
|
|
2357
2390
|
() => syncCategoryAssignments(em, record, before.categoryIds),
|
|
2358
2391
|
() => syncProductTags(em, record, before.tags),
|
|
2392
|
+
...buildProductDeleteChildrenRestorePhases(em, owner, childrenPlan),
|
|
2359
2393
|
],
|
|
2360
2394
|
{ transaction: true },
|
|
2361
2395
|
);
|
|
@@ -2370,6 +2404,13 @@ const deleteProductCommand: CommandHandler<
|
|
|
2370
2404
|
values: before.custom,
|
|
2371
2405
|
});
|
|
2372
2406
|
}
|
|
2407
|
+
await emitProductDeleteChildrenRestoreSideEffects({
|
|
2408
|
+
ctx,
|
|
2409
|
+
dataEngine,
|
|
2410
|
+
owner,
|
|
2411
|
+
children,
|
|
2412
|
+
plan: childrenPlan,
|
|
2413
|
+
});
|
|
2373
2414
|
await emitProductCrudUndoChange({
|
|
2374
2415
|
dataEngine,
|
|
2375
2416
|
action: "created",
|