feat: payments plans, phase auto-cascade, transcripts, manual timer
- Pagamenti: totale ereditato dalla somma accepted_total delle offerte attive (con override) + selettore schema rate 1/2/3 step; nuova colonna payments.percent per rescalare gli importi preservando label/stato - Fasi & Task: recomputePhaseStatus auto-cascade (task -> fase) su updateTaskStatus/addTask, fix UI stale, etichette Da iniziare/In corso/Completata - Pagina pubblica: colori fasi (grigio/blu/#1A463C) + fasi collassabili - Transcript del cliente visibili in tab Documenti admin e dashboard pubblica - Timer: inserimento manuale (+30/+60, minuti liberi, data) + elimina entry - CLAUDE.md: procedura Deploy & DB Access; push su main automatico Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,7 @@ export async function updateProjectAcceptedTotal(projectId: string, acceptedTota
|
||||
}
|
||||
|
||||
// Creates Acconto 50% + Saldo 50% stubs for a project that has no payments yet.
|
||||
// Kept for backward compatibility; prefer setPaymentPlan for new code.
|
||||
export async function initProjectPayments(projectId: string): Promise<void> {
|
||||
await requireAdmin();
|
||||
const rows = await db
|
||||
@@ -74,11 +75,50 @@ export async function initProjectPayments(projectId: string): Promise<void> {
|
||||
.limit(1);
|
||||
if (!rows[0]) throw new Error("Progetto non trovato");
|
||||
const total = parseFloat(rows[0].accepted_total ?? "0");
|
||||
const half = (total / 2).toFixed(2);
|
||||
await db.insert(payments).values([
|
||||
{ project_id: projectId, label: "Acconto 50%", amount: half, status: "da_saldare" },
|
||||
{ project_id: projectId, label: "Saldo 50%", amount: half, status: "da_saldare" },
|
||||
]);
|
||||
await setPaymentPlan(projectId, "two", total);
|
||||
}
|
||||
|
||||
// Replaces all payments for a project with a fresh plan at a given total.
|
||||
// Modes:
|
||||
// single → 1 row 100% "Pagamento unico (100%)"
|
||||
// two → 2 rows 50%/50% "Acconto 50% (inizio lavori)" / "Saldo 50% (alla consegna)"
|
||||
// three → 3 rows 50%/30%/20%
|
||||
export async function setPaymentPlan(
|
||||
projectId: string,
|
||||
mode: "single" | "two" | "three",
|
||||
total: number
|
||||
): Promise<void> {
|
||||
await requireAdmin();
|
||||
|
||||
type PaymentDef = { label: string; percent: number };
|
||||
const plans: Record<string, PaymentDef[]> = {
|
||||
single: [{ label: "Pagamento unico (100%)", percent: 100 }],
|
||||
two: [
|
||||
{ label: "Acconto 50% (inizio lavori)", percent: 50 },
|
||||
{ label: "Saldo 50% (alla consegna)", percent: 50 },
|
||||
],
|
||||
three: [
|
||||
{ label: "Acconto 50% (inizio lavori)", percent: 50 },
|
||||
{ label: "30% (post revisioni)", percent: 30 },
|
||||
{ label: "Saldo 20% (alla consegna)", percent: 20 },
|
||||
],
|
||||
};
|
||||
|
||||
const plan = plans[mode];
|
||||
if (!plan) throw new Error("Modalità pagamento non valida");
|
||||
|
||||
// Delete existing payments for this project, then insert new ones.
|
||||
await db.delete(payments).where(eq(payments.project_id, projectId));
|
||||
await db.insert(payments).values(
|
||||
plan.map((p) => ({
|
||||
project_id: projectId,
|
||||
label: p.label,
|
||||
percent: p.percent.toFixed(2),
|
||||
amount: ((total * p.percent) / 100).toFixed(2),
|
||||
status: "da_saldare" as const,
|
||||
}))
|
||||
);
|
||||
|
||||
revalidatePath(`/admin/projects/${projectId}`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user