import { Router } from "express"; import { pool } from "../db.js"; import { submitLimiter } from "../middleware/rate-limit.js"; import { v4 as uuidv4 } from "uuid"; import { createResource, createServiceOrder } from "../erpnext-client.js"; const router = Router(); // POST /api/v1/quotes — Request a custom quote for a service router.post("/api/v1/quotes", submitLimiter, async (req, res) => { try { const { name, email, company, phone, service_slug, details } = req.body ?? {}; if (!name || typeof name !== "string" || name.trim().length < 2) { res.status(400).json({ error: "Name is required (at least 2 characters)." }); return; } if (!email || typeof email !== "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { res.status(400).json({ error: "Valid email address is required." }); return; } if (!service_slug || typeof service_slug !== "string") { res.status(400).json({ error: "Service selection is required." }); return; } const result = await pool.query( `INSERT INTO quotes (name, email, company, phone, service_slug, details, status) VALUES ($1, $2, $3, $4, $5, $6, 'pending') RETURNING id`, [ name.trim(), email.toLowerCase().trim(), company || null, phone || null, service_slug.trim(), details || null, ], ); const quoteId = result.rows[0]?.id; // Push to ERPNext as an Opportunity — non-blocking, don't fail the response try { await createResource("Opportunity", { opportunity_from: "Lead", party_name: email.toLowerCase().trim(), contact_email: email.toLowerCase().trim(), opportunity_type: "Sales", custom_service_slug: service_slug.trim(), notes: [ `Name: ${name.trim()}`, company ? `Company: ${company}` : null, phone ? `Phone: ${phone}` : null, details ? `Details: ${details}` : null, `Source: performancewest.net quote form`, ] .filter(Boolean) .join("\n"), }); } catch (erpErr) { console.error("[quotes] ERPNext Opportunity creation failed (non-fatal):", erpErr); } res.status(201).json({ success: true, message: "Quote request received. We'll send your quote within one business day.", quote_id: quoteId ? String(quoteId) : undefined, }); } catch (err) { console.error("[quotes] Error:", err); res.status(500).json({ error: "Could not submit your request. Please try again." }); } }); // POST /api/v1/orders — Place a fixed-price service order router.post("/api/v1/orders", submitLimiter, async (req, res) => { try { const { name, email, company, service_slug, amount_cents, quote_id } = req.body ?? {}; if (!name || typeof name !== "string" || name.trim().length < 2) { res.status(400).json({ error: "Name is required." }); return; } if (!email || typeof email !== "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { res.status(400).json({ error: "Valid email address is required." }); return; } if (!service_slug || typeof service_slug !== "string") { res.status(400).json({ error: "Service selection is required." }); return; } // Generate order number: PW-YYYY-XXXX const year = new Date().getFullYear(); const short = uuidv4().split("-")[0]!.toUpperCase(); const orderNumber = `PW-${year}-${short}`; const result = await pool.query( `INSERT INTO orders (order_number, quote_id, service_slug, name, email, company, status, amount_cents) VALUES ($1, $2, $3, $4, $5, $6, 'received', $7) RETURNING id, order_number`, [ orderNumber, quote_id || null, service_slug.trim(), name.trim(), email.toLowerCase().trim(), company || null, amount_cents || null, ], ); const row = result.rows[0]; // Push to ERPNext as a Sales Order — non-blocking, don't fail the response try { await createServiceOrder({ customer: name.trim(), service_slug: service_slug.trim(), notes: [ `Order: ${orderNumber}`, company ? `Company: ${company}` : null, quote_id ? `Quote ref: ${quote_id}` : null, `Source: performancewest.net`, ] .filter(Boolean) .join("\n"), }); } catch (erpErr) { console.error("[orders] ERPNext Sales Order creation failed (non-fatal):", erpErr); } res.status(201).json({ success: true, message: "Order received. We'll begin processing within one business day.", order_number: row?.order_number, }); } catch (err) { console.error("[orders] Error:", err); res.status(500).json({ error: "Could not place your order. Please try again." }); } }); export default router;