@open-mercato/core 0.5.1-develop.2912.8d7b1fef24 → 0.5.1-develop.2924.d13908516e
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/companies/[id]/people/route.js +12 -7
- package/dist/modules/customers/api/companies/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +2 -1
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +7 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/commands/companies.js +93 -19
- package/dist/modules/customers/commands/companies.js.map +2 -2
- package/dist/modules/customers/commands/people.js +9 -1
- package/dist/modules/customers/commands/people.js.map +2 -2
- package/dist/modules/customers/commands/personCompanyLinks.js +2 -2
- package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyCard.js +32 -3
- package/dist/modules/customers/components/detail/CompanyCard.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js +37 -19
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +7 -4
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js +63 -2
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +37 -19
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/TasksSection.js +1 -11
- package/dist/modules/customers/components/detail/TasksSection.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +50 -39
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/events.js +3 -3
- package/dist/modules/customers/events.js.map +2 -2
- package/dist/modules/customers/lib/displayName.js +13 -1
- package/dist/modules/customers/lib/displayName.js.map +2 -2
- package/dist/modules/customers/lib/personCompanies.js +12 -7
- package/dist/modules/customers/lib/personCompanies.js.map +2 -2
- package/dist/modules/customers/lib/personCompanyLinkTable.js +5 -0
- package/dist/modules/customers/lib/personCompanyLinkTable.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js +21 -17
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/package.json +3 -3
- package/src/modules/customers/api/companies/[id]/people/route.ts +12 -7
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +2 -1
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +12 -2
- package/src/modules/customers/commands/companies.ts +107 -19
- package/src/modules/customers/commands/people.ts +16 -1
- package/src/modules/customers/commands/personCompanyLinks.ts +3 -2
- package/src/modules/customers/components/detail/CompanyCard.tsx +28 -4
- package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +18 -2
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +8 -4
- package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +66 -0
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +18 -2
- package/src/modules/customers/components/detail/TasksSection.tsx +1 -8
- package/src/modules/customers/components/formConfig.tsx +59 -40
- package/src/modules/customers/events.ts +3 -3
- package/src/modules/customers/i18n/de.json +10 -0
- package/src/modules/customers/i18n/en.json +10 -0
- package/src/modules/customers/i18n/es.json +10 -0
- package/src/modules/customers/i18n/pl.json +10 -0
- package/src/modules/customers/lib/displayName.ts +19 -0
- package/src/modules/customers/lib/personCompanies.ts +12 -7
- package/src/modules/customers/lib/personCompanyLinkTable.ts +14 -0
- package/src/modules/workflows/lib/activity-executor.ts +21 -18
|
@@ -7,6 +7,7 @@ import { Check, Pencil, Plus, Settings } from 'lucide-react'
|
|
|
7
7
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
8
8
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
9
9
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
10
|
+
import { deriveDisplayName, isDerivedDisplayName } from '../lib/displayName'
|
|
10
11
|
import {
|
|
11
12
|
Dialog,
|
|
12
13
|
DialogContent,
|
|
@@ -703,16 +704,18 @@ export const createDisplayNameSection = (t: Translator) =>
|
|
|
703
704
|
function DisplayNameSection({ values, setValue, errors }: CrudFormGroupComponentProps) {
|
|
704
705
|
const [editing, setEditing] = React.useState(false)
|
|
705
706
|
const [manualOverride, setManualOverride] = React.useState(() => {
|
|
706
|
-
const current = typeof values.displayName === 'string' ? values.displayName
|
|
707
|
-
|
|
707
|
+
const current = typeof values.displayName === 'string' ? values.displayName : ''
|
|
708
|
+
const firstInit = typeof values.firstName === 'string' ? values.firstName : ''
|
|
709
|
+
const lastInit = typeof values.lastName === 'string' ? values.lastName : ''
|
|
710
|
+
// Sticky-manual: treat as user-customized only when the persisted display name
|
|
711
|
+
// doesn't match the first+last derivation (matches the server-side rule in
|
|
712
|
+
// updatePersonCommand). Empty values are considered derived.
|
|
713
|
+
return !isDerivedDisplayName(current, firstInit, lastInit)
|
|
708
714
|
})
|
|
709
715
|
|
|
710
716
|
const first = typeof values.firstName === 'string' ? values.firstName.trim() : ''
|
|
711
717
|
const last = typeof values.lastName === 'string' ? values.lastName.trim() : ''
|
|
712
|
-
const derived = React.useMemo(() =>
|
|
713
|
-
const parts = [first, last].filter((part) => !!part)
|
|
714
|
-
return parts.join(' ').trim()
|
|
715
|
-
}, [first, last])
|
|
718
|
+
const derived = React.useMemo(() => deriveDisplayName(first, last), [first, last])
|
|
716
719
|
|
|
717
720
|
React.useEffect(() => {
|
|
718
721
|
if (!manualOverride) {
|
|
@@ -1684,40 +1687,56 @@ export const createPersonEditGroups = (t: Translator): CrudFormGroup[] => [
|
|
|
1684
1687
|
* Groups for the Person v2 "Dane osobowe" Figma layout (SPEC-048 mockup).
|
|
1685
1688
|
* All groups in column 1 (Zone 1). Notes handled separately in Zone 2 tabs.
|
|
1686
1689
|
*/
|
|
1687
|
-
export const createPersonPersonalDataGroups = (
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1690
|
+
export const createPersonPersonalDataGroups = (
|
|
1691
|
+
t: Translator,
|
|
1692
|
+
options?: { entityName?: string | null },
|
|
1693
|
+
): CrudFormGroup[] => {
|
|
1694
|
+
const entityName = options?.entityName?.trim() || null
|
|
1695
|
+
const rolesTitle = entityName
|
|
1696
|
+
? t('customers.roles.groupTitle.person', 'My roles with {{name}}', { name: entityName })
|
|
1697
|
+
: t('customers.people.form.groups.roles', 'My roles')
|
|
1698
|
+
return [
|
|
1699
|
+
{
|
|
1700
|
+
id: 'personalDataDisplay',
|
|
1701
|
+
title: t('customers.people.form.groups.displayName', 'Display name'),
|
|
1702
|
+
column: 1,
|
|
1703
|
+
bare: true,
|
|
1704
|
+
component: createDisplayNameSection(t),
|
|
1705
|
+
},
|
|
1706
|
+
{
|
|
1707
|
+
id: 'personalData',
|
|
1708
|
+
title: t('customers.people.form.groups.personalData', 'Personal data'),
|
|
1709
|
+
column: 1,
|
|
1710
|
+
fields: ['firstName', 'lastName', 'jobTitle', 'primaryEmail', 'primaryPhone'],
|
|
1711
|
+
},
|
|
1712
|
+
{
|
|
1713
|
+
id: 'companyRole',
|
|
1714
|
+
title: t('customers.people.form.groups.companyRole', 'Company & role'),
|
|
1715
|
+
column: 1,
|
|
1716
|
+
fields: ['companyEntityId', 'status', 'lifecycleStage', 'source'],
|
|
1717
|
+
},
|
|
1718
|
+
{
|
|
1719
|
+
id: 'customFields',
|
|
1720
|
+
title: t('customers.people.form.groups.customAttributes', 'Custom attributes'),
|
|
1721
|
+
column: 1,
|
|
1722
|
+
kind: 'customFields',
|
|
1723
|
+
},
|
|
1724
|
+
{
|
|
1725
|
+
id: 'roles',
|
|
1726
|
+
title: rolesTitle,
|
|
1727
|
+
column: 1,
|
|
1728
|
+
component: ({ values }: CrudFormGroupComponentProps) => (
|
|
1729
|
+
values.id ? (
|
|
1730
|
+
<RolesSection
|
|
1731
|
+
entityType="person"
|
|
1732
|
+
entityId={values.id as string}
|
|
1733
|
+
entityName={typeof values.displayName === 'string' ? values.displayName : null}
|
|
1734
|
+
/>
|
|
1735
|
+
) : null
|
|
1736
|
+
),
|
|
1737
|
+
},
|
|
1738
|
+
]
|
|
1739
|
+
}
|
|
1721
1740
|
|
|
1722
1741
|
// ---------------------------------------------------------------------------
|
|
1723
1742
|
// Edit-mode payload builders
|
|
@@ -75,9 +75,9 @@ const events = [
|
|
|
75
75
|
{ id: 'customers.label_assignment.deleted', label: 'Label Unassigned', entity: 'label_assignment', category: 'crud' },
|
|
76
76
|
|
|
77
77
|
// Person-Company Links
|
|
78
|
-
{ id: 'customers.person_company_link.created', label: 'Person Linked To Company', entity: 'person_company_link', category: 'crud' },
|
|
79
|
-
{ id: 'customers.person_company_link.updated', label: 'Person-Company Link Updated', entity: 'person_company_link', category: 'crud' },
|
|
80
|
-
{ id: 'customers.person_company_link.deleted', label: 'Person Unlinked From Company', entity: 'person_company_link', category: 'crud' },
|
|
78
|
+
{ id: 'customers.person_company_link.created', label: 'Person Linked To Company', entity: 'person_company_link', category: 'crud', clientBroadcast: true },
|
|
79
|
+
{ id: 'customers.person_company_link.updated', label: 'Person-Company Link Updated', entity: 'person_company_link', category: 'crud', clientBroadcast: true },
|
|
80
|
+
{ id: 'customers.person_company_link.deleted', label: 'Person Unlinked From Company', entity: 'person_company_link', category: 'crud', clientBroadcast: true },
|
|
81
81
|
] as const
|
|
82
82
|
|
|
83
83
|
export const eventsConfig = createModuleEvents({
|
|
@@ -195,6 +195,10 @@
|
|
|
195
195
|
"customers.companies.dashboard.seeAllActivity": "Alle {{count}} Aktivitäten anzeigen",
|
|
196
196
|
"customers.companies.dashboard.showAll": "Show all",
|
|
197
197
|
"customers.companies.dashboard.upcomingMeetings": "Anstehende Meetings",
|
|
198
|
+
"customers.companies.delete.blocked": "Unternehmen kann nicht gelöscht werden: {{blockers}}. Bitte zuerst Verknüpfungen lösen oder neu zuweisen.",
|
|
199
|
+
"customers.companies.delete.blockers.deals": "verknüpfte Deals ({{count}})",
|
|
200
|
+
"customers.companies.delete.blockers.directPeople": "Personen, deren primäres Unternehmen dies ist ({{count}})",
|
|
201
|
+
"customers.companies.delete.blockers.persons": "verknüpfte Personen ({{count}})",
|
|
198
202
|
"customers.companies.detail.actions.addDeal": "Deal hinzufügen",
|
|
199
203
|
"customers.companies.detail.actions.backToList": "Zurück zu Unternehmen",
|
|
200
204
|
"customers.companies.detail.actions.cancel": "Abbrechen",
|
|
@@ -1189,6 +1193,11 @@
|
|
|
1189
1193
|
"customers.people.detail.companies.sortRecent": "Sort: Recently active",
|
|
1190
1194
|
"customers.people.detail.companies.subtitle": "Link one or more companies and choose the primary relationship for this person.",
|
|
1191
1195
|
"customers.people.detail.companies.summary": "{{count}} verknüpfte Firmen",
|
|
1196
|
+
"customers.people.detail.companies.unlinkAction": "Verknüpfung lösen",
|
|
1197
|
+
"customers.people.detail.companies.unlinkConfirm": "Verknüpfung von {{company}} mit {{person}} lösen?",
|
|
1198
|
+
"customers.people.detail.companies.unlinkConfirmTitle": "Firma entkoppeln",
|
|
1199
|
+
"customers.people.detail.companies.unlinkError": "Verknüpfung der Firma konnte nicht gelöst werden.",
|
|
1200
|
+
"customers.people.detail.companies.unlinkSuccess": "Firma entkoppelt.",
|
|
1192
1201
|
"customers.people.detail.company.current": "{{company}}",
|
|
1193
1202
|
"customers.people.detail.company.empty": "Kein Unternehmen zugewiesen",
|
|
1194
1203
|
"customers.people.detail.company.label": "Unternehmen",
|
|
@@ -1530,6 +1539,7 @@
|
|
|
1530
1539
|
"customers.people.form.groups.custom": "Individuelle Felder",
|
|
1531
1540
|
"customers.people.form.groups.customAttributes": "Custom attributes",
|
|
1532
1541
|
"customers.people.form.groups.details": "Details",
|
|
1542
|
+
"customers.people.form.groups.displayName": "Anzeigename",
|
|
1533
1543
|
"customers.people.form.groups.notes": "Notizen",
|
|
1534
1544
|
"customers.people.form.groups.personalData": "Personal data",
|
|
1535
1545
|
"customers.people.form.groups.roles": "My roles",
|
|
@@ -195,6 +195,10 @@
|
|
|
195
195
|
"customers.companies.dashboard.seeAllActivity": "See all {{count}} activities",
|
|
196
196
|
"customers.companies.dashboard.showAll": "Show all",
|
|
197
197
|
"customers.companies.dashboard.upcomingMeetings": "Upcoming meetings",
|
|
198
|
+
"customers.companies.delete.blocked": "Cannot delete company: {{blockers}}. Please unlink or reassign first.",
|
|
199
|
+
"customers.companies.delete.blockers.deals": "linked deals ({{count}})",
|
|
200
|
+
"customers.companies.delete.blockers.directPeople": "persons whose primary company is this one ({{count}})",
|
|
201
|
+
"customers.companies.delete.blockers.persons": "linked persons ({{count}})",
|
|
198
202
|
"customers.companies.detail.actions.addDeal": "Add deal",
|
|
199
203
|
"customers.companies.detail.actions.backToList": "Back to companies",
|
|
200
204
|
"customers.companies.detail.actions.cancel": "Cancel",
|
|
@@ -1189,6 +1193,11 @@
|
|
|
1189
1193
|
"customers.people.detail.companies.sortRecent": "Sort: Recently active",
|
|
1190
1194
|
"customers.people.detail.companies.subtitle": "Link one or more companies and choose the primary relationship for this person.",
|
|
1191
1195
|
"customers.people.detail.companies.summary": "{{count}} linked companies",
|
|
1196
|
+
"customers.people.detail.companies.unlinkAction": "Unlink",
|
|
1197
|
+
"customers.people.detail.companies.unlinkConfirm": "Unlink {{company}} from {{person}}?",
|
|
1198
|
+
"customers.people.detail.companies.unlinkConfirmTitle": "Unlink company",
|
|
1199
|
+
"customers.people.detail.companies.unlinkError": "Failed to unlink company.",
|
|
1200
|
+
"customers.people.detail.companies.unlinkSuccess": "Company unlinked.",
|
|
1192
1201
|
"customers.people.detail.company.current": "{{company}}",
|
|
1193
1202
|
"customers.people.detail.company.empty": "No company assigned",
|
|
1194
1203
|
"customers.people.detail.company.label": "Company",
|
|
@@ -1530,6 +1539,7 @@
|
|
|
1530
1539
|
"customers.people.form.groups.custom": "Custom fields",
|
|
1531
1540
|
"customers.people.form.groups.customAttributes": "Custom attributes",
|
|
1532
1541
|
"customers.people.form.groups.details": "Details",
|
|
1542
|
+
"customers.people.form.groups.displayName": "Display name",
|
|
1533
1543
|
"customers.people.form.groups.notes": "Notes",
|
|
1534
1544
|
"customers.people.form.groups.personalData": "Personal data",
|
|
1535
1545
|
"customers.people.form.groups.roles": "My roles",
|
|
@@ -195,6 +195,10 @@
|
|
|
195
195
|
"customers.companies.dashboard.seeAllActivity": "Ver todas las {{count}} actividades",
|
|
196
196
|
"customers.companies.dashboard.showAll": "Show all",
|
|
197
197
|
"customers.companies.dashboard.upcomingMeetings": "Próximas reuniones",
|
|
198
|
+
"customers.companies.delete.blocked": "No se puede eliminar la empresa: {{blockers}}. Por favor, desvincula o reasigna primero.",
|
|
199
|
+
"customers.companies.delete.blockers.deals": "oportunidades vinculadas ({{count}})",
|
|
200
|
+
"customers.companies.delete.blockers.directPeople": "personas cuya empresa principal es esta ({{count}})",
|
|
201
|
+
"customers.companies.delete.blockers.persons": "personas vinculadas ({{count}})",
|
|
198
202
|
"customers.companies.detail.actions.addDeal": "Agregar oportunidad",
|
|
199
203
|
"customers.companies.detail.actions.backToList": "Volver a empresas",
|
|
200
204
|
"customers.companies.detail.actions.cancel": "Cancelar",
|
|
@@ -1189,6 +1193,11 @@
|
|
|
1189
1193
|
"customers.people.detail.companies.sortRecent": "Sort: Recently active",
|
|
1190
1194
|
"customers.people.detail.companies.subtitle": "Link one or more companies and choose the primary relationship for this person.",
|
|
1191
1195
|
"customers.people.detail.companies.summary": "{{count}} empresas vinculadas",
|
|
1196
|
+
"customers.people.detail.companies.unlinkAction": "Desvincular",
|
|
1197
|
+
"customers.people.detail.companies.unlinkConfirm": "¿Desvincular {{company}} de {{person}}?",
|
|
1198
|
+
"customers.people.detail.companies.unlinkConfirmTitle": "Desvincular empresa",
|
|
1199
|
+
"customers.people.detail.companies.unlinkError": "No se pudo desvincular la empresa.",
|
|
1200
|
+
"customers.people.detail.companies.unlinkSuccess": "Empresa desvinculada.",
|
|
1192
1201
|
"customers.people.detail.company.current": "{{company}}",
|
|
1193
1202
|
"customers.people.detail.company.empty": "Ninguna empresa asignada",
|
|
1194
1203
|
"customers.people.detail.company.label": "Empresa",
|
|
@@ -1530,6 +1539,7 @@
|
|
|
1530
1539
|
"customers.people.form.groups.custom": "Campos personalizados",
|
|
1531
1540
|
"customers.people.form.groups.customAttributes": "Custom attributes",
|
|
1532
1541
|
"customers.people.form.groups.details": "Detalles",
|
|
1542
|
+
"customers.people.form.groups.displayName": "Nombre para mostrar",
|
|
1533
1543
|
"customers.people.form.groups.notes": "Notas",
|
|
1534
1544
|
"customers.people.form.groups.personalData": "Personal data",
|
|
1535
1545
|
"customers.people.form.groups.roles": "My roles",
|
|
@@ -195,6 +195,10 @@
|
|
|
195
195
|
"customers.companies.dashboard.seeAllActivity": "Zobacz wszystkie {{count}} aktywności",
|
|
196
196
|
"customers.companies.dashboard.showAll": "Show all",
|
|
197
197
|
"customers.companies.dashboard.upcomingMeetings": "Nadchodzące spotkania",
|
|
198
|
+
"customers.companies.delete.blocked": "Nie można usunąć firmy: {{blockers}}. Najpierw odłącz lub przepisz powiązania.",
|
|
199
|
+
"customers.companies.delete.blockers.deals": "powiązane szanse ({{count}})",
|
|
200
|
+
"customers.companies.delete.blockers.directPeople": "osoby, dla których ta firma jest podstawowa ({{count}})",
|
|
201
|
+
"customers.companies.delete.blockers.persons": "powiązane osoby ({{count}})",
|
|
198
202
|
"customers.companies.detail.actions.addDeal": "Dodaj szansę",
|
|
199
203
|
"customers.companies.detail.actions.backToList": "Powrót do listy firm",
|
|
200
204
|
"customers.companies.detail.actions.cancel": "Anuluj",
|
|
@@ -1189,6 +1193,11 @@
|
|
|
1189
1193
|
"customers.people.detail.companies.sortRecent": "Sort: Recently active",
|
|
1190
1194
|
"customers.people.detail.companies.subtitle": "Link one or more companies and choose the primary relationship for this person.",
|
|
1191
1195
|
"customers.people.detail.companies.summary": "{{count}} powiązanych firm",
|
|
1196
|
+
"customers.people.detail.companies.unlinkAction": "Odłącz",
|
|
1197
|
+
"customers.people.detail.companies.unlinkConfirm": "Odłączyć firmę {{company}} od {{person}}?",
|
|
1198
|
+
"customers.people.detail.companies.unlinkConfirmTitle": "Odłącz firmę",
|
|
1199
|
+
"customers.people.detail.companies.unlinkError": "Nie udało się odłączyć firmy.",
|
|
1200
|
+
"customers.people.detail.companies.unlinkSuccess": "Firma odłączona.",
|
|
1192
1201
|
"customers.people.detail.company.current": "{{company}}",
|
|
1193
1202
|
"customers.people.detail.company.empty": "Brak przypisanej firmy",
|
|
1194
1203
|
"customers.people.detail.company.label": "Firma",
|
|
@@ -1530,6 +1539,7 @@
|
|
|
1530
1539
|
"customers.people.form.groups.custom": "Pola niestandardowe",
|
|
1531
1540
|
"customers.people.form.groups.customAttributes": "Atrybuty niestandardowe",
|
|
1532
1541
|
"customers.people.form.groups.details": "Szczegóły",
|
|
1542
|
+
"customers.people.form.groups.displayName": "Nazwa wyświetlana",
|
|
1533
1543
|
"customers.people.form.groups.notes": "Notatki",
|
|
1534
1544
|
"customers.people.form.groups.personalData": "Dane osobowe",
|
|
1535
1545
|
"customers.people.form.groups.roles": "Moje role",
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
export function deriveDisplayName(
|
|
2
|
+
firstName: string | null | undefined,
|
|
3
|
+
lastName: string | null | undefined,
|
|
4
|
+
): string {
|
|
5
|
+
const first = (firstName ?? '').trim()
|
|
6
|
+
const last = (lastName ?? '').trim()
|
|
7
|
+
return [first, last].filter((part) => part.length > 0).join(' ').trim()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isDerivedDisplayName(
|
|
11
|
+
current: string | null | undefined,
|
|
12
|
+
firstName: string | null | undefined,
|
|
13
|
+
lastName: string | null | undefined,
|
|
14
|
+
): boolean {
|
|
15
|
+
const trimmed = (current ?? '').trim()
|
|
16
|
+
if (trimmed.length === 0) return true
|
|
17
|
+
return trimmed === deriveDisplayName(firstName, lastName)
|
|
18
|
+
}
|
|
19
|
+
|
|
1
20
|
export function deriveDisplayNameFromEmail(email: string | null | undefined): string | null {
|
|
2
21
|
if (typeof email !== 'string') return null
|
|
3
22
|
const trimmed = email.trim()
|
|
@@ -6,7 +6,10 @@ import {
|
|
|
6
6
|
CustomerPersonCompanyLink,
|
|
7
7
|
CustomerPersonProfile,
|
|
8
8
|
} from '../data/entities'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
filterActivePersonCompanyLinks,
|
|
11
|
+
withActiveCustomerPersonCompanyLinkFilter,
|
|
12
|
+
} from './personCompanyLinkTable'
|
|
10
13
|
|
|
11
14
|
export type PersonCompanySummary = {
|
|
12
15
|
linkId: string | null
|
|
@@ -62,12 +65,14 @@ export async function loadPersonCompanyLinks(
|
|
|
62
65
|
{ person, organizationId: person.organizationId, tenantId: person.tenantId },
|
|
63
66
|
'customers.personCompanies.loadPersonCompanyLinks',
|
|
64
67
|
)
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
return filterActivePersonCompanyLinks(
|
|
69
|
+
await findWithDecryption(
|
|
70
|
+
em,
|
|
71
|
+
CustomerPersonCompanyLink,
|
|
72
|
+
where,
|
|
73
|
+
{ populate: ['company'], orderBy: { isPrimary: 'desc', createdAt: 'asc' } },
|
|
74
|
+
{ tenantId: person.tenantId, organizationId: person.organizationId },
|
|
75
|
+
),
|
|
71
76
|
)
|
|
72
77
|
}
|
|
73
78
|
|
|
@@ -51,3 +51,17 @@ export async function withActiveCustomerPersonCompanyLinkFilter<T extends Record
|
|
|
51
51
|
}
|
|
52
52
|
return { ...where, deletedAt: null }
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Drop soft-deleted link rows from a result set as a defense-in-depth fallback.
|
|
57
|
+
* MikroORM has historically dropped `deletedAt: null` from the WHERE clause for
|
|
58
|
+
* nullable date columns under certain configurations, so callers SHOULD apply this
|
|
59
|
+
* after `findWithDecryption(...)` until the upstream query filter is verified to
|
|
60
|
+
* fully cover all callers.
|
|
61
|
+
*/
|
|
62
|
+
export function filterActivePersonCompanyLinks<T extends { deletedAt?: Date | string | null | undefined }>(
|
|
63
|
+
links: T[] | null | undefined,
|
|
64
|
+
): T[] {
|
|
65
|
+
if (!Array.isArray(links)) return []
|
|
66
|
+
return links.filter((entry) => entry?.deletedAt == null)
|
|
67
|
+
}
|
|
@@ -16,7 +16,7 @@ import { WorkflowInstance } from '../data/entities'
|
|
|
16
16
|
import { createModuleQueue, Queue } from '@open-mercato/queue'
|
|
17
17
|
import { getRedisUrl } from '@open-mercato/shared/lib/redis/connection'
|
|
18
18
|
import {
|
|
19
|
-
|
|
19
|
+
safeOutboundFetch,
|
|
20
20
|
UnsafeOutboundUrlError,
|
|
21
21
|
type HostLookup,
|
|
22
22
|
} from '@open-mercato/shared/lib/url-safety'
|
|
@@ -646,12 +646,27 @@ export async function executeCallWebhook(
|
|
|
646
646
|
|
|
647
647
|
const allowPrivate = deps.allowPrivate ?? isAllowPrivateWorkflowWebhookUrlsEnabled()
|
|
648
648
|
|
|
649
|
+
let response: Response
|
|
649
650
|
try {
|
|
650
|
-
await
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
651
|
+
response = await safeOutboundFetch(
|
|
652
|
+
url,
|
|
653
|
+
{
|
|
654
|
+
method,
|
|
655
|
+
headers: {
|
|
656
|
+
'Content-Type': 'application/json',
|
|
657
|
+
...headers,
|
|
658
|
+
},
|
|
659
|
+
body: body !== undefined && body !== null ? JSON.stringify(body) : undefined,
|
|
660
|
+
redirect: 'manual',
|
|
661
|
+
signal: deps.signal,
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
subject: 'Workflow webhook URL',
|
|
665
|
+
allowPrivate,
|
|
666
|
+
lookupHost: deps.lookupHost,
|
|
667
|
+
fetchImpl: deps.fetchImpl,
|
|
668
|
+
},
|
|
669
|
+
)
|
|
655
670
|
} catch (error) {
|
|
656
671
|
if (error instanceof UnsafeOutboundUrlError) {
|
|
657
672
|
throw new Error(
|
|
@@ -661,18 +676,6 @@ export async function executeCallWebhook(
|
|
|
661
676
|
throw error
|
|
662
677
|
}
|
|
663
678
|
|
|
664
|
-
const fetchImpl = deps.fetchImpl ?? fetch
|
|
665
|
-
const response = await fetchImpl(url, {
|
|
666
|
-
method,
|
|
667
|
-
headers: {
|
|
668
|
-
'Content-Type': 'application/json',
|
|
669
|
-
...headers,
|
|
670
|
-
},
|
|
671
|
-
body: body !== undefined && body !== null ? JSON.stringify(body) : undefined,
|
|
672
|
-
redirect: 'manual',
|
|
673
|
-
signal: deps.signal,
|
|
674
|
-
})
|
|
675
|
-
|
|
676
679
|
if (response.status >= 300 && response.status < 400) {
|
|
677
680
|
const location = response.headers.get('location')
|
|
678
681
|
throw new Error(
|