Files
tehran_taj/apps/api/prisma/manual/diagnose_expert_login.sql
T
s.esmaeiliandClaude Sonnet 5.5 06c8a5f434 feat(auth): split panel admins out of AdminUser into their own Admin table
AdminUser held both panel staff and sales experts, so one mobile could not be
both. Staff now live in "Admin" (panel only); sales experts stay in "AdminUser"
untouched. Uniqueness is per table.

- migration 20261026120000_split_admin_table: additive only. Creates "Admin",
  copies non-expert rows with their ids, re-points the "who did this" foreign
  keys (ledger, order status, sms, notification, upload, allocation, workshop,
  admin session) to "Admin" without moving data. Never writes to "AdminUser".
- login, JWT, SSO, branch/workshop supervisors and the users grid use "Admin";
  SALES_EXPERT create/edit still goes through admin/users into "AdminUser".
- an admin cannot be turned into an expert or the reverse (different tables).
- manual/: optional phase-2 script to drop the dormant admin copies from
  "AdminUser", and a read-only expert-login diagnostic.
- docs/ADMIN-TABLE-SPLIT.md: what changed, deploy checklist, rollback.

Tested on a copy with 12,000 experts: expert rows byte-identical (md5) before
and after; full migration chain applies and matches schema.prisma.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 23:52:33 +03:30

60 lines
3.2 KiB
SQL

-- READ-ONLY diagnostic: "I sign in as a sales expert and the customers menu /
-- list does not show". Changes nothing.
--
-- psql "$DATABASE_URL" -v mobile=09XXXXXXXXX -f diagnose_expert_login.sql
--
-- How the app decides (apps/api/src/modules/customer-auth/customer-auth.service.ts):
-- 1. request-otp: an ACTIVE "AdminUser" with role SALES_EXPERT and exactly this
-- mobile wins over any Customer with the same number.
-- 2. That expert needs a SalesExpertProfile and a usable trading Customer
-- (active, not archived). Created on first sign-in; if a Customer with the
-- same mobile already exists it is ADOPTED, and when that Customer is
-- inactive/archived the expert is NOT treated as an expert at all.
-- 3. verify-otp then issues a "sales-expert" token only if all of the above
-- hold; GET /api/expert/whoami answers isSalesExpert from that token, and
-- the apps show «مشتریان من» only when it is true.
-- 4. «مشتریان من» lists "Customer"."referredByAgentId" = the expert's id.
\echo '== 1. the expert row (must be exactly one, active, role SALES_EXPERT)'
SELECT id, mobile, role, "isActive", length(mobile) AS mobile_len
FROM "AdminUser" WHERE mobile = :'mobile';
\echo '== 1b. near-misses: same digits with spaces / country code / Persian digits'
SELECT id, mobile, role, "isActive"
FROM "AdminUser"
WHERE regexp_replace(mobile, '\D', '', 'g') IN (:'mobile', '98' || substr(:'mobile', 2))
AND mobile <> :'mobile';
\echo '== 2. profile + trading account (tradingCustomerId must be set and usable)'
SELECT p.id AS profile_id, p."tradingCustomerId",
c."isActive" AS trading_active, c."isArchived" AS trading_archived
FROM "AdminUser" a
LEFT JOIN "SalesExpertProfile" p ON p."adminUserId" = a.id
LEFT JOIN "Customer" c ON c.id = p."tradingCustomerId"
WHERE a.mobile = :'mobile';
\echo '== 3. Customer rows with this mobile (an inactive/archived one blocks expert mode)'
SELECT id, kind, "isActive", "isArchived", "verificationStep"
FROM "Customer" WHERE mobile = :'mobile';
\echo '== 4. customers referred by this expert (the «مشتریان من» list)'
SELECT count(*) AS referred_customers
FROM "Customer" c JOIN "AdminUser" a ON a.id = c."referredByAgentId"
WHERE a.mobile = :'mobile';
\echo '== 5. experts overall: how many cannot work as experts (for the whole table)'
SELECT
count(*) FILTER (WHERE a."isActive") AS active_experts,
count(*) FILTER (WHERE a."isActive" AND p.id IS NULL) AS no_profile,
count(*) FILTER (WHERE a."isActive" AND p.id IS NOT NULL
AND p."tradingCustomerId" IS NULL
AND EXISTS (SELECT 1 FROM "Customer" c
WHERE c.mobile = a.mobile
AND (NOT c."isActive" OR c."isArchived"))) AS blocked_by_inactive_same_mobile_customer,
count(*) FILTER (WHERE a."isActive" AND c.id IS NOT NULL
AND (NOT c."isActive" OR c."isArchived")) AS trading_account_disabled
FROM "AdminUser" a
LEFT JOIN "SalesExpertProfile" p ON p."adminUserId" = a.id
LEFT JOIN "Customer" c ON c.id = p."tradingCustomerId"
WHERE a.role = 'SALES_EXPERT';