@open-mercato/core 0.6.8-develop.7047.1.32ab5fcb07 → 0.6.8-develop.7049.1.b41427429a
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/modules/customers/api/dashboard/widgets/customer-todos/route.js +8 -5
- package/dist/modules/customers/api/dashboard/widgets/customer-todos/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/tasks/route.js +27 -4
- package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
- package/dist/modules/customers/api/todos/route.js +30 -5
- package/dist/modules/customers/api/todos/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +6 -1
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +12 -0
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/dashboards/lib/widgetScope.js +6 -1
- package/dist/modules/dashboards/lib/widgetScope.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customers/api/dashboard/widgets/customer-todos/route.ts +16 -8
- package/src/modules/customers/api/interactions/tasks/route.ts +24 -1
- package/src/modules/customers/api/todos/route.ts +27 -2
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +7 -2
- package/src/modules/customers/lib/todoCompatibility.ts +19 -1
- package/src/modules/dashboards/lib/widgetScope.ts +6 -0
|
@@ -3,6 +3,7 @@ import { z } from 'zod'
|
|
|
3
3
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
4
|
import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
5
5
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
6
|
+
import { isUnrestrictedOrganizationScope } from '@open-mercato/shared/lib/auth/organizationAccess'
|
|
6
7
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
7
8
|
import type { QueryEngine } from '@open-mercato/shared/lib/query/types'
|
|
8
9
|
import { createCustomersCrudOpenApi, createPagedListResponseSchema } from '../../openapi'
|
|
@@ -40,11 +41,30 @@ const MERGED_TASK_FETCH_CAP = 2000
|
|
|
40
41
|
export async function GET(request: Request): Promise<Response> {
|
|
41
42
|
const { translate } = await resolveTranslations()
|
|
42
43
|
try {
|
|
43
|
-
const { auth, em, organizationIds, container, selectedOrganizationId } =
|
|
44
|
+
const { auth, em, organizationIds, container, selectedOrganizationId, scope } =
|
|
44
45
|
await resolveCustomersRequestContext(request)
|
|
45
46
|
const query = querySchema.parse(Object.fromEntries(new URL(request.url).searchParams))
|
|
46
47
|
const flags = await resolveCustomerInteractionFeatureFlags(container, auth.tenantId)
|
|
47
48
|
const exportAll = parseBooleanToken(query.all) === true
|
|
49
|
+
const isUnrestricted = isUnrestrictedOrganizationScope({
|
|
50
|
+
isSuperAdmin: auth.isSuperAdmin === true,
|
|
51
|
+
allowedOrganizationIds: scope?.allowedIds,
|
|
52
|
+
})
|
|
53
|
+
if (!isUnrestricted && (!organizationIds || organizationIds.length === 0)) {
|
|
54
|
+
logger.warn('customers.interactions.tasks.list collapsed organization scope', {
|
|
55
|
+
tenantId: auth.tenantId,
|
|
56
|
+
organizationIds,
|
|
57
|
+
selectedId: scope?.selectedId ?? null,
|
|
58
|
+
allowedIdsCount: scope?.allowedIds?.length ?? null,
|
|
59
|
+
})
|
|
60
|
+
return NextResponse.json({
|
|
61
|
+
items: [],
|
|
62
|
+
total: 0,
|
|
63
|
+
page: exportAll ? 1 : query.page,
|
|
64
|
+
pageSize: exportAll ? 0 : query.pageSize,
|
|
65
|
+
totalPages: 1,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
48
68
|
const search = normalizeTodoSearch(query.search)
|
|
49
69
|
const queryEngine = container.resolve('queryEngine') as QueryEngine
|
|
50
70
|
|
|
@@ -59,6 +79,7 @@ export async function GET(request: Request): Promise<Response> {
|
|
|
59
79
|
entityId: query.entityId,
|
|
60
80
|
pagination: exportAll ? null : { page: query.page, pageSize: query.pageSize },
|
|
61
81
|
searchText: search,
|
|
82
|
+
isUnrestricted,
|
|
62
83
|
},
|
|
63
84
|
)
|
|
64
85
|
const total = canonical.total
|
|
@@ -80,6 +101,7 @@ export async function GET(request: Request): Promise<Response> {
|
|
|
80
101
|
const [legacyRows, canonicalRows] = await Promise.all([
|
|
81
102
|
listLegacyTodoRows(em, queryEngine, auth.tenantId, organizationIds, query.entityId, {
|
|
82
103
|
limit: legacyWindow,
|
|
104
|
+
isUnrestricted,
|
|
83
105
|
}),
|
|
84
106
|
listCanonicalTodoRows(
|
|
85
107
|
em,
|
|
@@ -91,6 +113,7 @@ export async function GET(request: Request): Promise<Response> {
|
|
|
91
113
|
entityId: query.entityId,
|
|
92
114
|
includeDeleted: true,
|
|
93
115
|
limit: legacyWindow,
|
|
116
|
+
isUnrestricted,
|
|
94
117
|
},
|
|
95
118
|
),
|
|
96
119
|
])
|
|
@@ -9,6 +9,7 @@ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
|
9
9
|
import type { CommandBus } from '@open-mercato/shared/lib/commands'
|
|
10
10
|
import { isCrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
|
|
11
11
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
12
|
+
import { isUnrestrictedOrganizationScope } from '@open-mercato/shared/lib/auth/organizationAccess'
|
|
12
13
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
13
14
|
import {
|
|
14
15
|
runCrudMutationGuardAfterSuccess,
|
|
@@ -233,15 +234,36 @@ async function resolveCanonicalTodoTargetId(
|
|
|
233
234
|
|
|
234
235
|
export async function GET(request: Request): Promise<Response> {
|
|
235
236
|
try {
|
|
236
|
-
const { auth, em, organizationIds, container, selectedOrganizationId } =
|
|
237
|
+
const { auth, em, organizationIds, container, selectedOrganizationId, scope } =
|
|
237
238
|
await resolveCustomersRequestContext(request)
|
|
238
239
|
const query = querySchema.parse(Object.fromEntries(new URL(request.url).searchParams))
|
|
239
240
|
const flags = await resolveCustomerInteractionFeatureFlags(container, auth.tenantId)
|
|
240
241
|
if (!flags.legacyAdapters) {
|
|
241
242
|
return await legacyAdaptersDisabledResponse()
|
|
242
243
|
}
|
|
243
|
-
const queryEngine = container.resolve('queryEngine') as QueryEngine
|
|
244
244
|
const exportAll = parseBooleanToken(query.all) === true
|
|
245
|
+
const isUnrestricted = isUnrestrictedOrganizationScope({
|
|
246
|
+
isSuperAdmin: auth.isSuperAdmin === true,
|
|
247
|
+
allowedOrganizationIds: scope?.allowedIds,
|
|
248
|
+
})
|
|
249
|
+
if (!isUnrestricted && (!organizationIds || organizationIds.length === 0)) {
|
|
250
|
+
logger.warn('customers.todos.list collapsed organization scope', {
|
|
251
|
+
tenantId: auth.tenantId,
|
|
252
|
+
organizationIds,
|
|
253
|
+
selectedId: scope?.selectedId ?? null,
|
|
254
|
+
allowedIdsCount: scope?.allowedIds?.length ?? null,
|
|
255
|
+
})
|
|
256
|
+
return withAdapterHeaders(
|
|
257
|
+
NextResponse.json({
|
|
258
|
+
items: [],
|
|
259
|
+
total: 0,
|
|
260
|
+
page: exportAll ? 1 : query.page,
|
|
261
|
+
pageSize: exportAll ? 0 : query.pageSize,
|
|
262
|
+
totalPages: 1,
|
|
263
|
+
}),
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
const queryEngine = container.resolve('queryEngine') as QueryEngine
|
|
245
267
|
const search = normalizeTodoSearch(query.search)
|
|
246
268
|
|
|
247
269
|
if (flags.unified) {
|
|
@@ -255,6 +277,7 @@ export async function GET(request: Request): Promise<Response> {
|
|
|
255
277
|
entityId: query.entityId,
|
|
256
278
|
pagination: exportAll ? null : { page: query.page, pageSize: query.pageSize },
|
|
257
279
|
searchText: search,
|
|
280
|
+
isUnrestricted,
|
|
258
281
|
},
|
|
259
282
|
)
|
|
260
283
|
const total = canonical.total
|
|
@@ -275,6 +298,7 @@ export async function GET(request: Request): Promise<Response> {
|
|
|
275
298
|
const [legacyRows, canonicalRows] = await Promise.all([
|
|
276
299
|
listLegacyTodoRows(em, queryEngine, auth.tenantId, organizationIds, query.entityId, {
|
|
277
300
|
limit: legacyWindow,
|
|
301
|
+
isUnrestricted,
|
|
278
302
|
}),
|
|
279
303
|
listCanonicalTodoRows(
|
|
280
304
|
em,
|
|
@@ -287,6 +311,7 @@ export async function GET(request: Request): Promise<Response> {
|
|
|
287
311
|
includeDeleted: true,
|
|
288
312
|
source: CUSTOMER_INTERACTION_TODO_ADAPTER_SOURCE,
|
|
289
313
|
limit: legacyWindow,
|
|
314
|
+
isUnrestricted,
|
|
290
315
|
},
|
|
291
316
|
),
|
|
292
317
|
])
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { extensionPoints } from '@open-mercato/core/modules/customers/extension-points'
|
|
5
5
|
import Link from 'next/link'
|
|
6
|
-
import { Building2, UserSearch, Users } from 'lucide-react'
|
|
6
|
+
import { Building2, FileText, Hash, UserSearch, Users } from 'lucide-react'
|
|
7
7
|
import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
|
|
8
8
|
import { useRouter, useSearchParams, usePathname } from 'next/navigation'
|
|
9
9
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
@@ -12,7 +12,7 @@ import { AttachmentsSection, ErrorMessage, LoadingMessage, NotesSection, RecordN
|
|
|
12
12
|
import { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'
|
|
13
13
|
import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
|
|
14
14
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
15
|
-
import { CollapsibleZoneLayout } from '@open-mercato/ui/backend/crud/CollapsibleZoneLayout'
|
|
15
|
+
import { CollapsibleZoneLayout, type ZoneSectionDescriptor } from '@open-mercato/ui/backend/crud/CollapsibleZoneLayout'
|
|
16
16
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
17
17
|
import { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'
|
|
18
18
|
import { E } from '#generated/entities.ids.generated'
|
|
@@ -57,6 +57,10 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
|
|
|
57
57
|
const pathname = usePathname()
|
|
58
58
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
59
59
|
const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])
|
|
60
|
+
const zoneSections = React.useMemo<ZoneSectionDescriptor[]>(() => [
|
|
61
|
+
{ id: 'details', icon: FileText, label: t('customers.people.detail.deals.form.details', 'Deal details') },
|
|
62
|
+
{ id: 'custom', icon: Hash, label: t('customers.people.detail.deals.form.customFields', 'Custom fields') },
|
|
63
|
+
], [t])
|
|
60
64
|
|
|
61
65
|
const { data, setData, isLoading, error, isNotFound, loadData } = useDealData(id)
|
|
62
66
|
const [isDirty, setIsDirty] = React.useState(false)
|
|
@@ -664,6 +668,7 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
|
|
|
664
668
|
pageType="deal-detail-v3"
|
|
665
669
|
entityName={dealName}
|
|
666
670
|
isDirty={isDirty}
|
|
671
|
+
sections={zoneSections}
|
|
667
672
|
zone1DefaultWidth="540px"
|
|
668
673
|
zone1={zone1Content}
|
|
669
674
|
zone2={zone2Content}
|
|
@@ -78,6 +78,17 @@ export type CanonicalTodoListResult = {
|
|
|
78
78
|
total: number
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
function emptyCanonicalTodoList(): CanonicalTodoListResult {
|
|
82
|
+
return { items: [], bridgeIds: new Set(), total: 0 }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isCollapsedRestrictedOrganizationScope(
|
|
86
|
+
organizationIds: string[] | null | undefined,
|
|
87
|
+
isUnrestricted: boolean | undefined,
|
|
88
|
+
): boolean {
|
|
89
|
+
return isUnrestricted !== true && (!organizationIds || organizationIds.length === 0)
|
|
90
|
+
}
|
|
91
|
+
|
|
81
92
|
export type ListTodosPagination = { page: number; pageSize: number }
|
|
82
93
|
|
|
83
94
|
function resolveLegacyTodoSource(source: string | null | undefined): string {
|
|
@@ -377,8 +388,11 @@ export async function listLegacyTodoRows(
|
|
|
377
388
|
tenantId: string,
|
|
378
389
|
organizationIds: string[] | null,
|
|
379
390
|
entityId: string | undefined,
|
|
380
|
-
options?: { limit?: number | null },
|
|
391
|
+
options?: { limit?: number | null; isUnrestricted?: boolean },
|
|
381
392
|
): Promise<CustomerTodoRow[]> {
|
|
393
|
+
if (isCollapsedRestrictedOrganizationScope(organizationIds, options?.isUnrestricted)) {
|
|
394
|
+
return []
|
|
395
|
+
}
|
|
382
396
|
const where: Record<string, unknown> = { tenantId }
|
|
383
397
|
if (organizationIds && organizationIds.length > 0) {
|
|
384
398
|
where.organizationId = { $in: organizationIds }
|
|
@@ -425,8 +439,12 @@ export async function listCanonicalTodoRows(
|
|
|
425
439
|
pagination?: ListTodosPagination | null
|
|
426
440
|
searchText?: string | null
|
|
427
441
|
limit?: number | null
|
|
442
|
+
isUnrestricted?: boolean
|
|
428
443
|
},
|
|
429
444
|
): Promise<CanonicalTodoListResult> {
|
|
445
|
+
if (isCollapsedRestrictedOrganizationScope(organizationIds, options?.isUnrestricted)) {
|
|
446
|
+
return emptyCanonicalTodoList()
|
|
447
|
+
}
|
|
430
448
|
const where: Record<string, unknown> = {
|
|
431
449
|
tenantId: auth.tenantId,
|
|
432
450
|
interactionType: 'task',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
2
|
import { createRequestContainer, type AppContainer } from '@open-mercato/shared/lib/di/container'
|
|
3
3
|
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
4
|
+
import { isUnrestrictedOrganizationScope } from '@open-mercato/shared/lib/auth/organizationAccess'
|
|
4
5
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
5
6
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
6
7
|
|
|
@@ -9,6 +10,7 @@ export type WidgetScopeContext = {
|
|
|
9
10
|
em: EntityManager
|
|
10
11
|
tenantId: string
|
|
11
12
|
organizationIds: string[] | null
|
|
13
|
+
isUnrestrictedOrganizationScope?: boolean
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
function normalizeScopeId(value: string | null | undefined): string | null {
|
|
@@ -91,5 +93,9 @@ export async function resolveWidgetScope(
|
|
|
91
93
|
em,
|
|
92
94
|
tenantId,
|
|
93
95
|
organizationIds,
|
|
96
|
+
isUnrestrictedOrganizationScope: isUnrestrictedOrganizationScope({
|
|
97
|
+
isSuperAdmin,
|
|
98
|
+
allowedOrganizationIds: scope?.allowedIds,
|
|
99
|
+
}),
|
|
94
100
|
}
|
|
95
101
|
}
|