mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 18:48:11 +00:00
fix: gm and poa email requirement
This commit is contained in:
@@ -2,6 +2,7 @@ import { DataSource } from 'typeorm';
|
||||
|
||||
import { NotificationsService } from './notifications.service';
|
||||
import {
|
||||
companyNotifyEmailExpr,
|
||||
companyNotifyPhoneExpr,
|
||||
primaryContactUserJoin,
|
||||
} from './resolve-company-phone.util';
|
||||
@@ -19,7 +20,8 @@ export async function sendCompanyChannels(
|
||||
): Promise<void> {
|
||||
const [contact]: Array<{ phone: string | null; email: string | null }> =
|
||||
await dataSource.query(
|
||||
`SELECT ${companyNotifyPhoneExpr('co')} AS phone, co.email
|
||||
`SELECT ${companyNotifyPhoneExpr('co')} AS phone,
|
||||
${companyNotifyEmailExpr('co')} AS email
|
||||
FROM freight.companies co
|
||||
${primaryContactUserJoin('co')}
|
||||
WHERE co.id = $1 AND co.deleted_at IS NULL`,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataSource, EntityManager } from "typeorm";
|
||||
|
||||
/**
|
||||
* Where a customer-facing SMS actually goes.
|
||||
* Where a customer-facing SMS or email actually goes.
|
||||
*
|
||||
* The person who signs up, logs in, and receives OTPs is an IAM user, and
|
||||
* `iam.users.phone_number` is the number they control and can change themselves
|
||||
@@ -12,11 +12,13 @@ import { DataSource, EntityManager } from "typeorm";
|
||||
* `companies.contact_person_phone` is deliberately NOT consulted: the live write
|
||||
* path stores that value in the `attributes` jsonb and has never populated the
|
||||
* column, so every reader of it was silently falling through to `phone` anyway.
|
||||
* `companies.general_manager_email` is the same trap on the email side — see
|
||||
* {@link companyNotifyEmailExpr}.
|
||||
*/
|
||||
|
||||
/**
|
||||
* LEFT JOIN a company alias to its primary contact's IAM user, exposing
|
||||
* `pc.phone_number`.
|
||||
* `pc.phone_number` and `pc.email`.
|
||||
*
|
||||
* LATERAL + LIMIT 1 rather than a plain join: nothing in the schema stops a
|
||||
* company having two `is_primary_contact` rows, and a plain join would then
|
||||
@@ -28,7 +30,7 @@ import { DataSource, EntityManager } from "typeorm";
|
||||
export function primaryContactUserJoin(alias: string): string {
|
||||
return `
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT u.phone_number
|
||||
SELECT u.phone_number, u.email
|
||||
FROM freight.external_profiles ep
|
||||
JOIN iam.users u ON u.id = ep.user_id AND u.is_active = true
|
||||
WHERE ep.company_id = ${alias}.id
|
||||
@@ -44,17 +46,45 @@ export function companyNotifyPhoneExpr(alias: string): string {
|
||||
return `COALESCE(pc.phone_number, ${alias}.phone)`;
|
||||
}
|
||||
|
||||
/** The SMS number for one company, or null when neither source has one. */
|
||||
export async function resolveCompanyNotifyPhone(
|
||||
/**
|
||||
* SQL expression for the company's notification address, given the joined `pc`
|
||||
* alias.
|
||||
*
|
||||
* `companies.email` alone is not enough: it is written from ONE place — a
|
||||
* Fayda-verified owner's email claim — so a foreign company, whose owner proves
|
||||
* identity by passport instead, never gets one. Readers papered over that with
|
||||
* `COALESCE(email, general_manager_email)`, but that column has the same problem
|
||||
* `contact_person_phone` has above: onboarding writes the value into the
|
||||
* `attributes` jsonb and nothing has ever populated the column, so the fallback
|
||||
* could not fire and the mail was dropped in silence.
|
||||
*
|
||||
* So: the company address, then the two the customer actually filled in during
|
||||
* onboarding, then the account that registered them — which always has one,
|
||||
* signup requires it. `NULLIF` because a blank jsonb key is not an address and
|
||||
* `COALESCE` would happily stop on it.
|
||||
*/
|
||||
export function companyNotifyEmailExpr(alias: string): string {
|
||||
return `COALESCE(
|
||||
NULLIF(${alias}.email, ''),
|
||||
NULLIF(${alias}.attributes->>'generalManagerEmail', ''),
|
||||
NULLIF(${alias}.attributes->>'contactPersonEmail', ''),
|
||||
NULLIF(pc.email, '')
|
||||
)`;
|
||||
}
|
||||
|
||||
/** Both channels for one company; either side is null when nothing has one. */
|
||||
export async function resolveCompanyNotifyContact(
|
||||
db: DataSource | EntityManager,
|
||||
companyId: string,
|
||||
): Promise<string | null> {
|
||||
const rows: Array<{ phone: string | null }> = await db.query(
|
||||
`SELECT ${companyNotifyPhoneExpr("co")} AS phone
|
||||
FROM freight.companies co
|
||||
${primaryContactUserJoin("co")}
|
||||
WHERE co.id = $1 AND co.deleted_at IS NULL`,
|
||||
[companyId],
|
||||
);
|
||||
return rows[0]?.phone ?? null;
|
||||
): Promise<{ phone: string | null; email: string | null }> {
|
||||
const rows: Array<{ phone: string | null; email: string | null }> =
|
||||
await db.query(
|
||||
`SELECT ${companyNotifyPhoneExpr("co")} AS phone,
|
||||
${companyNotifyEmailExpr("co")} AS email
|
||||
FROM freight.companies co
|
||||
${primaryContactUserJoin("co")}
|
||||
WHERE co.id = $1 AND co.deleted_at IS NULL`,
|
||||
[companyId],
|
||||
);
|
||||
return { phone: rows[0]?.phone ?? null, email: rows[0]?.email ?? null };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user