@open-mercato/core 0.6.7-develop.6696.1.4236709f7e → 0.6.7-develop.6725.1.07a748cfc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/api/admin/nav.js +14 -4
- package/dist/modules/auth/api/admin/nav.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +8 -19
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/frontend/login.js +4 -1
- package/dist/modules/auth/frontend/login.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +31 -14
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/route.js +3 -4
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +3 -2
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +2 -2
- package/dist/modules/customers/api/utils.js +12 -18
- package/dist/modules/customers/api/utils.js.map +2 -2
- package/dist/modules/customers/cli.js +11 -4
- package/dist/modules/customers/cli.js.map +2 -2
- package/dist/modules/customers/components/detail/ActiveDealCard.js +2 -1
- package/dist/modules/customers/components/detail/ActiveDealCard.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyKpiBar.js +6 -5
- package/dist/modules/customers/components/detail/CompanyKpiBar.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +4 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/dashboard/helpers.js +4 -3
- package/dist/modules/customers/components/detail/dashboard/helpers.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +14 -10
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/lib/dealStatus.js +38 -0
- package/dist/modules/customers/lib/dealStatus.js.map +7 -0
- package/dist/modules/dashboards/api/widgets/data/batch/route.js +4 -2
- package/dist/modules/dashboards/api/widgets/data/batch/route.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/data/route.js +20 -2
- package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
- package/dist/modules/dashboards/lib/aggregations.js +76 -40
- package/dist/modules/dashboards/lib/aggregations.js.map +2 -2
- package/dist/modules/dashboards/lib/exactDecimal.js +69 -0
- package/dist/modules/dashboards/lib/exactDecimal.js.map +7 -0
- package/dist/modules/dashboards/services/widgetDataService.js +228 -5
- package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
- package/dist/modules/feature_toggles/components/FeatureGuard.js +3 -2
- package/dist/modules/feature_toggles/components/FeatureGuard.js.map +2 -2
- package/dist/modules/feature_toggles/components/hooks/staleTime.js +5 -0
- package/dist/modules/feature_toggles/components/hooks/staleTime.js.map +7 -0
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.js +5 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.js.map +2 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagJson.js +5 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagJson.js.map +2 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagNumber.js +5 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagNumber.js.map +2 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagString.js +5 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagString.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/route.js +16 -1
- package/dist/modules/inbox_ops/api/proposals/route.js.map +2 -2
- package/dist/modules/inbox_ops/subscribers/search-reindex-proposal-created.js +32 -0
- package/dist/modules/inbox_ops/subscribers/search-reindex-proposal-created.js.map +7 -0
- package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js +0 -5
- package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js.map +2 -2
- package/dist/modules/integrations/widgets/injection/external-ids/widget.js +14 -0
- package/dist/modules/integrations/widgets/injection/external-ids/widget.js.map +7 -0
- package/dist/modules/messages/lib/searchLookup.js +8 -16
- package/dist/modules/messages/lib/searchLookup.js.map +2 -2
- package/dist/modules/payment_gateways/api/sessions/route.js +5 -0
- package/dist/modules/payment_gateways/api/sessions/route.js.map +2 -2
- package/dist/modules/payment_gateways/di.js +17 -1
- package/dist/modules/payment_gateways/di.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +8 -0
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
- package/dist/modules/payment_gateways/lib/order-amount-reconciliation.js +58 -0
- package/dist/modules/payment_gateways/lib/order-amount-reconciliation.js.map +7 -0
- package/dist/modules/query_index/api/openapi.js +13 -1
- package/dist/modules/query_index/api/openapi.js.map +2 -2
- package/dist/modules/query_index/api/status.js +165 -105
- package/dist/modules/query_index/api/status.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +128 -41
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/query_index/lib/batch.js +11 -4
- package/dist/modules/query_index/lib/batch.js.map +2 -2
- package/dist/modules/query_index/lib/document.js +12 -5
- package/dist/modules/query_index/lib/document.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +36 -4
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +1 -1
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/query_index/lib/search-tokens.js +7 -4
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/sales/api/documents/factory.js +14 -3
- package/dist/modules/sales/api/documents/factory.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +22 -1
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/components/useSalesChannelsEnabled.js +2 -35
- package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +2 -2
- package/dist/modules/sales/data/validators.js +25 -4
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/dist/modules/sales/di.js +7 -0
- package/dist/modules/sales/di.js.map +2 -2
- package/dist/modules/sales/services/paymentOrderTotalResolver.js +52 -0
- package/dist/modules/sales/services/paymentOrderTotalResolver.js.map +7 -0
- package/package.json +7 -7
- package/src/modules/auth/api/admin/nav.ts +24 -4
- package/src/modules/auth/api/users/route.ts +8 -28
- package/src/modules/auth/frontend/login.tsx +4 -1
- package/src/modules/auth/lib/backendChrome.tsx +51 -13
- package/src/modules/customers/api/companies/[id]/route.ts +3 -4
- package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +3 -2
- package/src/modules/customers/api/utils.ts +12 -31
- package/src/modules/customers/cli.ts +11 -4
- package/src/modules/customers/components/detail/ActiveDealCard.tsx +2 -1
- package/src/modules/customers/components/detail/CompanyKpiBar.tsx +6 -5
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +4 -0
- package/src/modules/customers/components/detail/dashboard/helpers.ts +4 -3
- package/src/modules/customers/components/formConfig.tsx +12 -8
- package/src/modules/customers/lib/dealStatus.ts +48 -0
- package/src/modules/dashboards/api/widgets/data/batch/route.ts +4 -0
- package/src/modules/dashboards/api/widgets/data/route.ts +21 -0
- package/src/modules/dashboards/lib/aggregations.ts +122 -43
- package/src/modules/dashboards/lib/exactDecimal.ts +96 -0
- package/src/modules/dashboards/services/widgetDataService.ts +342 -4
- package/src/modules/feature_toggles/components/FeatureGuard.tsx +9 -2
- package/src/modules/feature_toggles/components/hooks/staleTime.ts +5 -0
- package/src/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.ts +13 -1
- package/src/modules/feature_toggles/components/hooks/useFeatureFlagJson.ts +12 -3
- package/src/modules/feature_toggles/components/hooks/useFeatureFlagNumber.ts +10 -1
- package/src/modules/feature_toggles/components/hooks/useFeatureFlagString.ts +10 -1
- package/src/modules/inbox_ops/api/proposals/route.ts +20 -1
- package/src/modules/inbox_ops/subscribers/search-reindex-proposal-created.ts +39 -0
- package/src/modules/integrations/widgets/injection/external-ids/widget.client.tsx +0 -5
- package/src/modules/integrations/widgets/injection/external-ids/widget.ts +13 -0
- package/src/modules/messages/lib/searchLookup.ts +11 -34
- package/src/modules/payment_gateways/api/sessions/route.ts +5 -0
- package/src/modules/payment_gateways/di.ts +28 -2
- package/src/modules/payment_gateways/i18n/de.json +3 -0
- package/src/modules/payment_gateways/i18n/en.json +3 -0
- package/src/modules/payment_gateways/i18n/es.json +3 -0
- package/src/modules/payment_gateways/i18n/pl.json +3 -0
- package/src/modules/payment_gateways/lib/gateway-service.ts +14 -0
- package/src/modules/payment_gateways/lib/order-amount-reconciliation.ts +93 -0
- package/src/modules/query_index/api/openapi.ts +12 -0
- package/src/modules/query_index/api/status.ts +238 -136
- package/src/modules/query_index/components/QueryIndexesTable.tsx +192 -46
- package/src/modules/query_index/i18n/de.json +5 -3
- package/src/modules/query_index/i18n/en.json +5 -3
- package/src/modules/query_index/i18n/es.json +5 -3
- package/src/modules/query_index/i18n/pl.json +5 -3
- package/src/modules/query_index/lib/batch.ts +11 -4
- package/src/modules/query_index/lib/document.ts +35 -4
- package/src/modules/query_index/lib/engine.ts +53 -5
- package/src/modules/query_index/lib/indexer.ts +1 -1
- package/src/modules/query_index/lib/search-tokens.ts +13 -4
- package/src/modules/sales/api/documents/factory.ts +28 -3
- package/src/modules/sales/commands/documents.ts +26 -2
- package/src/modules/sales/components/useSalesChannelsEnabled.ts +2 -48
- package/src/modules/sales/data/validators.ts +50 -5
- package/src/modules/sales/di.ts +9 -0
- package/src/modules/sales/services/paymentOrderTotalResolver.ts +67 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.7-develop.
|
|
3
|
+
"version": "0.6.7-develop.6725.1.07a748cfc2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -254,16 +254,16 @@
|
|
|
254
254
|
"zod": "^4.4.3"
|
|
255
255
|
},
|
|
256
256
|
"peerDependencies": {
|
|
257
|
-
"@open-mercato/ai-assistant": "0.6.7-develop.
|
|
258
|
-
"@open-mercato/shared": "0.6.7-develop.
|
|
259
|
-
"@open-mercato/ui": "0.6.7-develop.
|
|
257
|
+
"@open-mercato/ai-assistant": "0.6.7-develop.6725.1.07a748cfc2",
|
|
258
|
+
"@open-mercato/shared": "0.6.7-develop.6725.1.07a748cfc2",
|
|
259
|
+
"@open-mercato/ui": "0.6.7-develop.6725.1.07a748cfc2",
|
|
260
260
|
"react": "^19.0.0",
|
|
261
261
|
"react-dom": "^19.0.0"
|
|
262
262
|
},
|
|
263
263
|
"devDependencies": {
|
|
264
|
-
"@open-mercato/ai-assistant": "0.6.7-develop.
|
|
265
|
-
"@open-mercato/shared": "0.6.7-develop.
|
|
266
|
-
"@open-mercato/ui": "0.6.7-develop.
|
|
264
|
+
"@open-mercato/ai-assistant": "0.6.7-develop.6725.1.07a748cfc2",
|
|
265
|
+
"@open-mercato/shared": "0.6.7-develop.6725.1.07a748cfc2",
|
|
266
|
+
"@open-mercato/ui": "0.6.7-develop.6725.1.07a748cfc2",
|
|
267
267
|
"@testing-library/dom": "^10.4.1",
|
|
268
268
|
"@testing-library/jest-dom": "^6.9.1",
|
|
269
269
|
"@testing-library/react": "^16.3.1",
|
|
@@ -5,6 +5,7 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
|
5
5
|
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
6
6
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
7
7
|
import { getBackendRouteManifests } from '@open-mercato/shared/modules/registry'
|
|
8
|
+
import { getModuleSurfaceFingerprint } from '@open-mercato/shared/lib/modules/surfaceFingerprint'
|
|
8
9
|
import { resolveFeatureCheckContext } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
9
10
|
import { groupBackendRoutesByModule, resolveBackendChromePayload } from '../../lib/backendChrome'
|
|
10
11
|
|
|
@@ -12,6 +13,13 @@ export const metadata = {
|
|
|
12
13
|
GET: { requireAuth: true },
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
// The fingerprint covers the module set, each module's declared features and
|
|
17
|
+
// the serializable route manifest, so the TTL only has to bound what it cannot
|
|
18
|
+
// see — chiefly a route `icon` swap, which serializes identically. Rebuilding
|
|
19
|
+
// this payload is expensive (a `renderToStaticMarkup` per nav icon plus several
|
|
20
|
+
// scoped queries), so the bound is generous rather than aggressive.
|
|
21
|
+
const NAV_CACHE_TTL_MS = 30 * 60 * 1000
|
|
22
|
+
|
|
15
23
|
const sidebarNavItemSchema: z.ZodType<{
|
|
16
24
|
id?: string
|
|
17
25
|
href: string
|
|
@@ -90,6 +98,13 @@ const adminNavResponseSchema = z.object({
|
|
|
90
98
|
profilePathPrefixes: z.array(z.string()),
|
|
91
99
|
grantedFeatures: z.array(z.string()),
|
|
92
100
|
roles: z.array(z.string()),
|
|
101
|
+
// Present when a single organization is in scope; `null` under an all-organizations selection or
|
|
102
|
+
// when the lookup fails. Declared optional so the response contract stays additive for clients
|
|
103
|
+
// generated against an older schema.
|
|
104
|
+
currentOrganization: z.object({
|
|
105
|
+
id: z.string(),
|
|
106
|
+
name: z.string(),
|
|
107
|
+
}).nullable().optional(),
|
|
93
108
|
})
|
|
94
109
|
|
|
95
110
|
const adminNavErrorSchema = z.object({
|
|
@@ -104,7 +119,7 @@ export async function GET(req: Request) {
|
|
|
104
119
|
const container = await createRequestContainer()
|
|
105
120
|
const cache = container.resolve('cache') as {
|
|
106
121
|
get?: (key: string) => Promise<unknown>
|
|
107
|
-
set?: (key: string, value: unknown, options?: { tags?: string[] }) => Promise<void>
|
|
122
|
+
set?: (key: string, value: unknown, options?: { tags?: string[]; ttl?: number }) => Promise<void>
|
|
108
123
|
} | null
|
|
109
124
|
let selectedOrganizationId: string | null | undefined
|
|
110
125
|
let selectedTenantId: string | null | undefined
|
|
@@ -138,8 +153,13 @@ export async function GET(req: Request) {
|
|
|
138
153
|
selectedTenantId = auth.tenantId ?? null
|
|
139
154
|
}
|
|
140
155
|
|
|
141
|
-
|
|
142
|
-
|
|
156
|
+
// v6: the payload gained `currentOrganization`, and the payload also embeds the enabled-module set,
|
|
157
|
+
// its declared features, and the backend route manifest. The selection is part of the key because
|
|
158
|
+
// the resolved organization cannot distinguish "all organizations" from "my own organization".
|
|
159
|
+
// The fingerprint invalidates module-surface changes; the TTL bounds anything it cannot observe.
|
|
160
|
+
const cacheVersion = `v6:${getModuleSurfaceFingerprint()}`
|
|
161
|
+
const cacheSelection = selectedOrganizationId === undefined ? 'default' : (selectedOrganizationId ?? 'null')
|
|
162
|
+
const cacheKey = `nav:sidebar:${cacheVersion}:${locale}:${auth.sub}:${cacheScopeTenantId || 'null'}:${cacheScopeOrganizationId || 'null'}:${cacheSelection}`
|
|
143
163
|
try {
|
|
144
164
|
if (cache?.get) {
|
|
145
165
|
const cached = await cache.get(cacheKey)
|
|
@@ -172,7 +192,7 @@ export async function GET(req: Request) {
|
|
|
172
192
|
`nav:sidebar:scope:${auth.sub}:${cacheScopeTenantId || 'null'}:${cacheScopeOrganizationId || 'null'}:${locale}`,
|
|
173
193
|
...((Array.isArray(auth.roles) ? auth.roles : []).map((role) => `nav:sidebar:role:${role}`)),
|
|
174
194
|
].filter(Boolean) as string[]
|
|
175
|
-
await cache.set(cacheKey, payload, { tags })
|
|
195
|
+
await cache.set(cacheKey, payload, { tags, ttl: NAV_CACHE_TTL_MS })
|
|
176
196
|
}
|
|
177
197
|
} catch {
|
|
178
198
|
// ignore cache write failures
|
|
@@ -23,9 +23,7 @@ import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/
|
|
|
23
23
|
import { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'
|
|
24
24
|
import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
|
|
25
25
|
import { parseBooleanFlag } from '@open-mercato/shared/lib/boolean'
|
|
26
|
-
import {
|
|
27
|
-
import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
|
|
28
|
-
import { sql } from 'kysely'
|
|
26
|
+
import { findEntityIdsBySearchTokensCompat, type SearchTokenDatabase } from '@open-mercato/shared/lib/search/tokenLookup'
|
|
29
27
|
import { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'
|
|
30
28
|
import {
|
|
31
29
|
getSelectedTenantFromRequest,
|
|
@@ -504,31 +502,13 @@ async function findUserIdsBySearchTokens(
|
|
|
504
502
|
tenantScope: string | null | undefined,
|
|
505
503
|
field?: string,
|
|
506
504
|
): Promise<string[] | null> {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const db = (em as any).getKysely() as any
|
|
515
|
-
let query = db
|
|
516
|
-
.selectFrom('search_tokens')
|
|
517
|
-
.select('entity_id')
|
|
518
|
-
.where('entity_type', '=', entityType)
|
|
519
|
-
.where('token_hash', 'in', hashes)
|
|
520
|
-
.groupBy('entity_id')
|
|
521
|
-
.having(sql<boolean>`count(distinct token_hash) >= ${hashes.length}`)
|
|
522
|
-
if (field) {
|
|
523
|
-
query = query.where('field', '=', field)
|
|
524
|
-
}
|
|
525
|
-
if (tenantScope !== undefined) {
|
|
526
|
-
query = query.where(sql<boolean>`tenant_id is not distinct from ${tenantScope}`)
|
|
527
|
-
}
|
|
528
|
-
const rows = (await query.execute()) as Array<{ entity_id?: unknown }>
|
|
529
|
-
return rows
|
|
530
|
-
.map((row) => (typeof row.entity_id === 'string' ? row.entity_id : null))
|
|
531
|
-
.filter((id): id is string => typeof id === 'string' && id.length > 0)
|
|
505
|
+
return findEntityIdsBySearchTokensCompat({
|
|
506
|
+
db: em.getKysely<SearchTokenDatabase>(),
|
|
507
|
+
entityType,
|
|
508
|
+
query: search,
|
|
509
|
+
fields: field ? [field] : undefined,
|
|
510
|
+
scope: { tenantId: tenantScope },
|
|
511
|
+
})
|
|
532
512
|
}
|
|
533
513
|
|
|
534
514
|
async function assertCanModifySuperAdminTarget(req: Request, targetUserId: string) {
|
|
@@ -135,7 +135,10 @@ export default function LoginPage() {
|
|
|
135
135
|
try {
|
|
136
136
|
const res = await apiCall<{ userId?: string }>('/api/auth/feature-check', {
|
|
137
137
|
method: 'POST',
|
|
138
|
-
headers: {
|
|
138
|
+
headers: {
|
|
139
|
+
'content-type': 'application/json',
|
|
140
|
+
'x-om-unauthorized-redirect': '0',
|
|
141
|
+
},
|
|
139
142
|
body: JSON.stringify({ features: [] }),
|
|
140
143
|
cache: 'no-store',
|
|
141
144
|
})
|
|
@@ -22,6 +22,7 @@ import { resolveRegisteredLucideIconNode } from '@open-mercato/ui/backend/icons/
|
|
|
22
22
|
import { profilePathPrefixes, profileSections } from './profile-sections'
|
|
23
23
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
24
24
|
import { filterGrantsByEnabledModules } from '@open-mercato/shared/security/enabledModulesRegistry'
|
|
25
|
+
import { getNavGroupOrderOverride } from '@open-mercato/shared/modules/overrides'
|
|
25
26
|
import {
|
|
26
27
|
getSelectedOrganizationFromRequest,
|
|
27
28
|
resolveFeatureCheckContext,
|
|
@@ -147,19 +148,35 @@ async function serializeNavItem(item: AdminNavItem): Promise<ResolvedNavItem> {
|
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
|
|
151
|
+
const defaultGroupOrder = [
|
|
152
|
+
'customers.nav.group',
|
|
153
|
+
'catalog.nav.group',
|
|
154
|
+
'customers~sales.nav.group',
|
|
155
|
+
'wms.nav.group',
|
|
156
|
+
'resources.nav.group',
|
|
157
|
+
'staff.nav.group',
|
|
158
|
+
'entities.nav.group',
|
|
159
|
+
'directory.nav.group',
|
|
160
|
+
'attachments.nav.group',
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Group ids ranked ahead of everything else, most significant first.
|
|
165
|
+
*
|
|
166
|
+
* An app may prepend its own ids via `overrides.nav.groupOrder` in `modules.ts`; ids it does not name
|
|
167
|
+
* keep the ordering they have today. With no override configured this returns `defaultGroupOrder`
|
|
168
|
+
* itself, so ordering is unchanged for every existing install.
|
|
169
|
+
*/
|
|
170
|
+
function resolveGroupOrder(): string[] {
|
|
171
|
+
const override = getNavGroupOrderOverride()
|
|
172
|
+
if (!override || override.length === 0) return defaultGroupOrder
|
|
173
|
+
const overridden = new Set(override)
|
|
174
|
+
return [...override, ...defaultGroupOrder.filter((id) => !overridden.has(id))]
|
|
175
|
+
}
|
|
176
|
+
|
|
150
177
|
function normalizeGroupWeights(groups: NavGroupWithWeight[]): NavGroupWithWeight[] {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
'catalog.nav.group',
|
|
154
|
-
'customers~sales.nav.group',
|
|
155
|
-
'wms.nav.group',
|
|
156
|
-
'resources.nav.group',
|
|
157
|
-
'staff.nav.group',
|
|
158
|
-
'entities.nav.group',
|
|
159
|
-
'directory.nav.group',
|
|
160
|
-
'attachments.nav.group',
|
|
161
|
-
]
|
|
162
|
-
const groupOrderIndex = new Map(defaultGroupOrder.map((id, index) => [id, index]))
|
|
178
|
+
const groupOrder = resolveGroupOrder()
|
|
179
|
+
const groupOrderIndex = new Map(groupOrder.map((id, index) => [id, index]))
|
|
163
180
|
groups.sort((a, b) => {
|
|
164
181
|
const aIndex = groupOrderIndex.get(a.id)
|
|
165
182
|
const bIndex = groupOrderIndex.get(b.id)
|
|
@@ -171,7 +188,7 @@ function normalizeGroupWeights(groups: NavGroupWithWeight[]): NavGroupWithWeight
|
|
|
171
188
|
if (a.weight !== b.weight) return a.weight - b.weight
|
|
172
189
|
return a.name.localeCompare(b.name)
|
|
173
190
|
})
|
|
174
|
-
const defaultGroupCount =
|
|
191
|
+
const defaultGroupCount = groupOrder.length
|
|
175
192
|
groups.forEach((group, index) => {
|
|
176
193
|
const rank = groupOrderIndex.get(group.id)
|
|
177
194
|
const fallbackWeight = typeof group.weight === 'number' ? group.weight : 10_000
|
|
@@ -275,6 +292,11 @@ export async function resolveBackendChromePayload({
|
|
|
275
292
|
|
|
276
293
|
let scopedOrganizationId: string | null = auth.orgId ?? null
|
|
277
294
|
let scopedTenantId: string | null = auth.tenantId ?? null
|
|
295
|
+
// The organization the caller actually *selected*, as distinct from the one the scope resolver fell
|
|
296
|
+
// back to. `resolveFeatureCheckContext` resolves `organizationId` to `auth.orgId` when no concrete
|
|
297
|
+
// organization is selected — which is precisely what an all-organizations view produces — so the
|
|
298
|
+
// resolved id cannot answer "which organization am I viewing".
|
|
299
|
+
let concretelySelectedOrganizationId: string | null = null
|
|
278
300
|
let allowNavigation = true
|
|
279
301
|
|
|
280
302
|
try {
|
|
@@ -287,12 +309,14 @@ export async function resolveBackendChromePayload({
|
|
|
287
309
|
})
|
|
288
310
|
scopedOrganizationId = organizationId
|
|
289
311
|
scopedTenantId = scope.tenantId ?? auth.tenantId ?? null
|
|
312
|
+
concretelySelectedOrganizationId = scope.selectedId ?? null
|
|
290
313
|
if (Array.isArray(allowedOrganizationIds) && allowedOrganizationIds.length === 0) {
|
|
291
314
|
allowNavigation = false
|
|
292
315
|
}
|
|
293
316
|
} catch {
|
|
294
317
|
scopedOrganizationId = auth.orgId ?? null
|
|
295
318
|
scopedTenantId = auth.tenantId ?? null
|
|
319
|
+
concretelySelectedOrganizationId = null
|
|
296
320
|
}
|
|
297
321
|
|
|
298
322
|
const acl = allowNavigation
|
|
@@ -411,6 +435,11 @@ export async function resolveBackendChromePayload({
|
|
|
411
435
|
?? (fallbackOrganizationId && !isAllOrganizationsSelection(fallbackOrganizationId) ? fallbackOrganizationId : null)
|
|
412
436
|
|
|
413
437
|
let brand: BackendChromePayload['brand'] = null
|
|
438
|
+
// Resolved here rather than left to callers. `brand` only populates when the organization has a
|
|
439
|
+
// logo, so it is a branding channel, not a dependable "which organization am I viewing" source.
|
|
440
|
+
// Without this field every downstream app has to fetch `/api/directory/organization-switcher` and
|
|
441
|
+
// walk its tree for the selected id. The row is already loaded below, so the name costs nothing.
|
|
442
|
+
let currentOrganization: BackendChromePayload['currentOrganization'] = null
|
|
414
443
|
if (brandOrganizationId && scopedTenantId) {
|
|
415
444
|
try {
|
|
416
445
|
const organization = await findOneWithDecryption(
|
|
@@ -420,6 +449,12 @@ export async function resolveBackendChromePayload({
|
|
|
420
449
|
undefined,
|
|
421
450
|
{ tenantId: scopedTenantId, organizationId: brandOrganizationId },
|
|
422
451
|
)
|
|
452
|
+
// Only when a concrete organization was selected. Under an all-organizations view
|
|
453
|
+
// `brandOrganizationId` still resolves (to the caller's own organization, which is what keeps
|
|
454
|
+
// branding working), so gating on the loaded row alone would misreport the scope.
|
|
455
|
+
if (organization && concretelySelectedOrganizationId === brandOrganizationId) {
|
|
456
|
+
currentOrganization = { id: String(organization.id), name: organization.name }
|
|
457
|
+
}
|
|
423
458
|
if (organization?.logoUrl) {
|
|
424
459
|
brand = {
|
|
425
460
|
name: organization.name,
|
|
@@ -430,7 +465,9 @@ export async function resolveBackendChromePayload({
|
|
|
430
465
|
}
|
|
431
466
|
}
|
|
432
467
|
} catch {
|
|
468
|
+
// Fail soft, as before: a failed organization lookup must not take down the nav payload.
|
|
433
469
|
brand = null
|
|
470
|
+
currentOrganization = null
|
|
434
471
|
}
|
|
435
472
|
}
|
|
436
473
|
|
|
@@ -443,5 +480,6 @@ export async function resolveBackendChromePayload({
|
|
|
443
480
|
grantedFeatures,
|
|
444
481
|
roles: Array.isArray(auth.roles) ? auth.roles : [],
|
|
445
482
|
brand,
|
|
483
|
+
currentOrganization,
|
|
446
484
|
}
|
|
447
485
|
}
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
mergeCompanyCustomFieldValues,
|
|
30
30
|
} from '../../../lib/customFieldRouting'
|
|
31
31
|
import { isOpenInteractionStatus, TERMINAL_INTERACTION_STATUS_LIST } from '../../../lib/interactionStatus'
|
|
32
|
+
import { isOpenDealStatus, isWonDealStatus } from '../../../lib/dealStatus'
|
|
32
33
|
import {
|
|
33
34
|
CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE,
|
|
34
35
|
EXAMPLE_TODO_SOURCE,
|
|
@@ -1015,10 +1016,8 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
1015
1016
|
.map((value) => (value instanceof Date ? value.toISOString() : typeof value === 'string' ? value : ''))
|
|
1016
1017
|
.filter((value) => value.length > 0),
|
|
1017
1018
|
)
|
|
1018
|
-
const activeDeals = dealLinksForMetrics.filter(
|
|
1019
|
-
|
|
1020
|
-
)
|
|
1021
|
-
const wonDeals = dealLinksForMetrics.filter((deal) => deal.status === 'won')
|
|
1019
|
+
const activeDeals = dealLinksForMetrics.filter((deal) => isOpenDealStatus(deal.status))
|
|
1020
|
+
const wonDeals = dealLinksForMetrics.filter((deal) => isWonDealStatus(deal.status))
|
|
1022
1021
|
const activeDealsValue = activeDeals.reduce((sum, deal) => sum + (parseDealAmount(deal.valueAmount) ?? 0), 0)
|
|
1023
1022
|
const ltvValue = wonDeals.length
|
|
1024
1023
|
? wonDeals.reduce((sum, deal) => sum + (parseDealAmount(deal.valueAmount) ?? 0), 0)
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
filterActivePersonCompanyLinks,
|
|
26
26
|
withActiveCustomerPersonCompanyLinkFilter,
|
|
27
27
|
} from '../../../../../lib/personCompanyLinkTable'
|
|
28
|
+
import { isOpenDealStatus, isWonDealStatus } from '../../../../../lib/dealStatus'
|
|
28
29
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
29
30
|
|
|
30
31
|
const logger = createLogger('customers')
|
|
@@ -280,13 +281,13 @@ export async function GET(req: Request, ctx: { params?: { id?: string } }) {
|
|
|
280
281
|
|
|
281
282
|
const activeDeals = companyDealLinks
|
|
282
283
|
.map((dcl) => dcl.deal as CustomerDeal)
|
|
283
|
-
.filter((deal) => deal.status
|
|
284
|
+
.filter((deal) => isOpenDealStatus(deal.status) && !deal.deletedAt)
|
|
284
285
|
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
|
|
285
286
|
const activeDeal = activeDeals.length > 0 ? activeDeals[0] : null
|
|
286
287
|
|
|
287
288
|
const wonDeals = companyDealLinks
|
|
288
289
|
.map((dcl) => dcl.deal as CustomerDeal)
|
|
289
|
-
.filter((deal) => deal.status
|
|
290
|
+
.filter((deal) => isWonDealStatus(deal.status) && !deal.deletedAt)
|
|
290
291
|
let clv: { amount: number; currency: string } | null = null
|
|
291
292
|
if (wonDeals.length > 0) {
|
|
292
293
|
const currencies = new Map<string, number>()
|
|
@@ -4,8 +4,7 @@ import { sql } from 'kysely'
|
|
|
4
4
|
import type { CrudCtx } from '@open-mercato/shared/lib/crud/factory'
|
|
5
5
|
import type { EntityId } from '@open-mercato/shared/modules/entities'
|
|
6
6
|
import type { QueryCustomFieldSource, QueryJoinEdge, QueryEngine } from '@open-mercato/shared/lib/query/types'
|
|
7
|
-
import {
|
|
8
|
-
import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
|
|
7
|
+
import { findEntityIdsBySearchTokensCompat, type SearchTokenDatabase } from '@open-mercato/shared/lib/search/tokenLookup'
|
|
9
8
|
import { SortDir } from '@open-mercato/shared/lib/query/types'
|
|
10
9
|
|
|
11
10
|
const { withScopedPayload, parseScopedCommandInput } = createScopedApiHelpers({
|
|
@@ -106,36 +105,18 @@ async function findSearchTokenEntityIds({
|
|
|
106
105
|
fields,
|
|
107
106
|
query,
|
|
108
107
|
}: SearchTokenMatchInput): Promise<string[] | null> {
|
|
109
|
-
const trimmed = query.trim()
|
|
110
|
-
if (!trimmed) return null
|
|
111
|
-
|
|
112
|
-
const tokens = tokenizeText(trimmed, resolveSearchConfig())
|
|
113
|
-
if (!tokens.hashes.length) return []
|
|
114
|
-
|
|
115
108
|
const em = ctx.container.resolve('em') as EntityManager
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
searchQuery = searchQuery.where(sql<boolean>`tenant_id is not distinct from ${ctx.auth?.tenantId ?? null}`)
|
|
128
|
-
}
|
|
129
|
-
if (ctx.selectedOrganizationId) {
|
|
130
|
-
searchQuery = searchQuery.where('organization_id', '=', ctx.selectedOrganizationId)
|
|
131
|
-
} else if (Array.isArray(ctx.organizationIds) && ctx.organizationIds.length > 0) {
|
|
132
|
-
searchQuery = searchQuery.where('organization_id', 'in', ctx.organizationIds)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const rows = await searchQuery.execute() as Array<{ entity_id?: unknown }>
|
|
136
|
-
return rows
|
|
137
|
-
.map((row) => (typeof row.entity_id === 'string' ? row.entity_id : null))
|
|
138
|
-
.filter((id): id is string => typeof id === 'string' && id.length > 0)
|
|
109
|
+
return findEntityIdsBySearchTokensCompat({
|
|
110
|
+
db: em.getKysely<SearchTokenDatabase>(),
|
|
111
|
+
entityType,
|
|
112
|
+
query,
|
|
113
|
+
fields,
|
|
114
|
+
scope: {
|
|
115
|
+
tenantId: ctx.auth?.tenantId !== undefined ? ctx.auth?.tenantId ?? null : undefined,
|
|
116
|
+
organizationId: ctx.selectedOrganizationId ?? undefined,
|
|
117
|
+
organizationIds: ctx.organizationIds,
|
|
118
|
+
},
|
|
119
|
+
})
|
|
139
120
|
}
|
|
140
121
|
|
|
141
122
|
async function mapScopedEntityIds({
|
|
@@ -12,6 +12,7 @@ import { E as CoreEntities } from '#generated/entities.ids.generated'
|
|
|
12
12
|
import { createProgressBar } from '@open-mercato/shared/lib/cli/progress'
|
|
13
13
|
import { buildIndexDocument, type IndexCustomFieldValue } from '@open-mercato/core/modules/query_index/lib/document'
|
|
14
14
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
15
|
+
import { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'
|
|
15
16
|
import type { QueryEngine } from '@open-mercato/shared/lib/query/types'
|
|
16
17
|
import type { EntityId } from '@open-mercato/shared/modules/entities'
|
|
17
18
|
import {
|
|
@@ -1994,6 +1995,7 @@ async function seedCustomerStressTest(
|
|
|
1994
1995
|
updated_at: Date
|
|
1995
1996
|
deleted_at: null
|
|
1996
1997
|
}> = []
|
|
1998
|
+
const searchConfig = resolveSearchConfig()
|
|
1997
1999
|
for (const [entityType, bucket] of pendingIndexDocs.entries()) {
|
|
1998
2000
|
for (const entry of bucket.values()) {
|
|
1999
2001
|
if (!entry.baseRow || Object.keys(entry.baseRow).length === 0) continue
|
|
@@ -2002,10 +2004,15 @@ async function seedCustomerStressTest(
|
|
|
2002
2004
|
entity_id: entry.recordId,
|
|
2003
2005
|
organization_id: entry.organizationId,
|
|
2004
2006
|
tenant_id: entry.tenantId,
|
|
2005
|
-
doc: buildIndexDocument(
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2007
|
+
doc: buildIndexDocument(
|
|
2008
|
+
entry.baseRow,
|
|
2009
|
+
entry.customFields,
|
|
2010
|
+
{
|
|
2011
|
+
organizationId: entry.organizationId,
|
|
2012
|
+
tenantId: entry.tenantId,
|
|
2013
|
+
},
|
|
2014
|
+
{ entityType, config: searchConfig },
|
|
2015
|
+
),
|
|
2009
2016
|
index_version: 1,
|
|
2010
2017
|
created_at: entry.createdAt,
|
|
2011
2018
|
updated_at: entry.updatedAt,
|
|
@@ -7,6 +7,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
|
7
7
|
import { Badge } from '@open-mercato/ui/primitives/badge'
|
|
8
8
|
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
9
9
|
import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
10
|
+
import { isOpenDealStatus } from '../../lib/dealStatus'
|
|
10
11
|
import type { DealSummary } from '../formConfig'
|
|
11
12
|
import { formatCurrency } from './utils'
|
|
12
13
|
|
|
@@ -25,7 +26,7 @@ export function ActiveDealCard({ deals, onHide }: ActiveDealCardProps) {
|
|
|
25
26
|
const [pipelineStages, setPipelineStages] = React.useState<PipelineStageOption[]>([])
|
|
26
27
|
|
|
27
28
|
const activeDeals = React.useMemo(
|
|
28
|
-
() => deals.filter((d) => d.status
|
|
29
|
+
() => deals.filter((d) => isOpenDealStatus(d.status)),
|
|
29
30
|
[deals],
|
|
30
31
|
)
|
|
31
32
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
writeVersionedIdSet,
|
|
12
12
|
clearVersionedPreference,
|
|
13
13
|
} from '@open-mercato/shared/lib/browser/versionedPreference'
|
|
14
|
+
import { isOpenDealStatus, isWonDealStatus } from '../../lib/dealStatus'
|
|
14
15
|
import type { CompanyOverview, DealSummary, InteractionSummary } from '../formConfig'
|
|
15
16
|
import { formatCurrency } from './utils'
|
|
16
17
|
|
|
@@ -19,7 +20,7 @@ const STORAGE_VERSION = 1
|
|
|
19
20
|
|
|
20
21
|
function sumActiveDeals(deals: DealSummary[]): number {
|
|
21
22
|
return deals
|
|
22
|
-
.filter((d) => d.status
|
|
23
|
+
.filter((d) => isOpenDealStatus(d.status))
|
|
23
24
|
.reduce((sum, d) => {
|
|
24
25
|
const amount = typeof d.valueAmount === 'number' ? d.valueAmount : parseFloat(String(d.valueAmount ?? '0'))
|
|
25
26
|
return sum + (Number.isFinite(amount) ? amount : 0)
|
|
@@ -27,7 +28,7 @@ function sumActiveDeals(deals: DealSummary[]): number {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
function getActiveDeals(deals: DealSummary[]): DealSummary[] {
|
|
30
|
-
return deals.filter((d) => d.status
|
|
31
|
+
return deals.filter((d) => isOpenDealStatus(d.status))
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
function computeActivityTrend(interactions: InteractionSummary[]): KpiTrend | undefined {
|
|
@@ -51,7 +52,7 @@ function computeActivityTrend(interactions: InteractionSummary[]): KpiTrend | un
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
function computeDealTrend(deals: DealSummary[]): KpiTrend | undefined {
|
|
54
|
-
const active = deals.filter((d) => d.status
|
|
55
|
+
const active = deals.filter((d) => isOpenDealStatus(d.status))
|
|
55
56
|
if (active.length === 0) return undefined
|
|
56
57
|
const now = Date.now()
|
|
57
58
|
const monthMs = 30 * 86_400_000
|
|
@@ -81,7 +82,7 @@ export function CompanyKpiBar({ data }: CompanyKpiBarProps) {
|
|
|
81
82
|
|
|
82
83
|
const ltvValue = React.useMemo(() => {
|
|
83
84
|
if (data.kpis?.ltvValue !== undefined) return data.kpis.ltvValue
|
|
84
|
-
const wonDeals = data.deals.filter((d) => d.status
|
|
85
|
+
const wonDeals = data.deals.filter((d) => isWonDealStatus(d.status))
|
|
85
86
|
if (wonDeals.length === 0) return null
|
|
86
87
|
return wonDeals.reduce((sum, d) => {
|
|
87
88
|
const amt = typeof d.valueAmount === 'number' ? d.valueAmount : parseFloat(String(d.valueAmount ?? '0'))
|
|
@@ -154,7 +155,7 @@ export function CompanyKpiBar({ data }: CompanyKpiBarProps) {
|
|
|
154
155
|
: `${v} ${v === 1 ? t('customers.companies.dashboard.kpi.year', 'year') : t('customers.companies.dashboard.kpi.years', 'years')}`
|
|
155
156
|
: undefined,
|
|
156
157
|
comparisonLabel: clientTenureYears !== null
|
|
157
|
-
? `${data.kpis?.completedDealsCount ?? data.deals.filter((d) => d.status
|
|
158
|
+
? `${data.kpis?.completedDealsCount ?? data.deals.filter((d) => isWonDealStatus(d.status)).length} ${t('customers.companies.dashboard.kpi.completedDeals', 'completed deals')}`
|
|
158
159
|
: t('customers.companies.dashboard.kpi.noInteractions', 'No interactions yet'),
|
|
159
160
|
},
|
|
160
161
|
], [t, activeDealsValue, dealTrend, dealCurrency, activeDeals.length, activityTrend, ltvValue, clientTenureYears, data.deals, data.interactions.length, data.kpis])
|
|
@@ -99,6 +99,8 @@ interface ScheduleActivityDialogProps {
|
|
|
99
99
|
onActivityCreated?: () => void
|
|
100
100
|
/** When provided, dialog opens in edit mode with pre-filled data */
|
|
101
101
|
editData?: ScheduleActivityEditData | null
|
|
102
|
+
/** Default country ISO2 code for phone number input when value is empty/unparsed */
|
|
103
|
+
defaultCountryIso2?: string
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
export function ScheduleActivityDialog({
|
|
@@ -111,6 +113,7 @@ export function ScheduleActivityDialog({
|
|
|
111
113
|
entityType,
|
|
112
114
|
onActivityCreated,
|
|
113
115
|
editData,
|
|
116
|
+
defaultCountryIso2,
|
|
114
117
|
}: ScheduleActivityDialogProps) {
|
|
115
118
|
const t = useT()
|
|
116
119
|
const state = useScheduleFormState({ open, editData: editData ?? null })
|
|
@@ -689,6 +692,7 @@ export function ScheduleActivityDialog({
|
|
|
689
692
|
externalError={callPhoneError}
|
|
690
693
|
invalidLabel={callPhoneInvalidMessage}
|
|
691
694
|
minDigits={7}
|
|
695
|
+
defaultCountryIso2={defaultCountryIso2}
|
|
692
696
|
/>
|
|
693
697
|
</div>
|
|
694
698
|
) : (
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { KpiTrend } from '@open-mercato/ui/backend/charts/KpiCard'
|
|
2
|
+
import { isOpenDealStatus } from '../../../lib/dealStatus'
|
|
2
3
|
import type { DealSummary, InteractionSummary, TodoLinkSummary } from '../../formConfig'
|
|
3
4
|
|
|
4
5
|
export function sumActiveDeals(deals: DealSummary[]): number {
|
|
5
6
|
return deals
|
|
6
|
-
.filter((d) => d.status
|
|
7
|
+
.filter((d) => isOpenDealStatus(d.status))
|
|
7
8
|
.reduce((sum, d) => {
|
|
8
9
|
const amount = typeof d.valueAmount === 'number' ? d.valueAmount : parseFloat(String(d.valueAmount ?? '0'))
|
|
9
10
|
return sum + (Number.isFinite(amount) ? amount : 0)
|
|
@@ -11,7 +12,7 @@ export function sumActiveDeals(deals: DealSummary[]): number {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function getActiveDeals(deals: DealSummary[]): DealSummary[] {
|
|
14
|
-
return deals.filter((d) => d.status
|
|
15
|
+
return deals.filter((d) => isOpenDealStatus(d.status))
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function getOpenTasks(todos: TodoLinkSummary[]): TodoLinkSummary[] {
|
|
@@ -68,7 +69,7 @@ export function computeActivityTrend(interactions: InteractionSummary[]): KpiTre
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
export function computeDealTrend(deals: DealSummary[]): KpiTrend | undefined {
|
|
71
|
-
const active = deals.filter((d) => d.status
|
|
72
|
+
const active = deals.filter((d) => isOpenDealStatus(d.status))
|
|
72
73
|
if (active.length === 0) return undefined
|
|
73
74
|
const now = Date.now()
|
|
74
75
|
const monthMs = 30 * 86_400_000
|
|
@@ -418,7 +418,7 @@ const companyDictionaryFieldDefinitions: DictionaryFieldDefinition[] = [
|
|
|
418
418
|
},
|
|
419
419
|
]
|
|
420
420
|
|
|
421
|
-
const createPrimaryPhoneField = (t: Translator): CrudField => ({
|
|
421
|
+
const createPrimaryPhoneField = (t: Translator, defaultCountryIso2?: string): CrudField => ({
|
|
422
422
|
id: 'primaryPhone',
|
|
423
423
|
label: t('customers.people.form.primaryPhone'),
|
|
424
424
|
type: 'custom',
|
|
@@ -447,6 +447,7 @@ const createPrimaryPhoneField = (t: Translator): CrudField => ({
|
|
|
447
447
|
invalidLabel={t('customers.people.form.primaryPhone.invalid', 'Enter a valid phone number with country code (e.g. +1 212 555 1234)')}
|
|
448
448
|
minDigits={7}
|
|
449
449
|
onDuplicateLookup={!disabled && !error ? duplicateLookup : undefined}
|
|
450
|
+
defaultCountryIso2={defaultCountryIso2}
|
|
450
451
|
/>
|
|
451
452
|
)
|
|
452
453
|
},
|
|
@@ -869,7 +870,8 @@ export const createDisplayNameSection = (t: Translator) =>
|
|
|
869
870
|
)
|
|
870
871
|
}
|
|
871
872
|
|
|
872
|
-
export const createPersonFormFields = (t: Translator): CrudField[] => {
|
|
873
|
+
export const createPersonFormFields = (t: Translator, options?: { defaultCountryIso2?: string }): CrudField[] => {
|
|
874
|
+
const defaultCountryIso2 = options?.defaultCountryIso2
|
|
873
875
|
const contactSection = createSectionHeadingField('__contactInformationSection', t('customers.people.form.sections.contactInformation'))
|
|
874
876
|
const companySection = createSectionHeadingField('__companyInformationSection', t('customers.people.form.sections.companyInformation'))
|
|
875
877
|
const dictionaryFields: CrudField[] = dictionaryFieldDefinitions.map((definition) => ({
|
|
@@ -930,7 +932,7 @@ export const createPersonFormFields = (t: Translator): CrudField[] => {
|
|
|
930
932
|
},
|
|
931
933
|
contactSection,
|
|
932
934
|
createPrimaryEmailField(t),
|
|
933
|
-
createPrimaryPhoneField(t),
|
|
935
|
+
createPrimaryPhoneField(t, defaultCountryIso2),
|
|
934
936
|
companySection,
|
|
935
937
|
{
|
|
936
938
|
id: 'companyEntityId',
|
|
@@ -1225,7 +1227,8 @@ export const createCompanyFormSchema = () =>
|
|
|
1225
1227
|
})
|
|
1226
1228
|
.passthrough()
|
|
1227
1229
|
|
|
1228
|
-
export const createCompanyFormFields = (t: Translator): CrudField[] => {
|
|
1230
|
+
export const createCompanyFormFields = (t: Translator, options?: { defaultCountryIso2?: string }): CrudField[] => {
|
|
1231
|
+
const defaultCountryIso2 = options?.defaultCountryIso2
|
|
1229
1232
|
const dictionaryFields: CrudField[] = companyDictionaryFieldDefinitions.map((definition) => ({
|
|
1230
1233
|
id: definition.id,
|
|
1231
1234
|
label: t(definition.labelKey),
|
|
@@ -1270,6 +1273,7 @@ export const createCompanyFormFields = (t: Translator): CrudField[] => {
|
|
|
1270
1273
|
placeholder={t('customers.companies.form.primaryPhonePlaceholder', '+1 555 123 4567')}
|
|
1271
1274
|
invalidLabel={t('customers.people.form.primaryPhone.invalid', 'Enter a valid phone number with country code (e.g. +1 212 555 1234)')}
|
|
1272
1275
|
minDigits={7}
|
|
1276
|
+
defaultCountryIso2={defaultCountryIso2}
|
|
1273
1277
|
/>
|
|
1274
1278
|
),
|
|
1275
1279
|
} as CrudField,
|
|
@@ -1651,8 +1655,8 @@ const buildIndustryLabels = (t: Translator): DictionarySelectLabels => ({
|
|
|
1651
1655
|
manageTitle: t('customers.people.form.dictionary.manage'),
|
|
1652
1656
|
})
|
|
1653
1657
|
|
|
1654
|
-
export const createCompanyEditFields = (t: Translator): CrudField[] => {
|
|
1655
|
-
const baseFields = createCompanyFormFields(t)
|
|
1658
|
+
export const createCompanyEditFields = (t: Translator, options?: { defaultCountryIso2?: string }): CrudField[] => {
|
|
1659
|
+
const baseFields = createCompanyFormFields(t, options)
|
|
1656
1660
|
const industryLabels = buildIndustryLabels(t)
|
|
1657
1661
|
|
|
1658
1662
|
return baseFields.map((field) => {
|
|
@@ -1676,8 +1680,8 @@ export const createCompanyEditFields = (t: Translator): CrudField[] => {
|
|
|
1676
1680
|
})
|
|
1677
1681
|
}
|
|
1678
1682
|
|
|
1679
|
-
export const createPersonEditFields = (t: Translator): CrudField[] => {
|
|
1680
|
-
const baseFields = createPersonFormFields(t)
|
|
1683
|
+
export const createPersonEditFields = (t: Translator, options?: { defaultCountryIso2?: string }): CrudField[] => {
|
|
1684
|
+
const baseFields = createPersonFormFields(t, options)
|
|
1681
1685
|
return [
|
|
1682
1686
|
...baseFields,
|
|
1683
1687
|
{
|