@open-mercato/core 0.4.2-canary-ba1d84349b → 0.4.2-canary-07dbc98202
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/dist/generated/entities/notification/index.js +57 -0
- package/dist/generated/entities/notification/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +63 -59
- package/dist/generated/entities.ids.generated.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/api_docs/frontend/docs/api/page.js +3 -2
- package/dist/modules/api_docs/frontend/docs/api/page.js.map +2 -2
- package/dist/modules/auth/api/admin/nav.js +4 -3
- package/dist/modules/auth/api/admin/nav.js.map +2 -2
- package/dist/modules/auth/api/profile/route.js +155 -0
- package/dist/modules/auth/api/profile/route.js.map +7 -0
- package/dist/modules/auth/api/reset/confirm.js +25 -2
- package/dist/modules/auth/api/reset/confirm.js.map +2 -2
- package/dist/modules/auth/api/reset.js +23 -0
- package/dist/modules/auth/api/reset.js.map +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js +14 -9
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
- package/dist/modules/auth/backend/auth/profile/page.js +99 -0
- package/dist/modules/auth/backend/auth/profile/page.js.map +7 -0
- package/dist/modules/auth/backend/auth/profile/page.meta.js +12 -0
- package/dist/modules/auth/backend/auth/profile/page.meta.js.map +7 -0
- package/dist/modules/auth/commands/users.js +55 -0
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/lib/setup-app.js +1 -0
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/auth/notifications.js +112 -0
- package/dist/modules/auth/notifications.js.map +7 -0
- package/dist/modules/auth/services/authService.js +3 -3
- package/dist/modules/auth/services/authService.js.map +2 -2
- package/dist/modules/business_rules/notifications.js +28 -0
- package/dist/modules/business_rules/notifications.js.map +7 -0
- package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js +37 -0
- package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js.map +7 -0
- package/dist/modules/catalog/notifications.js +28 -0
- package/dist/modules/catalog/notifications.js.map +7 -0
- package/dist/modules/catalog/subscribers/low-stock-notification.js +38 -0
- package/dist/modules/catalog/subscribers/low-stock-notification.js.map +7 -0
- package/dist/modules/configs/cli.js +6 -0
- package/dist/modules/configs/cli.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +31 -0
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/notifications.js +48 -0
- package/dist/modules/customers/notifications.js.map +7 -0
- package/dist/modules/notifications/acl.js +11 -0
- package/dist/modules/notifications/acl.js.map +7 -0
- package/dist/modules/notifications/api/[id]/action/route.js +74 -0
- package/dist/modules/notifications/api/[id]/action/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/dismiss/route.js +15 -0
- package/dist/modules/notifications/api/[id]/dismiss/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/read/route.js +15 -0
- package/dist/modules/notifications/api/[id]/read/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/restore/route.js +53 -0
- package/dist/modules/notifications/api/[id]/restore/route.js.map +7 -0
- package/dist/modules/notifications/api/batch/route.js +17 -0
- package/dist/modules/notifications/api/batch/route.js.map +7 -0
- package/dist/modules/notifications/api/feature/route.js +17 -0
- package/dist/modules/notifications/api/feature/route.js.map +7 -0
- package/dist/modules/notifications/api/mark-all-read/route.js +35 -0
- package/dist/modules/notifications/api/mark-all-read/route.js.map +7 -0
- package/dist/modules/notifications/api/openapi.js +76 -0
- package/dist/modules/notifications/api/openapi.js.map +7 -0
- package/dist/modules/notifications/api/role/route.js +17 -0
- package/dist/modules/notifications/api/role/route.js.map +7 -0
- package/dist/modules/notifications/api/route.js +85 -0
- package/dist/modules/notifications/api/route.js.map +7 -0
- package/dist/modules/notifications/api/settings/route.js +155 -0
- package/dist/modules/notifications/api/settings/route.js.map +7 -0
- package/dist/modules/notifications/api/unread-count/route.js +38 -0
- package/dist/modules/notifications/api/unread-count/route.js.map +7 -0
- package/dist/modules/notifications/backend/config/notifications/page.js +10 -0
- package/dist/modules/notifications/backend/config/notifications/page.js.map +7 -0
- package/dist/modules/notifications/backend/config/notifications/page.meta.js +24 -0
- package/dist/modules/notifications/backend/config/notifications/page.meta.js.map +7 -0
- package/dist/modules/notifications/cli.js +16 -0
- package/dist/modules/notifications/cli.js.map +7 -0
- package/dist/modules/notifications/data/entities.js +112 -0
- package/dist/modules/notifications/data/entities.js.map +7 -0
- package/dist/modules/notifications/data/validators.js +94 -0
- package/dist/modules/notifications/data/validators.js.map +7 -0
- package/dist/modules/notifications/di.js +13 -0
- package/dist/modules/notifications/di.js.map +7 -0
- package/dist/modules/notifications/emails/NotificationEmail.js +58 -0
- package/dist/modules/notifications/emails/NotificationEmail.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationInboxPageClient.js +44 -0
- package/dist/modules/notifications/frontend/NotificationInboxPageClient.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +219 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +7 -0
- package/dist/modules/notifications/index.js +14 -0
- package/dist/modules/notifications/index.js.map +7 -0
- package/dist/modules/notifications/lib/deliveryConfig.js +105 -0
- package/dist/modules/notifications/lib/deliveryConfig.js.map +7 -0
- package/dist/modules/notifications/lib/events.js +12 -0
- package/dist/modules/notifications/lib/events.js.map +7 -0
- package/dist/modules/notifications/lib/notificationBuilder.js +66 -0
- package/dist/modules/notifications/lib/notificationBuilder.js.map +7 -0
- package/dist/modules/notifications/lib/notificationFactory.js +54 -0
- package/dist/modules/notifications/lib/notificationFactory.js.map +7 -0
- package/dist/modules/notifications/lib/notificationMapper.js +34 -0
- package/dist/modules/notifications/lib/notificationMapper.js.map +7 -0
- package/dist/modules/notifications/lib/notificationRecipients.js +35 -0
- package/dist/modules/notifications/lib/notificationRecipients.js.map +7 -0
- package/dist/modules/notifications/lib/notificationService.js +279 -0
- package/dist/modules/notifications/lib/notificationService.js.map +7 -0
- package/dist/modules/notifications/lib/routeHelpers.js +101 -0
- package/dist/modules/notifications/lib/routeHelpers.js.map +7 -0
- package/dist/modules/notifications/lib/safeHref.js +24 -0
- package/dist/modules/notifications/lib/safeHref.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260123000001.js +70 -0
- package/dist/modules/notifications/migrations/Migration20260123000001.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260126150000.js +37 -0
- package/dist/modules/notifications/migrations/Migration20260126150000.js.map +7 -0
- package/dist/modules/notifications/subscribers/deliver-notification.js +139 -0
- package/dist/modules/notifications/subscribers/deliver-notification.js.map +7 -0
- package/dist/modules/notifications/workers/create-notification.worker.js +70 -0
- package/dist/modules/notifications/workers/create-notification.worker.js.map +7 -0
- package/dist/modules/sales/commands/documents.js +53 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +26 -0
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/notifications.client.js +51 -0
- package/dist/modules/sales/notifications.client.js.map +7 -0
- package/dist/modules/sales/notifications.js +88 -0
- package/dist/modules/sales/notifications.js.map +7 -0
- package/dist/modules/sales/subscribers/quote-expiring-notification.js +38 -0
- package/dist/modules/sales/subscribers/quote-expiring-notification.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js +137 -0
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js +137 -0
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/index.js +7 -0
- package/dist/modules/sales/widgets/notifications/index.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js +60 -0
- package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js.map +7 -0
- package/dist/modules/staff/commands/leave-requests.js +79 -0
- package/dist/modules/staff/commands/leave-requests.js.map +2 -2
- package/dist/modules/staff/notifications.js +75 -0
- package/dist/modules/staff/notifications.js.map +7 -0
- package/dist/modules/workflows/notifications.js +28 -0
- package/dist/modules/workflows/notifications.js.map +7 -0
- package/dist/modules/workflows/subscribers/task-assigned-notification.js +38 -0
- package/dist/modules/workflows/subscribers/task-assigned-notification.js.map +7 -0
- package/generated/entities/notification/index.ts +27 -0
- package/generated/entities.ids.generated.ts +63 -59
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +2 -2
- package/src/modules/api_docs/frontend/docs/api/page.tsx +3 -2
- package/src/modules/auth/api/admin/nav.ts +10 -6
- package/src/modules/auth/api/profile/route.ts +160 -0
- package/src/modules/auth/api/reset/confirm.ts +25 -2
- package/src/modules/auth/api/reset.ts +23 -0
- package/src/modules/auth/api/sidebar/preferences/route.ts +21 -12
- package/src/modules/auth/backend/auth/profile/page.meta.ts +8 -0
- package/src/modules/auth/backend/auth/profile/page.tsx +127 -0
- package/src/modules/auth/commands/users.ts +68 -0
- package/src/modules/auth/i18n/de.json +29 -1
- package/src/modules/auth/i18n/en.json +29 -1
- package/src/modules/auth/i18n/es.json +29 -1
- package/src/modules/auth/i18n/pl.json +29 -1
- package/src/modules/auth/lib/setup-app.ts +1 -0
- package/src/modules/auth/notifications.ts +109 -0
- package/src/modules/auth/services/authService.ts +4 -4
- package/src/modules/business_rules/i18n/en.json +3 -1
- package/src/modules/business_rules/notifications.ts +25 -0
- package/src/modules/business_rules/subscribers/rule-execution-failed-notification.ts +50 -0
- package/src/modules/catalog/i18n/en.json +3 -1
- package/src/modules/catalog/notifications.ts +25 -0
- package/src/modules/catalog/subscribers/low-stock-notification.ts +52 -0
- package/src/modules/configs/cli.ts +6 -0
- package/src/modules/customers/commands/deals.ts +39 -0
- package/src/modules/customers/i18n/en.json +5 -1
- package/src/modules/customers/notifications.ts +44 -0
- package/src/modules/notifications/acl.ts +7 -0
- package/src/modules/notifications/api/[id]/action/route.ts +75 -0
- package/src/modules/notifications/api/[id]/dismiss/route.ts +12 -0
- package/src/modules/notifications/api/[id]/read/route.ts +12 -0
- package/src/modules/notifications/api/[id]/restore/route.ts +53 -0
- package/src/modules/notifications/api/batch/route.ts +14 -0
- package/src/modules/notifications/api/feature/route.ts +14 -0
- package/src/modules/notifications/api/mark-all-read/route.ts +34 -0
- package/src/modules/notifications/api/openapi.ts +76 -0
- package/src/modules/notifications/api/role/route.ts +14 -0
- package/src/modules/notifications/api/route.ts +92 -0
- package/src/modules/notifications/api/settings/route.ts +157 -0
- package/src/modules/notifications/api/unread-count/route.ts +38 -0
- package/src/modules/notifications/backend/config/notifications/page.meta.ts +22 -0
- package/src/modules/notifications/backend/config/notifications/page.tsx +12 -0
- package/src/modules/notifications/cli.ts +18 -0
- package/src/modules/notifications/data/entities.ts +99 -0
- package/src/modules/notifications/data/validators.ts +110 -0
- package/src/modules/notifications/di.ts +11 -0
- package/src/modules/notifications/emails/NotificationEmail.tsx +98 -0
- package/src/modules/notifications/frontend/NotificationInboxPageClient.tsx +42 -0
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +231 -0
- package/src/modules/notifications/i18n/de.json +50 -0
- package/src/modules/notifications/i18n/en.json +50 -0
- package/src/modules/notifications/i18n/es.json +50 -0
- package/src/modules/notifications/i18n/pl.json +50 -0
- package/src/modules/notifications/index.ts +12 -0
- package/src/modules/notifications/lib/deliveryConfig.ts +145 -0
- package/src/modules/notifications/lib/events.ts +48 -0
- package/src/modules/notifications/lib/notificationBuilder.ts +121 -0
- package/src/modules/notifications/lib/notificationFactory.ts +76 -0
- package/src/modules/notifications/lib/notificationMapper.ts +33 -0
- package/src/modules/notifications/lib/notificationRecipients.ts +83 -0
- package/src/modules/notifications/lib/notificationService.ts +414 -0
- package/src/modules/notifications/lib/routeHelpers.ts +151 -0
- package/src/modules/notifications/lib/safeHref.ts +29 -0
- package/src/modules/notifications/migrations/.snapshot-open-mercato.json +300 -0
- package/src/modules/notifications/migrations/Migration20260123000001.ts +73 -0
- package/src/modules/notifications/migrations/Migration20260126150000.ts +39 -0
- package/src/modules/notifications/subscribers/deliver-notification.ts +175 -0
- package/src/modules/notifications/workers/create-notification.worker.ts +122 -0
- package/src/modules/sales/commands/documents.ts +65 -0
- package/src/modules/sales/commands/payments.ts +33 -0
- package/src/modules/sales/i18n/de.json +20 -0
- package/src/modules/sales/i18n/en.json +25 -1
- package/src/modules/sales/i18n/es.json +20 -0
- package/src/modules/sales/i18n/pl.json +20 -0
- package/src/modules/sales/notifications.client.ts +65 -0
- package/src/modules/sales/notifications.ts +82 -0
- package/src/modules/sales/subscribers/quote-expiring-notification.ts +53 -0
- package/src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx +156 -0
- package/src/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.tsx +156 -0
- package/src/modules/sales/widgets/notifications/index.ts +2 -0
- package/src/modules/sales/widgets/notifications/useSalesDocumentTotals.ts +81 -0
- package/src/modules/staff/commands/leave-requests.ts +94 -0
- package/src/modules/staff/i18n/de.json +4 -0
- package/src/modules/staff/i18n/en.json +9 -1
- package/src/modules/staff/i18n/es.json +4 -0
- package/src/modules/staff/i18n/pl.json +4 -0
- package/src/modules/staff/notifications.ts +71 -0
- package/src/modules/workflows/i18n/en.json +3 -1
- package/src/modules/workflows/notifications.ts +25 -0
- package/src/modules/workflows/subscribers/task-assigned-notification.ts +53 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/core'
|
|
2
|
+
import type { Knex } from 'knex'
|
|
3
|
+
import { Notification } from '../data/entities'
|
|
4
|
+
import type { CreateNotificationInput, CreateRoleNotificationInput, CreateFeatureNotificationInput } from '../data/validators'
|
|
5
|
+
import { buildNotificationEntity, emitNotificationCreated, emitNotificationCreatedBatch } from '../lib/notificationFactory'
|
|
6
|
+
import { getRecipientUserIdsForFeature, getRecipientUserIdsForRole } from '../lib/notificationRecipients'
|
|
7
|
+
|
|
8
|
+
function getKnex(em: EntityManager): Knex {
|
|
9
|
+
return (em.getConnection() as unknown as { getKnex: () => Knex }).getKnex()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const NOTIFICATIONS_QUEUE_NAME = 'notifications'
|
|
13
|
+
|
|
14
|
+
export type CreateNotificationJob = {
|
|
15
|
+
type: 'create'
|
|
16
|
+
input: CreateNotificationInput
|
|
17
|
+
tenantId: string
|
|
18
|
+
organizationId?: string | null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type CreateRoleNotificationJob = {
|
|
22
|
+
type: 'create-role'
|
|
23
|
+
input: CreateRoleNotificationInput
|
|
24
|
+
tenantId: string
|
|
25
|
+
organizationId?: string | null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type CreateFeatureNotificationJob = {
|
|
29
|
+
type: 'create-feature'
|
|
30
|
+
input: CreateFeatureNotificationInput
|
|
31
|
+
tenantId: string
|
|
32
|
+
organizationId?: string | null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type CleanupExpiredJob = {
|
|
36
|
+
type: 'cleanup-expired'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type NotificationJob = CreateNotificationJob | CreateRoleNotificationJob | CreateFeatureNotificationJob | CleanupExpiredJob
|
|
40
|
+
|
|
41
|
+
export const metadata = {
|
|
42
|
+
queue: NOTIFICATIONS_QUEUE_NAME,
|
|
43
|
+
id: 'notifications:create',
|
|
44
|
+
concurrency: 5,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type HandlerContext = {
|
|
48
|
+
resolve: <T = unknown>(name: string) => T
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default async function handle(
|
|
52
|
+
job: { payload: NotificationJob },
|
|
53
|
+
ctx: HandlerContext
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
const { payload } = job
|
|
56
|
+
|
|
57
|
+
if (payload.type === 'create') {
|
|
58
|
+
const em = (ctx.resolve('em') as EntityManager).fork()
|
|
59
|
+
const eventBus = ctx.resolve('eventBus') as { emit: (event: string, payload: unknown) => Promise<void> }
|
|
60
|
+
const { input, tenantId, organizationId } = payload
|
|
61
|
+
const { recipientUserId, ...content } = input
|
|
62
|
+
const notification = buildNotificationEntity(em, content, recipientUserId, { tenantId, organizationId })
|
|
63
|
+
|
|
64
|
+
await em.persistAndFlush(notification)
|
|
65
|
+
|
|
66
|
+
await emitNotificationCreated(eventBus, notification, { tenantId, organizationId })
|
|
67
|
+
} else if (payload.type === 'create-role') {
|
|
68
|
+
const em = (ctx.resolve('em') as EntityManager).fork()
|
|
69
|
+
const eventBus = ctx.resolve('eventBus') as { emit: (event: string, payload: unknown) => Promise<void> }
|
|
70
|
+
const { input, tenantId, organizationId } = payload
|
|
71
|
+
|
|
72
|
+
const knex = getKnex(em)
|
|
73
|
+
const recipientUserIds = await getRecipientUserIdsForRole(knex, tenantId, input.roleId)
|
|
74
|
+
if (recipientUserIds.length === 0) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const { roleId: _roleId, ...content } = input
|
|
79
|
+
const notifications: Notification[] = []
|
|
80
|
+
for (const recipientUserId of recipientUserIds) {
|
|
81
|
+
const notification = buildNotificationEntity(em, content, recipientUserId, { tenantId, organizationId })
|
|
82
|
+
notifications.push(notification)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await em.persistAndFlush(notifications)
|
|
86
|
+
|
|
87
|
+
await emitNotificationCreatedBatch(eventBus, notifications, { tenantId, organizationId })
|
|
88
|
+
} else if (payload.type === 'create-feature') {
|
|
89
|
+
const em = (ctx.resolve('em') as EntityManager).fork()
|
|
90
|
+
const eventBus = ctx.resolve('eventBus') as { emit: (event: string, payload: unknown) => Promise<void> }
|
|
91
|
+
const { input, tenantId, organizationId } = payload
|
|
92
|
+
|
|
93
|
+
const knex = getKnex(em)
|
|
94
|
+
const recipientUserIds = await getRecipientUserIdsForFeature(knex, tenantId, input.requiredFeature)
|
|
95
|
+
|
|
96
|
+
if (recipientUserIds.length === 0) {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const notifications: Notification[] = []
|
|
101
|
+
const { requiredFeature: _requiredFeature, ...content } = input
|
|
102
|
+
for (const recipientUserId of recipientUserIds) {
|
|
103
|
+
const notification = buildNotificationEntity(em, content, recipientUserId, { tenantId, organizationId })
|
|
104
|
+
notifications.push(notification)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
await em.persistAndFlush(notifications)
|
|
108
|
+
|
|
109
|
+
await emitNotificationCreatedBatch(eventBus, notifications, { tenantId, organizationId })
|
|
110
|
+
} else if (payload.type === 'cleanup-expired') {
|
|
111
|
+
const em = (ctx.resolve('em') as EntityManager).fork()
|
|
112
|
+
const knex = getKnex(em)
|
|
113
|
+
|
|
114
|
+
await knex('notifications')
|
|
115
|
+
.where('expires_at', '<', knex.fn.now())
|
|
116
|
+
.whereNotIn('status', ['actioned', 'dismissed'])
|
|
117
|
+
.update({
|
|
118
|
+
status: 'dismissed',
|
|
119
|
+
dismissed_at: knex.fn.now(),
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -11,6 +11,8 @@ import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
|
|
|
11
11
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
12
12
|
import { deriveResourceFromCommandId, invalidateCrudCache } from '@open-mercato/shared/lib/crud/cache'
|
|
13
13
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
14
|
+
import { resolveNotificationService } from '../../notifications/lib/notificationService'
|
|
15
|
+
import { buildFeatureNotificationFromType } from '../../notifications/lib/notificationBuilder'
|
|
14
16
|
import { setRecordCustomFields } from '@open-mercato/core/modules/entities/lib/helpers'
|
|
15
17
|
import { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'
|
|
16
18
|
import { normalizeCustomFieldValues } from '@open-mercato/shared/lib/custom-fields/normalize'
|
|
@@ -88,6 +90,7 @@ import { resolveDictionaryEntryValue } from '../lib/dictionaries'
|
|
|
88
90
|
import { resolveStatusEntryIdByValue } from '../lib/statusHelpers'
|
|
89
91
|
import { SalesDocumentNumberGenerator } from '../services/salesDocumentNumberGenerator'
|
|
90
92
|
import { loadSalesSettings } from './settings'
|
|
93
|
+
import { notificationTypes } from '../notifications'
|
|
91
94
|
|
|
92
95
|
type DocumentAddressSnapshot = {
|
|
93
96
|
id: string
|
|
@@ -3111,6 +3114,37 @@ const createQuoteCommand: CommandHandler<QuoteCreateInput, { quoteId: string }>
|
|
|
3111
3114
|
})
|
|
3112
3115
|
await em.flush()
|
|
3113
3116
|
|
|
3117
|
+
// Create notification for users with sales.quotes.manage feature
|
|
3118
|
+
try {
|
|
3119
|
+
const notificationService = resolveNotificationService(ctx.container)
|
|
3120
|
+
const typeDef = notificationTypes.find((type) => type.type === 'sales.quote.created')
|
|
3121
|
+
if (typeDef) {
|
|
3122
|
+
const totalAmount = quote.grandTotalGrossAmount && quote.currencyCode
|
|
3123
|
+
? `${quote.grandTotalGrossAmount} ${quote.currencyCode}`
|
|
3124
|
+
: ''
|
|
3125
|
+
const totalDisplay = totalAmount ? ` (${totalAmount})` : ''
|
|
3126
|
+
const notificationInput = buildFeatureNotificationFromType(typeDef, {
|
|
3127
|
+
requiredFeature: 'sales.quotes.manage',
|
|
3128
|
+
bodyVariables: {
|
|
3129
|
+
quoteNumber: quote.quoteNumber,
|
|
3130
|
+
total: totalDisplay,
|
|
3131
|
+
totalAmount,
|
|
3132
|
+
},
|
|
3133
|
+
sourceEntityType: 'sales:quote',
|
|
3134
|
+
sourceEntityId: quote.id,
|
|
3135
|
+
linkHref: `/backend/sales/quotes/${quote.id}`,
|
|
3136
|
+
})
|
|
3137
|
+
|
|
3138
|
+
await notificationService.createForFeature(notificationInput, {
|
|
3139
|
+
tenantId: quote.tenantId,
|
|
3140
|
+
organizationId: quote.organizationId ?? null,
|
|
3141
|
+
})
|
|
3142
|
+
}
|
|
3143
|
+
} catch (err) {
|
|
3144
|
+
// Notification creation is non-critical, don't fail the command
|
|
3145
|
+
console.error('[sales.quotes.create] Failed to create notification:', err)
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3114
3148
|
return { quoteId: quote.id }
|
|
3115
3149
|
},
|
|
3116
3150
|
captureAfter: async (_input, result, ctx) => {
|
|
@@ -3782,6 +3816,37 @@ const createOrderCommand: CommandHandler<OrderCreateInput, { orderId: string }>
|
|
|
3782
3816
|
})
|
|
3783
3817
|
await em.flush()
|
|
3784
3818
|
|
|
3819
|
+
// Create notification for users with sales.orders.manage feature
|
|
3820
|
+
try {
|
|
3821
|
+
const notificationService = resolveNotificationService(ctx.container)
|
|
3822
|
+
const typeDef = notificationTypes.find((type) => type.type === 'sales.order.created')
|
|
3823
|
+
if (typeDef) {
|
|
3824
|
+
const totalAmount = order.grandTotalGrossAmount && order.currencyCode
|
|
3825
|
+
? `${order.grandTotalGrossAmount} ${order.currencyCode}`
|
|
3826
|
+
: ''
|
|
3827
|
+
const totalDisplay = totalAmount ? ` (${totalAmount})` : ''
|
|
3828
|
+
const notificationInput = buildFeatureNotificationFromType(typeDef, {
|
|
3829
|
+
requiredFeature: 'sales.orders.manage',
|
|
3830
|
+
bodyVariables: {
|
|
3831
|
+
orderNumber: order.orderNumber,
|
|
3832
|
+
total: totalDisplay,
|
|
3833
|
+
totalAmount,
|
|
3834
|
+
},
|
|
3835
|
+
sourceEntityType: 'sales:order',
|
|
3836
|
+
sourceEntityId: order.id,
|
|
3837
|
+
linkHref: `/backend/sales/orders/${order.id}`,
|
|
3838
|
+
})
|
|
3839
|
+
|
|
3840
|
+
await notificationService.createForFeature(notificationInput, {
|
|
3841
|
+
tenantId: order.tenantId,
|
|
3842
|
+
organizationId: order.organizationId ?? null,
|
|
3843
|
+
})
|
|
3844
|
+
}
|
|
3845
|
+
} catch (err) {
|
|
3846
|
+
// Notification creation is non-critical, don't fail the command
|
|
3847
|
+
console.error('[sales.orders.create] Failed to create notification:', err)
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3785
3850
|
return { orderId: order.id }
|
|
3786
3851
|
},
|
|
3787
3852
|
captureAfter: async (_input, result, ctx) => {
|
|
@@ -35,6 +35,9 @@ import { invalidateCrudCache } from '@open-mercato/shared/lib/crud/cache'
|
|
|
35
35
|
import { emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'
|
|
36
36
|
import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
|
|
37
37
|
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
38
|
+
import { resolveNotificationService } from '../../notifications/lib/notificationService'
|
|
39
|
+
import { buildFeatureNotificationFromType } from '../../notifications/lib/notificationBuilder'
|
|
40
|
+
import { notificationTypes } from '../notifications'
|
|
38
41
|
|
|
39
42
|
export type PaymentAllocationSnapshot = {
|
|
40
43
|
id: string
|
|
@@ -422,6 +425,36 @@ const createPaymentCommand: CommandHandler<
|
|
|
422
425
|
const totals = await recomputeOrderPaymentTotals(em, order)
|
|
423
426
|
await em.flush()
|
|
424
427
|
await invalidateOrderCache(ctx.container, order, ctx.auth?.tenantId ?? null)
|
|
428
|
+
|
|
429
|
+
// Create notification for payment received
|
|
430
|
+
try {
|
|
431
|
+
const notificationService = resolveNotificationService(ctx.container)
|
|
432
|
+
const typeDef = notificationTypes.find((type) => type.type === 'sales.payment.received')
|
|
433
|
+
if (typeDef) {
|
|
434
|
+
const amountDisplay = payment.amount && payment.currencyCode
|
|
435
|
+
? `${payment.currencyCode} ${payment.amount}`
|
|
436
|
+
: ''
|
|
437
|
+
const notificationInput = buildFeatureNotificationFromType(typeDef, {
|
|
438
|
+
requiredFeature: 'sales.orders.manage',
|
|
439
|
+
bodyVariables: {
|
|
440
|
+
orderNumber: order.orderNumber ?? '',
|
|
441
|
+
amount: amountDisplay,
|
|
442
|
+
},
|
|
443
|
+
sourceEntityType: 'sales:order',
|
|
444
|
+
sourceEntityId: order.id,
|
|
445
|
+
linkHref: `/backend/sales/orders/${order.id}`,
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
await notificationService.createForFeature(notificationInput, {
|
|
449
|
+
tenantId: payment.tenantId,
|
|
450
|
+
organizationId: payment.organizationId ?? null,
|
|
451
|
+
})
|
|
452
|
+
}
|
|
453
|
+
} catch (err) {
|
|
454
|
+
// Notification creation is non-critical, don't fail the command
|
|
455
|
+
console.error('[sales.payments.create] Failed to create notification:', err)
|
|
456
|
+
}
|
|
457
|
+
|
|
425
458
|
return { paymentId: payment.id, orderTotals: totals }
|
|
426
459
|
},
|
|
427
460
|
captureAfter: async (_input, result, ctx) => {
|
|
@@ -9,6 +9,26 @@
|
|
|
9
9
|
"title": "Vertriebsmanagement",
|
|
10
10
|
"description": "Angebote, Aufträge, Fulfillment, Rechnungsstellung und Zahlungen verbunden mit dem Produktkatalog."
|
|
11
11
|
},
|
|
12
|
+
"notifications": {
|
|
13
|
+
"order": {
|
|
14
|
+
"created": {
|
|
15
|
+
"title": "Neue Bestellung",
|
|
16
|
+
"body": "Bestellung {orderNumber} wurde erstellt{total}"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"quote": {
|
|
20
|
+
"created": {
|
|
21
|
+
"title": "Neues Angebot",
|
|
22
|
+
"body": "Angebot {quoteNumber} wurde erstellt{total}"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"renderer": {
|
|
26
|
+
"viewOrder": "Bestellung anzeigen",
|
|
27
|
+
"viewQuote": "Angebot anzeigen",
|
|
28
|
+
"assignedToYou": "Ihnen zugewiesen",
|
|
29
|
+
"pendingReview": "Ausstehende Prüfung"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
12
32
|
"quotes": {
|
|
13
33
|
"send": {
|
|
14
34
|
"action": "An Kunden senden",
|
|
@@ -9,6 +9,26 @@
|
|
|
9
9
|
"title": "Sales Management",
|
|
10
10
|
"description": "Quotes, orders, fulfillment, invoicing, and payments connected to the product catalog."
|
|
11
11
|
},
|
|
12
|
+
"notifications": {
|
|
13
|
+
"order": {
|
|
14
|
+
"created": {
|
|
15
|
+
"title": "New Sales Order",
|
|
16
|
+
"body": "Sales order {orderNumber} has been created{total}"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"quote": {
|
|
20
|
+
"created": {
|
|
21
|
+
"title": "New Sales Quote",
|
|
22
|
+
"body": "Sales quote {quoteNumber} has been created{total}"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"renderer": {
|
|
26
|
+
"viewOrder": "View Order",
|
|
27
|
+
"viewQuote": "View Quote",
|
|
28
|
+
"assignedToYou": "Assigned to you",
|
|
29
|
+
"pendingReview": "Pending review"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
12
32
|
"quotes": {
|
|
13
33
|
"send": {
|
|
14
34
|
"action": "Send to customer",
|
|
@@ -1516,5 +1536,9 @@
|
|
|
1516
1536
|
"sales.search.badge.deliveryWindow": "Delivery window",
|
|
1517
1537
|
"sales.search.badge.paymentMethod": "Payment method",
|
|
1518
1538
|
"sales.search.badge.taxRate": "Tax rate",
|
|
1519
|
-
"sales.search.badge.documentTag": "Document tag"
|
|
1539
|
+
"sales.search.badge.documentTag": "Document tag",
|
|
1540
|
+
"sales.notifications.payment.received.title": "Payment Received",
|
|
1541
|
+
"sales.notifications.payment.received.body": "Payment of {amount} received for order #{orderNumber}",
|
|
1542
|
+
"sales.notifications.quote.expiring.title": "Quote Expiring Soon",
|
|
1543
|
+
"sales.notifications.quote.expiring.body": "Quote #{quoteNumber} expires in {daysUntilExpiry} days ({expiresAt})"
|
|
1520
1544
|
}
|
|
@@ -9,6 +9,26 @@
|
|
|
9
9
|
"title": "Gestión de ventas",
|
|
10
10
|
"description": "Cotizaciones, pedidos, cumplimiento, facturación y pagos conectados al catálogo de productos."
|
|
11
11
|
},
|
|
12
|
+
"notifications": {
|
|
13
|
+
"order": {
|
|
14
|
+
"created": {
|
|
15
|
+
"title": "Nuevo pedido de venta",
|
|
16
|
+
"body": "El pedido {orderNumber} ha sido creado{total}"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"quote": {
|
|
20
|
+
"created": {
|
|
21
|
+
"title": "Nueva cotización",
|
|
22
|
+
"body": "La cotización {quoteNumber} ha sido creada{total}"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"renderer": {
|
|
26
|
+
"viewOrder": "Ver pedido",
|
|
27
|
+
"viewQuote": "Ver cotización",
|
|
28
|
+
"assignedToYou": "Asignado a usted",
|
|
29
|
+
"pendingReview": "Pendiente de revisión"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
12
32
|
"quotes": {
|
|
13
33
|
"send": {
|
|
14
34
|
"action": "Enviar al cliente",
|
|
@@ -9,6 +9,26 @@
|
|
|
9
9
|
"title": "Sprzedaż",
|
|
10
10
|
"description": "Oferty, zamówienia, realizacja, fakturowanie i płatności powiązane z katalogiem produktów."
|
|
11
11
|
},
|
|
12
|
+
"notifications": {
|
|
13
|
+
"order": {
|
|
14
|
+
"created": {
|
|
15
|
+
"title": "Nowe zamówienie",
|
|
16
|
+
"body": "Zamówienie {orderNumber} zostało utworzone{total}"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"quote": {
|
|
20
|
+
"created": {
|
|
21
|
+
"title": "Nowa oferta",
|
|
22
|
+
"body": "Oferta {quoteNumber} została utworzona{total}"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"renderer": {
|
|
26
|
+
"viewOrder": "Zobacz zamówienie",
|
|
27
|
+
"viewQuote": "Zobacz ofertę",
|
|
28
|
+
"assignedToYou": "Przypisane do Ciebie",
|
|
29
|
+
"pendingReview": "Oczekuje na przegląd"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
12
32
|
"quotes": {
|
|
13
33
|
"send": {
|
|
14
34
|
"action": "Wyślij do klienta",
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'
|
|
4
|
+
import { SalesOrderCreatedRenderer } from './widgets/notifications/SalesOrderCreatedRenderer'
|
|
5
|
+
import { SalesQuoteCreatedRenderer } from './widgets/notifications/SalesQuoteCreatedRenderer'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Client-side notification type definitions with custom renderers.
|
|
9
|
+
* These should be used in client components where custom rendering is needed.
|
|
10
|
+
*
|
|
11
|
+
* Example usage:
|
|
12
|
+
* ```tsx
|
|
13
|
+
* import { salesNotificationTypes } from '@open-mercato/core/modules/sales/notifications.client'
|
|
14
|
+
*
|
|
15
|
+
* // Use in NotificationPanel or NotificationItem
|
|
16
|
+
* const renderer = salesNotificationTypes.find(t => t.type === notification.type)?.Renderer
|
|
17
|
+
* if (renderer) {
|
|
18
|
+
* return <renderer notification={notification} onAction={...} onDismiss={...} actions={...} />
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export const salesNotificationTypes: NotificationTypeDefinition[] = [
|
|
23
|
+
{
|
|
24
|
+
type: 'sales.order.created',
|
|
25
|
+
module: 'sales',
|
|
26
|
+
titleKey: 'sales.notifications.order.created.title',
|
|
27
|
+
bodyKey: 'sales.notifications.order.created.body',
|
|
28
|
+
icon: 'shopping-cart',
|
|
29
|
+
severity: 'info',
|
|
30
|
+
actions: [
|
|
31
|
+
{
|
|
32
|
+
id: 'view',
|
|
33
|
+
labelKey: 'common.view',
|
|
34
|
+
variant: 'outline',
|
|
35
|
+
href: '/backend/sales/orders/{sourceEntityId}',
|
|
36
|
+
icon: 'external-link',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
linkHref: '/backend/sales/orders/{sourceEntityId}',
|
|
40
|
+
Renderer: SalesOrderCreatedRenderer,
|
|
41
|
+
expiresAfterHours: 168,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'sales.quote.created',
|
|
45
|
+
module: 'sales',
|
|
46
|
+
titleKey: 'sales.notifications.quote.created.title',
|
|
47
|
+
bodyKey: 'sales.notifications.quote.created.body',
|
|
48
|
+
icon: 'file-text',
|
|
49
|
+
severity: 'info',
|
|
50
|
+
actions: [
|
|
51
|
+
{
|
|
52
|
+
id: 'view',
|
|
53
|
+
labelKey: 'common.view',
|
|
54
|
+
variant: 'outline',
|
|
55
|
+
href: '/backend/sales/quotes/{sourceEntityId}',
|
|
56
|
+
icon: 'external-link',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
linkHref: '/backend/sales/quotes/{sourceEntityId}',
|
|
60
|
+
Renderer: SalesQuoteCreatedRenderer,
|
|
61
|
+
expiresAfterHours: 168,
|
|
62
|
+
},
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
export default salesNotificationTypes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'
|
|
2
|
+
|
|
3
|
+
export const notificationTypes: NotificationTypeDefinition[] = [
|
|
4
|
+
{
|
|
5
|
+
type: 'sales.order.created',
|
|
6
|
+
module: 'sales',
|
|
7
|
+
titleKey: 'sales.notifications.order.created.title',
|
|
8
|
+
bodyKey: 'sales.notifications.order.created.body',
|
|
9
|
+
icon: 'shopping-cart',
|
|
10
|
+
severity: 'info',
|
|
11
|
+
actions: [
|
|
12
|
+
{
|
|
13
|
+
id: 'view',
|
|
14
|
+
labelKey: 'common.view',
|
|
15
|
+
variant: 'outline',
|
|
16
|
+
href: '/backend/sales/orders/{sourceEntityId}',
|
|
17
|
+
icon: 'external-link',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
linkHref: '/backend/sales/orders/{sourceEntityId}',
|
|
21
|
+
expiresAfterHours: 168, // 7 days
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: 'sales.quote.created',
|
|
25
|
+
module: 'sales',
|
|
26
|
+
titleKey: 'sales.notifications.quote.created.title',
|
|
27
|
+
bodyKey: 'sales.notifications.quote.created.body',
|
|
28
|
+
icon: 'file-text',
|
|
29
|
+
severity: 'info',
|
|
30
|
+
actions: [
|
|
31
|
+
{
|
|
32
|
+
id: 'view',
|
|
33
|
+
labelKey: 'common.view',
|
|
34
|
+
variant: 'outline',
|
|
35
|
+
href: '/backend/sales/quotes/{sourceEntityId}',
|
|
36
|
+
icon: 'external-link',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
linkHref: '/backend/sales/quotes/{sourceEntityId}',
|
|
40
|
+
expiresAfterHours: 168, // 7 days
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'sales.payment.received',
|
|
44
|
+
module: 'sales',
|
|
45
|
+
titleKey: 'sales.notifications.payment.received.title',
|
|
46
|
+
bodyKey: 'sales.notifications.payment.received.body',
|
|
47
|
+
icon: 'credit-card',
|
|
48
|
+
severity: 'success',
|
|
49
|
+
actions: [
|
|
50
|
+
{
|
|
51
|
+
id: 'view',
|
|
52
|
+
labelKey: 'common.view',
|
|
53
|
+
variant: 'outline',
|
|
54
|
+
href: '/backend/sales/orders/{sourceEntityId}',
|
|
55
|
+
icon: 'external-link',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
linkHref: '/backend/sales/orders/{sourceEntityId}',
|
|
59
|
+
expiresAfterHours: 168, // 7 days
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'sales.quote.expiring',
|
|
63
|
+
module: 'sales',
|
|
64
|
+
titleKey: 'sales.notifications.quote.expiring.title',
|
|
65
|
+
bodyKey: 'sales.notifications.quote.expiring.body',
|
|
66
|
+
icon: 'clock',
|
|
67
|
+
severity: 'warning',
|
|
68
|
+
actions: [
|
|
69
|
+
{
|
|
70
|
+
id: 'view',
|
|
71
|
+
labelKey: 'common.view',
|
|
72
|
+
variant: 'outline',
|
|
73
|
+
href: '/backend/sales/quotes/{sourceEntityId}',
|
|
74
|
+
icon: 'external-link',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
linkHref: '/backend/sales/quotes/{sourceEntityId}',
|
|
78
|
+
expiresAfterHours: 72, // 3 days
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
export default notificationTypes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { resolveNotificationService } from '../../notifications/lib/notificationService'
|
|
3
|
+
import { buildFeatureNotificationFromType } from '../../notifications/lib/notificationBuilder'
|
|
4
|
+
import { notificationTypes } from '../notifications'
|
|
5
|
+
|
|
6
|
+
export const metadata = {
|
|
7
|
+
event: 'sales.quote.expiring',
|
|
8
|
+
persistent: true,
|
|
9
|
+
id: 'sales:quote-expiring-notification',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type QuoteExpiringPayload = {
|
|
13
|
+
quoteId: string
|
|
14
|
+
quoteNumber: string
|
|
15
|
+
expiresAt: string
|
|
16
|
+
daysUntilExpiry: number
|
|
17
|
+
customerName?: string | null
|
|
18
|
+
totalAmount?: string | null
|
|
19
|
+
tenantId: string
|
|
20
|
+
organizationId?: string | null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type ResolverContext = {
|
|
24
|
+
resolve: <T = unknown>(name: string) => T
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default async function handle(payload: QuoteExpiringPayload, ctx: ResolverContext) {
|
|
28
|
+
try {
|
|
29
|
+
const notificationService = resolveNotificationService(ctx)
|
|
30
|
+
const typeDef = notificationTypes.find((type) => type.type === 'sales.quote.expiring')
|
|
31
|
+
if (!typeDef) return
|
|
32
|
+
|
|
33
|
+
const notificationInput = buildFeatureNotificationFromType(typeDef, {
|
|
34
|
+
requiredFeature: 'sales.quotes.manage',
|
|
35
|
+
bodyVariables: {
|
|
36
|
+
quoteNumber: payload.quoteNumber,
|
|
37
|
+
expiresAt: payload.expiresAt,
|
|
38
|
+
daysUntilExpiry: String(payload.daysUntilExpiry),
|
|
39
|
+
customerName: payload.customerName ?? '',
|
|
40
|
+
},
|
|
41
|
+
sourceEntityType: 'sales:quote',
|
|
42
|
+
sourceEntityId: payload.quoteId,
|
|
43
|
+
linkHref: `/backend/sales/quotes/${payload.quoteId}`,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
await notificationService.createForFeature(notificationInput, {
|
|
47
|
+
tenantId: payload.tenantId,
|
|
48
|
+
organizationId: payload.organizationId ?? null,
|
|
49
|
+
})
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error('[sales:quote-expiring-notification] Failed to create notification:', err)
|
|
52
|
+
}
|
|
53
|
+
}
|