Guard euro/altura inputs in precios actions so empty or non-numeric form values return a Spanish error instead of writing NaN and throwing a 500. Remove the now-unused formatEuros import flagged by ESLint. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
106 lines
3.9 KiB
TypeScript
106 lines
3.9 KiB
TypeScript
'use server';
|
|
|
|
import { and, eq } from 'drizzle-orm';
|
|
import { revalidatePath } from 'next/cache';
|
|
import { db } from '@/db';
|
|
import { catalogItems, pricingConfig } from '@/db/schema';
|
|
import { getTenantId } from '@/db/pricing-queries';
|
|
import { parseCatalogCsv } from '@/budget/csv';
|
|
|
|
// Valida un importe en euros del formulario y lo convierte a céntimos.
|
|
// Lanza un error en español si el valor no es un número finito >= 0.
|
|
function eurosToCents(raw: FormDataEntryValue | null, campo: string): number {
|
|
const euros = Number(raw);
|
|
if (!Number.isFinite(euros) || euros < 0) {
|
|
throw new Error(`El valor de "${campo}" debe ser un número mayor o igual que 0.`);
|
|
}
|
|
return Math.round(euros * 100);
|
|
}
|
|
|
|
function parsePositive(raw: FormDataEntryValue | null, campo: string): number {
|
|
const n = Number(raw);
|
|
if (!Number.isFinite(n) || n <= 0) {
|
|
throw new Error(`El valor de "${campo}" debe ser un número mayor que 0.`);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
export async function crearMaterial(formData: FormData) {
|
|
const tenantId = await getTenantId();
|
|
await db.insert(catalogItems).values({
|
|
tenantId,
|
|
categoria: formData.get('categoria') as 'suelo' | 'pared' | 'pintura' | 'mobiliario',
|
|
nombre: String(formData.get('nombre') ?? ''),
|
|
calidad: formData.get('calidad') as 'basica' | 'media' | 'premium',
|
|
precioUnit: eurosToCents(formData.get('precioEuros'), 'precio'),
|
|
unidad: formData.get('unidad') as 'm2' | 'ml' | 'ud',
|
|
descriptorRender: String(formData.get('descriptorRender') ?? ''),
|
|
esDefault: formData.get('esDefault') === 'on',
|
|
sku: String(formData.get('sku') ?? ''),
|
|
});
|
|
revalidatePath('/panel/precios');
|
|
}
|
|
|
|
export async function actualizarPrecio(formData: FormData) {
|
|
const tenantId = await getTenantId();
|
|
const id = String(formData.get('id') ?? '');
|
|
await db
|
|
.update(catalogItems)
|
|
.set({ precioUnit: eurosToCents(formData.get('precioEuros'), 'precio') })
|
|
.where(and(eq(catalogItems.id, id), eq(catalogItems.tenantId, tenantId)));
|
|
revalidatePath('/panel/precios');
|
|
}
|
|
|
|
export async function borrarMaterial(formData: FormData) {
|
|
const tenantId = await getTenantId();
|
|
const id = String(formData.get('id') ?? '');
|
|
await db.delete(catalogItems).where(and(eq(catalogItems.id, id), eq(catalogItems.tenantId, tenantId)));
|
|
revalidatePath('/panel/precios');
|
|
}
|
|
|
|
export async function actualizarConfig(formData: FormData) {
|
|
const tenantId = await getTenantId();
|
|
await db
|
|
.update(pricingConfig)
|
|
.set({
|
|
alturaTechoDefault: parsePositive(formData.get('alturaTechoDefault'), 'altura de techo'),
|
|
manoObra: {
|
|
demolicion: eurosToCents(formData.get('mo_demolicion'), 'demolición'),
|
|
fontaneria: eurosToCents(formData.get('mo_fontaneria'), 'fontanería'),
|
|
electricidad: eurosToCents(formData.get('mo_electricidad'), 'electricidad'),
|
|
mano_de_obra: eurosToCents(formData.get('mo_mano_de_obra'), 'mano de obra'),
|
|
},
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(eq(pricingConfig.tenantId, tenantId));
|
|
revalidatePath('/panel/precios');
|
|
}
|
|
|
|
export type ImportResult = { ok: boolean; inserted: number; errors: { line: number; message: string }[] };
|
|
|
|
export async function importarCatalogoCsv(_prev: ImportResult | null, formData: FormData): Promise<ImportResult> {
|
|
const tenantId = await getTenantId();
|
|
const csv = String(formData.get('csv') ?? '');
|
|
const { rows, errors } = parseCatalogCsv(csv);
|
|
if (errors.length > 0) return { ok: false, inserted: 0, errors };
|
|
|
|
for (const r of rows) {
|
|
await db
|
|
.insert(catalogItems)
|
|
.values({ tenantId, ...r })
|
|
.onConflictDoUpdate({
|
|
target: [catalogItems.tenantId, catalogItems.sku],
|
|
set: {
|
|
categoria: r.categoria,
|
|
nombre: r.nombre,
|
|
calidad: r.calidad,
|
|
precioUnit: r.precioUnit,
|
|
unidad: r.unidad,
|
|
descriptorRender: r.descriptorRender,
|
|
},
|
|
});
|
|
}
|
|
revalidatePath('/panel/precios');
|
|
return { ok: true, inserted: rows.length, errors: [] };
|
|
}
|