feat: lead → client conversion + client contact fields (email/phone)
Blocco A+B (milestone v2.3). Migration 0011 is additive (ADD COLUMN only): clients.email, clients.phone, leads.archived. - createClientCore() extracted from createClient and reused by conversion - clients.email/phone added to create + edit forms and shown on client header (never exposed on the public /client/[token] page) - convertLeadToClient(): reuses core, carries over lead transcripts (client_transcripts.client_id), links project.created_from_lead_id, archives the lead while keeping its "won" status; idempotent - "Converti in cliente" button on won leads; "Convertito → Apri cliente" once converted (clientId resolved via created_from_lead_id) - archived leads hidden from list/kanban; detail page fetches by id incl. archived Migration must be applied to the prod DB BEFORE this is deployed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -50,10 +50,17 @@ async function resolveEntity(id: string): Promise<{ projectId: string | null; pa
|
||||
|
||||
// ── CLIENT CRUD ───────────────────────────────────────────────────────────────
|
||||
|
||||
const emptyToNull = (v: unknown) => {
|
||||
const s = typeof v === "string" ? v.trim() : "";
|
||||
return s === "" ? null : s;
|
||||
};
|
||||
|
||||
const clientSchema = z.object({
|
||||
name: z.string().min(1, "Nome richiesto"),
|
||||
brand_name: z.string().min(1, "Brand name richiesto"),
|
||||
brief: z.string(),
|
||||
email: z.string().optional().transform(emptyToNull),
|
||||
phone: z.string().optional().transform(emptyToNull),
|
||||
slug: z
|
||||
.string()
|
||||
.regex(/^[a-z0-9-]{3,50}$/, "Slug non valido (es. mario-rossi, min 3 max 50 caratteri)")
|
||||
@@ -68,6 +75,8 @@ export async function updateClient(clientId: string, formData: FormData) {
|
||||
name: formData.get("name"),
|
||||
brand_name: formData.get("brand_name"),
|
||||
brief: formData.get("brief") ?? "",
|
||||
email: formData.get("email") ?? "",
|
||||
phone: formData.get("phone") ?? "",
|
||||
slug: formData.get("slug") ?? "",
|
||||
});
|
||||
if (!parsed.success) throw new Error(parsed.error.issues[0].message);
|
||||
|
||||
Reference in New Issue
Block a user