feat(inquiries): website_submissions tracking + display columns; capture populates contact name/email
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
|||||||
autoPromoteWebsiteBerthInquiry,
|
autoPromoteWebsiteBerthInquiry,
|
||||||
isWebsiteBerthAutopromoteEnabled,
|
isWebsiteBerthAutopromoteEnabled,
|
||||||
} from '@/lib/services/website-intake-promote.service';
|
} from '@/lib/services/website-intake-promote.service';
|
||||||
|
import { extractInquiryFields } from '@/lib/services/website-intake-fields';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/public/website-inquiries
|
* POST /api/public/website-inquiries
|
||||||
@@ -149,6 +150,10 @@ export async function POST(req: NextRequest) {
|
|||||||
// hits, `returning()` yields zero rows and we look up the existing row to
|
// hits, `returning()` yields zero rows and we look up the existing row to
|
||||||
// return its id, mirroring the first-delivery shape so the website never
|
// return its id, mirroring the first-delivery shape so the website never
|
||||||
// sees a difference between fresh and dup.
|
// sees a difference between fresh and dup.
|
||||||
|
// Extract contact name/email into real columns so the inquiry list can
|
||||||
|
// search/sort/display without digging into the JSONB payload per row.
|
||||||
|
const fields = extractInquiryFields(parsed.payload);
|
||||||
|
|
||||||
const insertResult = await db
|
const insertResult = await db
|
||||||
.insert(websiteSubmissions)
|
.insert(websiteSubmissions)
|
||||||
.values({
|
.values({
|
||||||
@@ -157,6 +162,8 @@ export async function POST(req: NextRequest) {
|
|||||||
kind: parsed.kind,
|
kind: parsed.kind,
|
||||||
payload: parsed.payload,
|
payload: parsed.payload,
|
||||||
legacyNocodbId: parsed.legacy_nocodb_id ?? null,
|
legacyNocodbId: parsed.legacy_nocodb_id ?? null,
|
||||||
|
contactName: fields.fullName || null,
|
||||||
|
contactEmail: fields.email || null,
|
||||||
sourceIp: ip,
|
sourceIp: ip,
|
||||||
userAgent: req.headers.get('user-agent') ?? null,
|
userAgent: req.headers.get('user-agent') ?? null,
|
||||||
utmSource: parsed.utm_source ?? null,
|
utmSource: parsed.utm_source ?? null,
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
-- 0093_website_submissions_inquiry_cols.sql
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
-- Inquiries workbench: tracking + display columns on website_submissions.
|
||||||
|
-- converted_client_id / converted_interest_id - set when an operator converts
|
||||||
|
-- an inquiry into CRM entities (FK to clients/interests).
|
||||||
|
-- contact_name / contact_email - extracted from the JSONB payload at capture
|
||||||
|
-- time so the list view can search/sort/display via real columns.
|
||||||
|
--
|
||||||
|
-- Idempotent: ADD COLUMN IF NOT EXISTS + CREATE INDEX IF NOT EXISTS + a
|
||||||
|
-- COALESCE backfill that only fills nulls. Safe to re-run.
|
||||||
|
|
||||||
|
ALTER TABLE website_submissions
|
||||||
|
ADD COLUMN IF NOT EXISTS converted_client_id text REFERENCES clients(id),
|
||||||
|
ADD COLUMN IF NOT EXISTS converted_interest_id text REFERENCES interests(id),
|
||||||
|
ADD COLUMN IF NOT EXISTS contact_name text,
|
||||||
|
ADD COLUMN IF NOT EXISTS contact_email text;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_ws_contact_email
|
||||||
|
ON website_submissions (port_id, contact_email);
|
||||||
|
|
||||||
|
-- Backfill display columns from existing payloads (only where still null).
|
||||||
|
UPDATE website_submissions
|
||||||
|
SET contact_email = COALESCE(contact_email, NULLIF(payload->>'email', '')),
|
||||||
|
contact_name = COALESCE(
|
||||||
|
contact_name,
|
||||||
|
NULLIF(TRIM(CONCAT_WS(' ', payload->>'first_name', payload->>'last_name')), ''),
|
||||||
|
NULLIF(payload->>'name', ''),
|
||||||
|
NULLIF(payload->>'fullName', ''),
|
||||||
|
NULLIF(payload->>'full_name', '')
|
||||||
|
)
|
||||||
|
WHERE contact_email IS NULL OR contact_name IS NULL;
|
||||||
@@ -51,6 +51,18 @@ export const websiteSubmissions = pgTable(
|
|||||||
* same form submission. Useful for reconciling: pick any submission
|
* same form submission. Useful for reconciling: pick any submission
|
||||||
* here, look up the matching NocoDB row, confirm both halves agree. */
|
* here, look up the matching NocoDB row, confirm both halves agree. */
|
||||||
legacyNocodbId: text('legacy_nocodb_id'),
|
legacyNocodbId: text('legacy_nocodb_id'),
|
||||||
|
/** Contact name + email extracted from `payload` at capture time so the
|
||||||
|
* inquiry list can search/sort/display via real columns (payload stays
|
||||||
|
* JSONB and isn't searched directly). Populated by the capture endpoint
|
||||||
|
* and backfilled in migration 0093. */
|
||||||
|
contactName: text('contact_name'),
|
||||||
|
contactEmail: text('contact_email'),
|
||||||
|
/** Set when an operator converts this inquiry into CRM entities. FK enforced
|
||||||
|
* at the DB level (migration 0093); typed as plain text here to avoid a
|
||||||
|
* circular schema import — `clients`/`interests` already reference
|
||||||
|
* `website_submissions`. */
|
||||||
|
convertedClientId: text('converted_client_id'),
|
||||||
|
convertedInterestId: text('converted_interest_id'),
|
||||||
/** Capture-time metadata for debugging. */
|
/** Capture-time metadata for debugging. */
|
||||||
sourceIp: text('source_ip'),
|
sourceIp: text('source_ip'),
|
||||||
userAgent: text('user_agent'),
|
userAgent: text('user_agent'),
|
||||||
|
|||||||
Reference in New Issue
Block a user