From 345979ed0013fa9f2e897e99e7a0269877207ce8 Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 2 Jun 2026 14:44:22 -0500 Subject: [PATCH] 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. --- api/migrations/084_multi_code_per_agent.sql | 23 ++++++++++ api/src/routes/agents.ts | 49 ++++++++++++++------- 2 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 api/migrations/084_multi_code_per_agent.sql diff --git a/api/migrations/084_multi_code_per_agent.sql b/api/migrations/084_multi_code_per_agent.sql new file mode 100644 index 0000000..f367fd0 --- /dev/null +++ b/api/migrations/084_multi_code_per_agent.sql @@ -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; diff --git a/api/src/routes/agents.ts b/api/src/routes/agents.ts index d18641b..750953c 100644 --- a/api/src/routes/agents.ts +++ b/api/src/routes/agents.ts @@ -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);