@open-mercato/core 0.8.1-develop.7452.1.5e211edd51 → 0.8.1-develop.7455.1.8f7e53badb
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/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/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/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/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/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/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/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/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 +7 -7
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +6 -1
- package/src/modules/customers/api/activity-types/route.ts +154 -0
- 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/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/types.ts +1 -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 +83 -0
- package/src/modules/customers/i18n/en.json +83 -0
- package/src/modules/customers/i18n/es.json +83 -0
- package/src/modules/customers/i18n/ko.json +83 -0
- package/src/modules/customers/i18n/pl.json +83 -0
- 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/planner/lib/availabilityMerge.ts +106 -41
- package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
5
|
+
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
6
|
+
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
7
|
+
import type { OrganizationHierarchyService } from '@open-mercato/shared/lib/auth/principal-service'
|
|
8
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
9
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
10
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
11
|
+
import { resolveDictionaryRouteContext } from '../dictionaries/context'
|
|
12
|
+
import { createDictionaryCacheTags, DICTIONARY_CACHE_TTL_MS } from '../dictionaries/cache'
|
|
13
|
+
import { getInjectionRegistryVersion } from '@open-mercato/shared/modules/widgets/injection-loader'
|
|
14
|
+
import {
|
|
15
|
+
resolveScopedCalendarEventTypes,
|
|
16
|
+
} from '../../lib/calendar/eventTypeResolver'
|
|
17
|
+
import { calendarEventTypeBehaviorSchema } from '../../calendar-event-types'
|
|
18
|
+
import { getCalendarEventTypeRegistryVersion } from '../../calendar-event-types'
|
|
19
|
+
|
|
20
|
+
const logger = createLogger('customers')
|
|
21
|
+
|
|
22
|
+
function reportActivityTypeError(error: unknown, code: string, attributes?: Record<string, string>): void {
|
|
23
|
+
try {
|
|
24
|
+
getTelemetryRuntime()?.reportError(error, { module: 'customers', code, attributes })
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const querySchema = z.object({ organizationId: z.string().uuid().optional() })
|
|
29
|
+
const responseSchema = z.object({
|
|
30
|
+
fallbackKey: z.literal('meeting'),
|
|
31
|
+
items: z.array(z.object({
|
|
32
|
+
key: z.string(),
|
|
33
|
+
label: z.string(),
|
|
34
|
+
labelKey: z.string().optional(),
|
|
35
|
+
icon: z.string().nullable().optional(),
|
|
36
|
+
color: z.string().nullable().optional(),
|
|
37
|
+
behavior: calendarEventTypeBehaviorSchema,
|
|
38
|
+
selectable: z.boolean(),
|
|
39
|
+
panelKey: z.string().optional(),
|
|
40
|
+
source: z.string(),
|
|
41
|
+
provenance: z.record(z.string(), z.unknown()),
|
|
42
|
+
historical: z.boolean(),
|
|
43
|
+
fallbackReason: z.enum(['tombstoned', 'module-unavailable', 'unknown']).optional(),
|
|
44
|
+
adminConfigurable: z.boolean(),
|
|
45
|
+
isInherited: z.boolean(),
|
|
46
|
+
isLocalOverride: z.boolean(),
|
|
47
|
+
updatedAt: z.string().nullable(),
|
|
48
|
+
missingCustomFieldsetIds: z.array(z.string()),
|
|
49
|
+
inactiveDictionaryOverride: z.boolean().optional(),
|
|
50
|
+
})),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
export const metadata = {
|
|
54
|
+
GET: { requireAuth: true },
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function GET(req: Request) {
|
|
58
|
+
try {
|
|
59
|
+
const url = new URL(req.url)
|
|
60
|
+
const query = querySchema.parse({ organizationId: url.searchParams.get('organizationId') ?? undefined })
|
|
61
|
+
const context = await resolveDictionaryRouteContext(req, { selectedId: query.organizationId })
|
|
62
|
+
if (!context.organizationId) {
|
|
63
|
+
return NextResponse.json({ error: context.translate('customers.errors.organization_required', 'Organization context is required') }, { status: 400 })
|
|
64
|
+
}
|
|
65
|
+
const organizationId = context.organizationId
|
|
66
|
+
const rbac = context.container.resolve('rbacService') as RbacService | undefined
|
|
67
|
+
const actorId = context.auth?.sub
|
|
68
|
+
if (!rbac || !actorId) {
|
|
69
|
+
throw new CrudHttpError(403, { error: context.translate('customers.calendar.activityTypes.errors.forbidden', 'You do not have permission to view activity types.') })
|
|
70
|
+
}
|
|
71
|
+
const permissionScope = { tenantId: context.tenantId, organizationId }
|
|
72
|
+
const canViewInteractions = await rbac.userHasAllFeatures(actorId, ['customers.interactions.view'], permissionScope)
|
|
73
|
+
const canManageSettings = canViewInteractions || await rbac.userHasAllFeatures(actorId, ['customers.settings.manage'], permissionScope)
|
|
74
|
+
if (!canManageSettings) {
|
|
75
|
+
throw new CrudHttpError(403, { error: context.translate('customers.calendar.activityTypes.errors.forbidden', 'You do not have permission to view activity types.') })
|
|
76
|
+
}
|
|
77
|
+
const hierarchy = context.container.resolve('organizationHierarchyService') as OrganizationHierarchyService
|
|
78
|
+
const ancestors = await hierarchy.resolveAncestorIds({ tenantId: context.tenantId, organizationId })
|
|
79
|
+
if (ancestors === null) {
|
|
80
|
+
throw new CrudHttpError(403, { error: context.translate('customers.errors.organization_forbidden', 'Organization not accessible') })
|
|
81
|
+
}
|
|
82
|
+
const scopeIds = Array.from(new Set([organizationId, ...ancestors.slice().reverse()]))
|
|
83
|
+
const cacheKey = `customers:activity-types:${context.tenantId}:org=${organizationId}:scope=${scopeIds.join('|')}:widgets=${getInjectionRegistryVersion()}:registry=${getCalendarEventTypeRegistryVersion()}`
|
|
84
|
+
const cached = await context.cache?.get(cacheKey)
|
|
85
|
+
if (cached) return NextResponse.json(cached)
|
|
86
|
+
|
|
87
|
+
let catalog
|
|
88
|
+
try {
|
|
89
|
+
catalog = await resolveScopedCalendarEventTypes({
|
|
90
|
+
em: context.em,
|
|
91
|
+
tenantId: context.tenantId,
|
|
92
|
+
organizationId,
|
|
93
|
+
readableOrganizationIds: scopeIds,
|
|
94
|
+
})
|
|
95
|
+
} catch (err) {
|
|
96
|
+
logger.error('customers.activity_types.resolve failed', {
|
|
97
|
+
err,
|
|
98
|
+
tenantId: context.tenantId,
|
|
99
|
+
organizationId,
|
|
100
|
+
})
|
|
101
|
+
reportActivityTypeError(err, 'customers.activity_type_catalog_resolution_failed', {
|
|
102
|
+
tenantId: context.tenantId,
|
|
103
|
+
organizationId,
|
|
104
|
+
})
|
|
105
|
+
return NextResponse.json({
|
|
106
|
+
error: context.translate('customers.calendar.activityTypes.errors.loadFailed', 'Failed to load activity types'),
|
|
107
|
+
code: 'activity_type_catalog_unavailable',
|
|
108
|
+
}, { status: 503 })
|
|
109
|
+
}
|
|
110
|
+
if (context.cache) {
|
|
111
|
+
await context.cache.set(cacheKey, catalog, {
|
|
112
|
+
ttl: DICTIONARY_CACHE_TTL_MS,
|
|
113
|
+
tags: createDictionaryCacheTags({
|
|
114
|
+
tenantId: context.tenantId,
|
|
115
|
+
mappedKind: 'activity_type',
|
|
116
|
+
organizationIds: scopeIds,
|
|
117
|
+
}),
|
|
118
|
+
}).catch((err) => {
|
|
119
|
+
logger.warn('customers.activity_types cache write failed', { err })
|
|
120
|
+
reportActivityTypeError(err, 'customers.activity_type_catalog_cache_write_failed', {
|
|
121
|
+
tenantId: context.tenantId,
|
|
122
|
+
organizationId,
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
return NextResponse.json(catalog)
|
|
127
|
+
} catch (err) {
|
|
128
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
|
|
129
|
+
logger.error('customers.activity_types request failed', { err })
|
|
130
|
+
reportActivityTypeError(err, 'customers.activity_type_catalog_request_failed')
|
|
131
|
+
const { translate } = await resolveTranslations()
|
|
132
|
+
return NextResponse.json({
|
|
133
|
+
error: translate('customers.calendar.activityTypes.errors.loadFailed', 'Failed to load activity types'),
|
|
134
|
+
}, { status: err instanceof z.ZodError ? 400 : 500 })
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const openApi: OpenApiRouteDoc = {
|
|
139
|
+
tag: 'Customers',
|
|
140
|
+
summary: 'Effective calendar activity types',
|
|
141
|
+
methods: {
|
|
142
|
+
GET: {
|
|
143
|
+
summary: 'List effective calendar activity types',
|
|
144
|
+
description: 'Returns the tenant and organization scoped calendar activity-type catalog. Requires customers.interactions.view or customers.settings.manage.',
|
|
145
|
+
responses: [{ status: 200, description: 'Effective activity-type catalog', schema: responseSchema }],
|
|
146
|
+
errors: [
|
|
147
|
+
{ status: 400, description: 'Organization scope is required', schema: z.object({ error: z.string() }) },
|
|
148
|
+
{ status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
|
|
149
|
+
{ status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },
|
|
150
|
+
{ status: 500, description: 'Unexpected server error', schema: z.object({ error: z.string() }) },
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
18
18
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
19
19
|
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
20
|
+
import { calendarEventTypeBehaviorSchema } from '../../../../calendar-event-types'
|
|
20
21
|
|
|
21
22
|
const logger = createLogger('customers')
|
|
22
23
|
|
|
@@ -30,8 +31,9 @@ const patchSchema = z
|
|
|
30
31
|
label: z.string().trim().max(150).optional(),
|
|
31
32
|
color: z.union([z.string().trim(), z.null()]).optional(),
|
|
32
33
|
icon: z.union([z.string().trim(), z.null()]).optional(),
|
|
34
|
+
behavior: calendarEventTypeBehaviorSchema.nullable().optional(),
|
|
33
35
|
})
|
|
34
|
-
.refine((input) => input.value !== undefined || input.label !== undefined || input.color !== undefined || input.icon !== undefined, {
|
|
36
|
+
.refine((input) => input.value !== undefined || input.label !== undefined || input.color !== undefined || input.icon !== undefined || input.behavior !== undefined, {
|
|
35
37
|
message: 'No changes provided',
|
|
36
38
|
})
|
|
37
39
|
|
|
@@ -81,6 +83,7 @@ export async function PATCH(req: Request, ctx: { params?: { kind?: string; id?:
|
|
|
81
83
|
label: payload.label,
|
|
82
84
|
color: payload.color,
|
|
83
85
|
icon: payload.icon,
|
|
86
|
+
behavior: payload.behavior,
|
|
84
87
|
},
|
|
85
88
|
ctx: routeContext.ctx,
|
|
86
89
|
})) as CommandExecuteResult<{ entryId: string; changed: boolean }>
|
|
@@ -103,6 +106,7 @@ export async function PATCH(req: Request, ctx: { params?: { kind?: string; id?:
|
|
|
103
106
|
),
|
|
104
107
|
})
|
|
105
108
|
}
|
|
109
|
+
if (typeof err.body.code === 'string') throw err
|
|
106
110
|
throw new CrudHttpError(409, { error: routeContext.translate('customers.config.dictionaries.errors.duplicate', 'An entry with this value already exists') })
|
|
107
111
|
}
|
|
108
112
|
if (err.status === 400) {
|
|
@@ -141,8 +145,10 @@ export async function PATCH(req: Request, ctx: { params?: { kind?: string; id?:
|
|
|
141
145
|
label: entry.label,
|
|
142
146
|
color: entry.color,
|
|
143
147
|
icon: entry.icon,
|
|
148
|
+
...(mappedKind === 'activity_type' ? { behavior: entry.activityTypeBehavior ?? null } : {}),
|
|
144
149
|
organizationId: entry.organizationId,
|
|
145
150
|
isInherited: false,
|
|
151
|
+
updatedAt: entry.updatedAt,
|
|
146
152
|
})
|
|
147
153
|
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
148
154
|
response.headers.set(
|
|
@@ -297,8 +303,10 @@ const dictionaryEntrySchema = z.object({
|
|
|
297
303
|
label: z.string().nullable().optional(),
|
|
298
304
|
color: z.string().nullable().optional(),
|
|
299
305
|
icon: z.string().nullable().optional(),
|
|
306
|
+
behavior: calendarEventTypeBehaviorSchema.nullable().optional(),
|
|
300
307
|
organizationId: z.string().uuid().nullable().optional(),
|
|
301
308
|
isInherited: z.boolean().optional(),
|
|
309
|
+
updatedAt: z.date().or(z.string()).optional(),
|
|
302
310
|
})
|
|
303
311
|
|
|
304
312
|
const dictionaryDeleteResponseSchema = z.object({
|
|
@@ -24,6 +24,8 @@ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
|
24
24
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
25
25
|
import { CUSTOMER_DICTIONARY_ORGANIZATION_REQUIRED_CODE } from '../../../lib/dictionaries'
|
|
26
26
|
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
27
|
+
import { calendarEventTypeBehaviorSchema } from '../../../calendar-event-types'
|
|
28
|
+
import type { OrganizationHierarchyService } from '@open-mercato/shared/lib/auth/principal-service'
|
|
27
29
|
|
|
28
30
|
const logger = createLogger('customers')
|
|
29
31
|
|
|
@@ -35,6 +37,7 @@ const postSchema = z.object({
|
|
|
35
37
|
label: z.string().trim().max(150).optional(),
|
|
36
38
|
color: colorSchema.or(z.null()).optional(),
|
|
37
39
|
icon: iconSchema.or(z.null()).optional(),
|
|
40
|
+
behavior: calendarEventTypeBehaviorSchema.nullable().optional(),
|
|
38
41
|
})
|
|
39
42
|
|
|
40
43
|
const querySchema = z.object({
|
|
@@ -52,7 +55,7 @@ export async function GET(req: Request, ctx: { params?: { kind?: string } }) {
|
|
|
52
55
|
const query = querySchema.parse({
|
|
53
56
|
organizationId: url.searchParams.get('organizationId') ?? undefined,
|
|
54
57
|
})
|
|
55
|
-
const { translate, em, organizationId, readableOrganizationIds, tenantId, cache } = await resolveDictionaryRouteContext(req, {
|
|
58
|
+
const { translate, em, organizationId, readableOrganizationIds, tenantId, cache, container } = await resolveDictionaryRouteContext(req, {
|
|
56
59
|
selectedId: query.organizationId ?? undefined,
|
|
57
60
|
})
|
|
58
61
|
const { kind, mappedKind } = mapDictionaryKind(ctx.params?.kind)
|
|
@@ -64,7 +67,15 @@ export async function GET(req: Request, ctx: { params?: { kind?: string } }) {
|
|
|
64
67
|
}
|
|
65
68
|
const settings = await loadCustomerSettings(em, { tenantId, organizationId })
|
|
66
69
|
const sortMode = resolveDictionaryEntrySortMode(settings?.dictionarySortModes?.[kind])
|
|
67
|
-
|
|
70
|
+
let scopedOrganizationIds = readableOrganizationIds.length > 0 ? readableOrganizationIds : [organizationId]
|
|
71
|
+
if (mappedKind === 'activity_type') {
|
|
72
|
+
const hierarchy = container.resolve('organizationHierarchyService') as OrganizationHierarchyService
|
|
73
|
+
const ancestors = await hierarchy.resolveAncestorIds({ tenantId, organizationId })
|
|
74
|
+
if (ancestors === null) {
|
|
75
|
+
throw new CrudHttpError(403, { error: translate('customers.errors.organization_forbidden', 'Organization not accessible') })
|
|
76
|
+
}
|
|
77
|
+
scopedOrganizationIds = Array.from(new Set([organizationId, ...ancestors.slice().reverse()]))
|
|
78
|
+
}
|
|
68
79
|
const canUseCache = Boolean(cache) && mappedKind !== 'person_company_role'
|
|
69
80
|
|
|
70
81
|
let cacheKey: string | null = null
|
|
@@ -133,6 +144,7 @@ export async function GET(req: Request, ctx: { params?: { kind?: string } }) {
|
|
|
133
144
|
label: entry.label,
|
|
134
145
|
color: entry.color,
|
|
135
146
|
icon: entry.icon,
|
|
147
|
+
...(mappedKind === 'activity_type' ? { behavior: entry.activityTypeBehavior ?? null } : {}),
|
|
136
148
|
organizationId: entry.organizationId,
|
|
137
149
|
isInherited: entry.organizationId !== organizationId,
|
|
138
150
|
createdAt: entry.createdAt,
|
|
@@ -213,6 +225,7 @@ export async function POST(req: Request, ctx: { params?: { kind?: string } }) {
|
|
|
213
225
|
label: body.label,
|
|
214
226
|
color: body.color,
|
|
215
227
|
icon: body.icon,
|
|
228
|
+
behavior: body.behavior,
|
|
216
229
|
},
|
|
217
230
|
ctx: context.ctx,
|
|
218
231
|
})) as CommandExecuteResult<{ entryId: string; mode: 'created' | 'updated' | 'unchanged' }>
|
|
@@ -248,8 +261,10 @@ export async function POST(req: Request, ctx: { params?: { kind?: string } }) {
|
|
|
248
261
|
label: entry.label,
|
|
249
262
|
color: entry.color,
|
|
250
263
|
icon: entry.icon,
|
|
264
|
+
...(mappedKind === 'activity_type' ? { behavior: entry.activityTypeBehavior ?? null } : {}),
|
|
251
265
|
organizationId: entry.organizationId,
|
|
252
266
|
isInherited: false,
|
|
267
|
+
updatedAt: entry.updatedAt,
|
|
253
268
|
},
|
|
254
269
|
{ status: result.mode === 'created' ? 201 : 200 }
|
|
255
270
|
)
|
|
@@ -288,6 +303,7 @@ const dictionaryEntrySchema = z.object({
|
|
|
288
303
|
label: z.string().nullable().optional(),
|
|
289
304
|
color: z.string().nullable().optional(),
|
|
290
305
|
icon: z.string().nullable().optional(),
|
|
306
|
+
behavior: calendarEventTypeBehaviorSchema.nullable().optional(),
|
|
291
307
|
organizationId: z.string().uuid().nullable().optional(),
|
|
292
308
|
isInherited: z.boolean().optional(),
|
|
293
309
|
createdAt: z.date().or(z.string()).optional(),
|
|
@@ -179,6 +179,7 @@ type InteractionListRow = {
|
|
|
179
179
|
appearance_icon: string | null
|
|
180
180
|
appearance_color: string | null
|
|
181
181
|
source: string | null
|
|
182
|
+
timezone: string | null
|
|
182
183
|
duration_minutes: number | null
|
|
183
184
|
location: string | null
|
|
184
185
|
all_day: boolean | null
|
|
@@ -314,6 +315,7 @@ const INTERACTION_LIST_COLUMNS = [
|
|
|
314
315
|
'appearance_icon',
|
|
315
316
|
'appearance_color',
|
|
316
317
|
'source',
|
|
318
|
+
'timezone',
|
|
317
319
|
'duration_minutes',
|
|
318
320
|
'location',
|
|
319
321
|
'all_day',
|
|
@@ -713,6 +715,7 @@ export async function GET(req: Request) {
|
|
|
713
715
|
source: row.source ?? null,
|
|
714
716
|
duration: row.duration_minutes ?? null,
|
|
715
717
|
durationMinutes: row.duration_minutes ?? null,
|
|
718
|
+
timezone: row.timezone ?? null,
|
|
716
719
|
location: row.location ?? null,
|
|
717
720
|
allDay: row.all_day ?? null,
|
|
718
721
|
recurrenceRule: row.recurrence_rule ?? null,
|
|
@@ -794,6 +797,7 @@ const interactionListItemSchema = z
|
|
|
794
797
|
source: z.string().nullable().optional(),
|
|
795
798
|
duration: z.number().nullable().optional(),
|
|
796
799
|
durationMinutes: z.number().nullable().optional(),
|
|
800
|
+
timezone: z.string().nullable().optional(),
|
|
797
801
|
location: z.string().nullable().optional(),
|
|
798
802
|
allDay: z.boolean().nullable().optional(),
|
|
799
803
|
recurrenceRule: z.string().nullable().optional(),
|
|
@@ -153,6 +153,7 @@ const todoItemSchema = z.object({
|
|
|
153
153
|
todoSeverity: z.string().nullable().optional(),
|
|
154
154
|
todoDescription: z.string().nullable().optional(),
|
|
155
155
|
todoDueAt: z.string().nullable().optional(),
|
|
156
|
+
todoTimezone: z.string().nullable().optional(),
|
|
156
157
|
todoCustomValues: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
157
158
|
todoOrganizationId: z.string().nullable(),
|
|
158
159
|
organizationId: z.string(),
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
const pipelineIcon = React.createElement(
|
|
4
|
+
'svg',
|
|
5
|
+
{
|
|
6
|
+
width: 16,
|
|
7
|
+
height: 16,
|
|
8
|
+
viewBox: '0 0 24 24',
|
|
9
|
+
fill: 'none',
|
|
10
|
+
stroke: 'currentColor',
|
|
11
|
+
strokeWidth: 2,
|
|
12
|
+
strokeLinecap: 'round',
|
|
13
|
+
strokeLinejoin: 'round',
|
|
14
|
+
},
|
|
15
|
+
React.createElement('rect', { x: 3, y: 4, width: 5, height: 16, rx: 1 }),
|
|
16
|
+
React.createElement('rect', { x: 10, y: 4, width: 5, height: 16, rx: 1 }),
|
|
17
|
+
React.createElement('rect', { x: 17, y: 4, width: 4, height: 16, rx: 1 })
|
|
18
|
+
)
|
|
19
|
+
|
|
1
20
|
export const metadata = {
|
|
2
21
|
requireAuth: true,
|
|
3
22
|
requireFeatures: ['customers.pipelines.manage'],
|
|
@@ -6,6 +25,7 @@ export const metadata = {
|
|
|
6
25
|
pageGroup: 'Module Configs',
|
|
7
26
|
pageGroupKey: 'settings.sections.moduleConfigs',
|
|
8
27
|
pageOrder: 4,
|
|
28
|
+
icon: pipelineIcon,
|
|
9
29
|
pageContext: 'settings' as const,
|
|
10
30
|
breadcrumb: [
|
|
11
31
|
{ label: 'Pipeline stages', labelKey: 'customers.config.nav.pipelineStages' },
|