feat(auth): one mobile, one person across sales experts and customers
Experts (AdminUser) and customers (Customer) sign in to the same apps by mobile but live in different tables, so the database cannot make the number unique across them. A number could be given to an expert and to a customer, and the login then silently merged the two (the customer row was adopted as the expert's trading account), which left the expert on a customer session and the panel's name edits unreachable. The reference rejected a duplicate at entry (`unique:customers`, the agent being a customers row too); this does the same where a number is typed in: - customer create/update (wholesale, banker, walk-in): refused when an ACTIVE expert holds the number; - expert create/update: refused when a customer holds it (the expert's own trading account excepted); - the app's self sign-up: refused when an active expert holds it. Only a number being created or CHANGED is checked, so existing duplicates keep signing in and stay editable; the login's "expert wins" adoption is untouched. Plain equality on the two unique columns, normalised with faToEn, so both lookups are index hits. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
5ddb225813
commit
5f3b00497e
@@ -0,0 +1,68 @@
|
||||
import { AdminRole } from '@prisma/client';
|
||||
import type { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
/**
|
||||
* One mobile, one person — across the two tables the apps sign in from.
|
||||
*
|
||||
* A sales expert (AdminUser) and a customer (Customer) both log in to the apps
|
||||
* with their mobile, but they live in different tables, so the database cannot
|
||||
* make the number unique across them. These checks do it where a number is
|
||||
* typed in: the reference rejected a duplicate the same way (`unique:customers`
|
||||
* on create and update, with the agent a customers row too).
|
||||
*
|
||||
* Plain equality on the unique columns, so both lookups are index hits. The
|
||||
* mobile must already be normalised (`faToEn`), as it is when stored.
|
||||
*
|
||||
* Nothing here reads or changes a row that already exists: callers only ask
|
||||
* when a number is being CREATED or CHANGED, so an existing duplicate keeps
|
||||
* logging in and stays editable.
|
||||
*/
|
||||
type Db = Pick<PrismaService, 'adminUser' | 'customer'>;
|
||||
|
||||
export const EXPERT_HOLDS_MOBILE = 'این شماره موبایل قبلاً برای یک کارشناس فروش ثبت شده است';
|
||||
export const CUSTOMER_HOLDS_MOBILE = 'این شماره موبایل قبلاً برای یک مشتری ثبت شده است';
|
||||
|
||||
/**
|
||||
* An ACTIVE sales expert holds this number. Active only, because that is who the
|
||||
* app login treats as an expert (`resolveExpertPrincipal`); a deactivated
|
||||
* expert's number still works as a customer today and must keep doing so.
|
||||
*
|
||||
* `exceptTradingCustomerId` skips the expert whose own trading account that
|
||||
* customer is — the row is theirs, so it may carry their number.
|
||||
*/
|
||||
export async function activeExpertHoldsMobile(
|
||||
db: Db,
|
||||
mobile: string,
|
||||
exceptTradingCustomerId?: string,
|
||||
): Promise<boolean> {
|
||||
const row = await db.adminUser.findFirst({
|
||||
where: {
|
||||
mobile,
|
||||
role: AdminRole.SALES_EXPERT,
|
||||
isActive: true,
|
||||
...(exceptTradingCustomerId
|
||||
? { NOT: { salesExpertProfile: { is: { tradingCustomerId: exceptTradingCustomerId } } } }
|
||||
: {}),
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
return row !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A customer row (any state) holds this number. Any state, because an expert
|
||||
* whose number sits on an inactive or archived customer cannot sign in at all.
|
||||
*
|
||||
* `exceptCustomerId` is the expert's own trading account.
|
||||
*/
|
||||
export async function customerHoldsMobile(
|
||||
db: Db,
|
||||
mobile: string,
|
||||
exceptCustomerId?: string | null,
|
||||
): Promise<boolean> {
|
||||
const row = await db.customer.findFirst({
|
||||
where: { mobile, ...(exceptCustomerId ? { id: { not: exceptCustomerId } } : {}) },
|
||||
select: { id: true },
|
||||
});
|
||||
return row !== null;
|
||||
}
|
||||
Reference in New Issue
Block a user