Allow multiple referral codes per sales agent

Drop the UNIQUE constraint on sales_agents.email (migration 084) so a single
agent (person/company) can hold several referral codes, each with its own
client discount and commission split. All commission lookups already key on
the unique agent_code, so no lookup logic changes.

Agent-creation endpoint now:
- accepts repeat emails (creates an additional code instead of 409)
- accepts client_discount_value, commission_type, commission_pct per code
- reports existing codes for the email in the response

Both Jay Kordic codes (REF-JKORDIC 7%/12%, REF-JAYK05 5%/15%) now share his
real email jay_kordic@thehorizongroup.biz.
This commit is contained in:
justin 2026-06-02 14:44:22 -05:00
parent 53857574d3
commit 345979ed00
2 changed files with 57 additions and 15 deletions

View file

@ -0,0 +1,23 @@
-- 084_multi_code_per_agent.sql
-- Allow a single sales agent (person/company) to have MULTIPLE referral codes,
-- each with its own client discount and agent commission split.
--
-- Background: each sales_agents row already represents one referral code
-- (it owns a unique agent_code and links to one discount_code). The only thing
-- that blocked the same person from having several codes was the UNIQUE
-- constraint on sales_agents.email. We drop that constraint (keeping a plain
-- index for lookups) so the same email can back several codes/splits.
--
-- All commission/referral lookups key on the unique agent_code, so no other
-- logic changes are required at the DB level.
BEGIN;
-- Drop the UNIQUE constraint on email (name is auto-generated by Postgres).
ALTER TABLE sales_agents DROP CONSTRAINT IF EXISTS sales_agents_email_key;
-- Keep a non-unique index so email lookups stay fast.
-- (idx_agents_email already exists from migration 011; create defensively.)
CREATE INDEX IF NOT EXISTS idx_agents_email ON sales_agents (email);
COMMIT;

View file

@ -76,18 +76,30 @@ export async function createCommission(params: {
// =====================================================================
router.post("/api/v1/admin/agents", requireAdmin, async (req, res) => {
try {
const { name, email, phone, company, commission_default_cents, commission_pct, commission_overrides, notes } = req.body ?? {};
const {
name, email, phone, company,
commission_type, commission_default_cents, commission_pct, commission_overrides,
client_discount_value, notes,
} = req.body ?? {};
if (!name || !email) {
res.status(400).json({ error: "Name and email are required." });
return;
}
// Check for duplicate email
const existing = await pool.query("SELECT id FROM sales_agents WHERE email = $1", [email.toLowerCase().trim()]);
if (existing.rows.length > 0) {
res.status(409).json({ error: "An agent with this email already exists." });
return;
}
// A single agent (person/company) may hold MULTIPLE referral codes, each with
// its own client discount + commission split. We therefore allow repeat emails;
// each call creates a new code. We surface any existing codes for the same email
// in the response so the caller knows it's an additional code, not a mistake.
const existing = await pool.query(
"SELECT agent_code, commission_pct FROM sales_agents WHERE email = $1 ORDER BY created_at",
[email.toLowerCase().trim()],
);
const existingCodes = existing.rows.map((r) => r.agent_code);
// Per-code config (defaults preserve prior behaviour: 5% client discount, 10% commission)
const clientDiscount = client_discount_value !== undefined ? client_discount_value : 5;
const commType = commission_type === "flat" ? "flat" : "percent";
const commPct = commission_pct !== undefined ? commission_pct : 10;
// Generate unique agent code
let agentCode = generateAgentCode();
@ -99,22 +111,23 @@ router.post("/api/v1/admin/agents", requireAdmin, async (req, res) => {
attempts++;
}
// Create the linked discount code (5% off service fees)
// Create the linked discount code (client discount off service fees;
// referral_pct mirrors the agent commission for reporting).
const dcResult = await pool.query(
`INSERT INTO discount_codes (code, description, discount_type, discount_value, referral_partner, referral_email, referral_pct, active)
VALUES ($1, $2, 'percent', 5, $3, $4, 0, TRUE)
VALUES ($1, $2, 'percent', $3, $4, $5, $6, TRUE)
RETURNING id`,
[agentCode, `Sales agent: ${name}`, name, email.toLowerCase().trim()],
[agentCode, `Sales agent: ${name}`, clientDiscount, name, email.toLowerCase().trim(), commType === "percent" ? commPct : 0],
);
const discountCodeId = dcResult.rows[0].id;
// Create the agent
// Create the agent (one row per referral code)
const result = await pool.query(
`INSERT INTO sales_agents (agent_code, discount_code_id, name, email, phone, company, commission_default_cents, commission_pct, commission_overrides, notes, onboarded_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, now())
`INSERT INTO sales_agents (agent_code, discount_code_id, name, email, phone, company, commission_type, commission_default_cents, commission_pct, commission_overrides, notes, onboarded_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, now())
RETURNING id, agent_code`,
[agentCode, discountCodeId, name, email.toLowerCase().trim(), phone || null, company || null,
commission_default_cents || 30000, commission_pct || 10,
commType, commission_default_cents || 30000, commPct,
JSON.stringify(commission_overrides || {}), notes || null],
);
@ -123,8 +136,14 @@ router.post("/api/v1/admin/agents", requireAdmin, async (req, res) => {
success: true,
agent_id: agent.id,
agent_code: agent.agent_code,
client_discount_value: clientDiscount,
commission_type: commType,
commission_pct: commType === "percent" ? commPct : undefined,
existing_codes_for_email: existingCodes,
referral_url: `https://performancewest.net/order/canada-crtc?code=${agent.agent_code}`,
message: `Agent created. Referral code: ${agent.agent_code}. Client gets 5% off, agent earns commission per sale.`,
message: existingCodes.length > 0
? `Additional referral code created for ${email}. New code: ${agent.agent_code} (${clientDiscount}% client discount${commType === "percent" ? `, ${commPct}% commission` : ""}). This agent now has ${existingCodes.length + 1} codes.`
: `Agent created. Referral code: ${agent.agent_code}. Client gets ${clientDiscount}% off, agent earns commission per sale.`,
});
} catch (err) {
console.error("[agents] Create error:", err);