44be190631
Barra di avanzamento a tutta larghezza (via il max-w-[1200px]). Card offerta: rimosso l'accordion "Cosa è compreso". La lista servizi non arriva più al client — client-view.ts legge dai servizi i soli prezzi — e OffersSection perde il suo unico useState, quindi anche il "use client". "Valore incluso" diventa "Valore dell'offerta" e accetta un override manuale (migration 0020, project_offers.offer_value_override). Prima era sempre la somma dei prezzi di catalogo del tier: in produzione mostrava €20.250 su offerte vendute a 7.000 e 5.500. NULL torna al calcolo, 0 nasconde la riga. Si gestisce dal tab Offerte del progetto, che affianca la cifra calcolata per far vedere cosa si sta sostituendo. La somma calcolata vive ora in src/lib/offer-value.ts, letta sia dal portale sia dall'admin: duplicarla avrebbe fatto divergere le due viste. "Prezzo finale" diventa "Investimento finale" (il ricorrente resta "Canone mensile"). Migration 0020 applicata a prod prima del push. Override tutti NULL, quindi il comportamento resta invariato finché non se ne imposta uno. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
79 lines
2.8 KiB
TypeScript
79 lines
2.8 KiB
TypeScript
import { inArray, eq } from "drizzle-orm";
|
|
import { db } from "@/db";
|
|
import {
|
|
offer_tier_services,
|
|
services,
|
|
offer_micro_services,
|
|
offer_services,
|
|
} from "@/db/schema";
|
|
|
|
/**
|
|
* "Valore dell'offerta" calcolato: la somma dei prezzi dei servizi collegati a
|
|
* un tier (micro).
|
|
*
|
|
* Due sorgenti, nell'ordine: `offer_tier_services` → `services` (catalogo
|
|
* Phase 12) e, per i tier che non hanno righe lì, il legacy
|
|
* `offer_micro_services` → `offer_services`.
|
|
*
|
|
* Vive in un modulo neutro perché lo leggono **due** lati: il portale cliente
|
|
* (`client-view.ts`, dove è il fallback quando l'admin non ha messo un
|
|
* override) e l'area admin (`admin-queries.ts`, dove serve a mostrare quale
|
|
* cifra si sta sostituendo). Duplicarlo significherebbe farli divergere.
|
|
*
|
|
* Legge solo prezzi: nomi e descrizioni dei servizi non servono a nessuno dei
|
|
* due chiamanti, e client-view non deve proiettare più di quanto renderizza.
|
|
*
|
|
* @returns Map micro_id → totale con 2 decimali. I micro senza servizi non
|
|
* compaiono nella mappa (il chiamante decide se è "0" o "assente").
|
|
*/
|
|
export async function getComputedOfferValues(
|
|
microIds: string[]
|
|
): Promise<Map<string, string>> {
|
|
const out = new Map<string, string>();
|
|
if (microIds.length === 0) return out;
|
|
|
|
const sum = (prices: Array<string | null>) =>
|
|
prices.reduce((acc, p) => acc + (parseFloat(p ?? "0") || 0), 0).toFixed(2);
|
|
|
|
// Sorgente primaria: catalogo unificato.
|
|
const tierRows = await db
|
|
.select({
|
|
tier_id: offer_tier_services.tier_id,
|
|
unit_price: services.unit_price,
|
|
})
|
|
.from(offer_tier_services)
|
|
.innerJoin(services, eq(offer_tier_services.service_id, services.id))
|
|
.where(inArray(offer_tier_services.tier_id, microIds));
|
|
|
|
const byTier = new Map<string, Array<string | null>>();
|
|
for (const row of tierRows) {
|
|
const existing = byTier.get(row.tier_id) ?? [];
|
|
existing.push(row.unit_price);
|
|
byTier.set(row.tier_id, existing);
|
|
}
|
|
for (const [tierId, prices] of byTier) out.set(tierId, sum(prices));
|
|
|
|
// Fallback legacy solo per i micro rimasti scoperti.
|
|
const needLegacy = microIds.filter((id) => !out.has(id));
|
|
if (needLegacy.length === 0) return out;
|
|
|
|
const legacyRows = await db
|
|
.select({
|
|
micro_id: offer_micro_services.micro_id,
|
|
price: offer_services.price,
|
|
})
|
|
.from(offer_micro_services)
|
|
.innerJoin(offer_services, eq(offer_micro_services.service_id, offer_services.id))
|
|
.where(inArray(offer_micro_services.micro_id, needLegacy));
|
|
|
|
const byMicro = new Map<string, Array<string | null>>();
|
|
for (const row of legacyRows) {
|
|
const existing = byMicro.get(row.micro_id) ?? [];
|
|
existing.push(row.price);
|
|
byMicro.set(row.micro_id, existing);
|
|
}
|
|
for (const [microId, prices] of byMicro) out.set(microId, sum(prices));
|
|
|
|
return out;
|
|
}
|