inserita la gestiione stabilimenti
This commit is contained in:
parent
c1f2dfb44d
commit
85e2256022
10 changed files with 891 additions and 25 deletions
11
src/app/(management)/stabilimenti/page.tsx
Normal file
11
src/app/(management)/stabilimenti/page.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import StabilimentiManager from "@/src/components/StabilimentiManager";
|
||||||
|
|
||||||
|
export default function StabilimentiPage() {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-black -mx-4 -my-6 px-4 py-6">
|
||||||
|
<StabilimentiManager />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
79
src/app/api/magazzini/[id]/route.ts
Normal file
79
src/app/api/magazzini/[id]/route.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
import { auth } from "@/src/auth";
|
||||||
|
import { getMagazzinoById, updateMagazzino, deleteMagazzino } from "@/src/lib/stabilimenti";
|
||||||
|
|
||||||
|
interface RouteContext {
|
||||||
|
params: Promise<{ id: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readId(params: Promise<{ id: string }>): Promise<number> {
|
||||||
|
const { id } = await params;
|
||||||
|
const magazzinoId = Number(id);
|
||||||
|
|
||||||
|
if (!Number.isInteger(magazzinoId) || magazzinoId <= 0) {
|
||||||
|
throw new Error("ID magazzino non valido.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return magazzinoId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(_request: Request, context: RouteContext) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const magazzino = await getMagazzinoById(await readId(context.params));
|
||||||
|
if (!magazzino) {
|
||||||
|
return NextResponse.json({ error: "Magazzino non trovato" }, { status: 404 });
|
||||||
|
}
|
||||||
|
return NextResponse.json({ magazzino });
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: error instanceof Error ? error.message : "Errore recupero magazzino" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PUT(request: Request, context: RouteContext) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const magazzino = await updateMagazzino(
|
||||||
|
await readId(context.params),
|
||||||
|
await request.json()
|
||||||
|
);
|
||||||
|
return NextResponse.json({ magazzino });
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: error instanceof Error ? error.message : "Errore modifica magazzino" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(_request: Request, context: RouteContext) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteMagazzino(await readId(context.params));
|
||||||
|
return NextResponse.json({ ok: true });
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: error instanceof Error ? error.message : "Errore eliminazione magazzino" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
import { auth } from "@/src/auth";
|
import { auth } from "@/src/auth";
|
||||||
import { listMagazzini } from "@/src/lib/stabilimenti";
|
import { listMagazzini, createMagazzino } from "@/src/lib/stabilimenti";
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
@ -27,3 +27,21 @@ export async function GET(request: Request) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const magazzino = await createMagazzino(await request.json());
|
||||||
|
return NextResponse.json({ magazzino }, { status: 201 });
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: error instanceof Error ? error.message : "Errore creazione magazzino" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
83
src/app/api/stabilimenti/[id]/route.ts
Normal file
83
src/app/api/stabilimenti/[id]/route.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
import { auth } from "@/src/auth";
|
||||||
|
import {
|
||||||
|
getStabilimentoById,
|
||||||
|
updateStabilimento,
|
||||||
|
deleteStabilimento,
|
||||||
|
} from "@/src/lib/stabilimenti";
|
||||||
|
|
||||||
|
interface RouteContext {
|
||||||
|
params: Promise<{ id: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readId(params: Promise<{ id: string }>): Promise<number> {
|
||||||
|
const { id } = await params;
|
||||||
|
const stabilimentoId = Number(id);
|
||||||
|
|
||||||
|
if (!Number.isInteger(stabilimentoId) || stabilimentoId <= 0) {
|
||||||
|
throw new Error("ID stabilimento non valido.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return stabilimentoId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(_request: Request, context: RouteContext) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stabilimento = await getStabilimentoById(await readId(context.params));
|
||||||
|
if (!stabilimento) {
|
||||||
|
return NextResponse.json({ error: "Stabilimento non trovato" }, { status: 404 });
|
||||||
|
}
|
||||||
|
return NextResponse.json({ stabilimento });
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: error instanceof Error ? error.message : "Errore recupero stabilimento" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PUT(request: Request, context: RouteContext) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stabilimento = await updateStabilimento(
|
||||||
|
await readId(context.params),
|
||||||
|
await request.json()
|
||||||
|
);
|
||||||
|
return NextResponse.json({ stabilimento });
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: error instanceof Error ? error.message : "Errore modifica stabilimento" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(_request: Request, context: RouteContext) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteStabilimento(await readId(context.params));
|
||||||
|
return NextResponse.json({ ok: true });
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: error instanceof Error ? error.message : "Errore eliminazione stabilimento" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
import { auth } from "@/src/auth";
|
import { auth } from "@/src/auth";
|
||||||
import { listStabilimenti, getStabilimentiWithMagazzini } from "@/src/lib/stabilimenti";
|
import {
|
||||||
|
listStabilimenti,
|
||||||
|
getStabilimentiWithMagazzini,
|
||||||
|
createStabilimento,
|
||||||
|
} from "@/src/lib/stabilimenti";
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
@ -29,3 +33,21 @@ export async function GET(request: Request) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stabilimento = await createStabilimento(await request.json());
|
||||||
|
return NextResponse.json({ stabilimento }, { status: 201 });
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: error instanceof Error ? error.message : "Errore creazione stabilimento" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ const navLinks: NavLink[] = [
|
||||||
{ href: "/movimenti", label: "Movimenti"},
|
{ href: "/movimenti", label: "Movimenti"},
|
||||||
{ href: "/articoli", label: "Articoli"},
|
{ href: "/articoli", label: "Articoli"},
|
||||||
{ href: "/macchine", label: "Macchine"},
|
{ href: "/macchine", label: "Macchine"},
|
||||||
|
{ href: "/stabilimenti", label: "Stabilimenti"},
|
||||||
{ href: "/utenti", label: "Utenti"},
|
{ href: "/utenti", label: "Utenti"},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
431
src/components/StabilimentiManager.tsx
Normal file
431
src/components/StabilimentiManager.tsx
Normal file
|
|
@ -0,0 +1,431 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect, useCallback } from "react";
|
||||||
|
import type { Stabilimento, Magazzino, StabilimentoWithMagazzini } from "@/src/types/stabilimento";
|
||||||
|
import { useStabilimento } from "@/src/contexts/StabilimentoContext";
|
||||||
|
|
||||||
|
function FormModal({
|
||||||
|
title,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
children,
|
||||||
|
submitLabel,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (e: React.FormEvent) => void;
|
||||||
|
children: React.ReactNode;
|
||||||
|
submitLabel: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||||
|
<div className="absolute inset-0 bg-black/70 backdrop-blur-sm" onClick={onClose} />
|
||||||
|
<div className="relative w-full max-w-md rounded-xl bg-zinc-900 border border-zinc-700 p-6 shadow-xl">
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-4">{title}</h3>
|
||||||
|
<form onSubmit={onSubmit} className="space-y-4">
|
||||||
|
{children}
|
||||||
|
<div className="flex justify-end gap-3 pt-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="px-4 py-2 rounded-lg border border-zinc-700 text-zinc-300 hover:bg-zinc-800"
|
||||||
|
>
|
||||||
|
Annulla
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="px-4 py-2 rounded-lg bg-blue-600 text-white hover:bg-blue-500"
|
||||||
|
>
|
||||||
|
{submitLabel}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StabilimentiManager() {
|
||||||
|
const { reload } = useStabilimento();
|
||||||
|
const [stabilimenti, setStabilimenti] = useState<StabilimentoWithMagazzini[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
const [stabModal, setStabModal] = useState<"create" | "edit" | null>(null);
|
||||||
|
const [magModal, setMagModal] = useState<"create" | "edit" | null>(null);
|
||||||
|
const [editingStabilimento, setEditingStabilimento] = useState<Stabilimento | null>(null);
|
||||||
|
const [editingMagazzino, setEditingMagazzino] = useState<Magazzino | null>(null);
|
||||||
|
const [presetStabilimentoId, setPresetStabilimentoId] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const [stabDescrizione, setStabDescrizione] = useState("");
|
||||||
|
const [magDescrizione, setMagDescrizione] = useState("");
|
||||||
|
const [magStabilimentoId, setMagStabilimentoId] = useState("");
|
||||||
|
|
||||||
|
const loadData = useCallback(async () => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/stabilimenti?includeMagazzini=true");
|
||||||
|
if (!res.ok) throw new Error("Errore nel caricamento");
|
||||||
|
const data = await res.json();
|
||||||
|
setStabilimenti(data.stabilimenti || []);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : "Errore sconosciuto");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData();
|
||||||
|
}, [loadData]);
|
||||||
|
|
||||||
|
const afterMutation = async () => {
|
||||||
|
await loadData();
|
||||||
|
await reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCreateStabilimento = () => {
|
||||||
|
setEditingStabilimento(null);
|
||||||
|
setStabDescrizione("");
|
||||||
|
setStabModal("create");
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEditStabilimento = (stab: Stabilimento) => {
|
||||||
|
setEditingStabilimento(stab);
|
||||||
|
setStabDescrizione(stab.descrizione);
|
||||||
|
setStabModal("edit");
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCreateMagazzino = (stabilimentoId?: number) => {
|
||||||
|
setEditingMagazzino(null);
|
||||||
|
setMagDescrizione("");
|
||||||
|
setMagStabilimentoId(stabilimentoId?.toString() || "");
|
||||||
|
setPresetStabilimentoId(stabilimentoId ?? null);
|
||||||
|
setMagModal("create");
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEditMagazzino = (mag: Magazzino) => {
|
||||||
|
setEditingMagazzino(mag);
|
||||||
|
setMagDescrizione(mag.descrizione);
|
||||||
|
setMagStabilimentoId(mag.id_stabilimento.toString());
|
||||||
|
setMagModal("edit");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStabilimentoSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const url =
|
||||||
|
stabModal === "edit" && editingStabilimento
|
||||||
|
? `/api/stabilimenti/${editingStabilimento.id}`
|
||||||
|
: "/api/stabilimenti";
|
||||||
|
const method = stabModal === "edit" ? "PUT" : "POST";
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ descrizione: stabDescrizione }),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) throw new Error(data.error || "Operazione fallita");
|
||||||
|
setStabModal(null);
|
||||||
|
await afterMutation();
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : "Errore");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMagazzinoSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const url =
|
||||||
|
magModal === "edit" && editingMagazzino
|
||||||
|
? `/api/magazzini/${editingMagazzino.id}`
|
||||||
|
: "/api/magazzini";
|
||||||
|
const method = magModal === "edit" ? "PUT" : "POST";
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
descrizione: magDescrizione,
|
||||||
|
id_stabilimento: parseInt(magStabilimentoId, 10),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) throw new Error(data.error || "Operazione fallita");
|
||||||
|
setMagModal(null);
|
||||||
|
await afterMutation();
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : "Errore");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteStabilimento = async (id: number, nome: string) => {
|
||||||
|
if (!confirm(`Eliminare lo stabilimento "${nome}"?`)) return;
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/stabilimenti/${id}`, { method: "DELETE" });
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) throw new Error(data.error || "Eliminazione fallita");
|
||||||
|
await afterMutation();
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : "Errore");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteMagazzino = async (id: number, nome: string) => {
|
||||||
|
if (!confirm(`Eliminare il magazzino "${nome}"?`)) return;
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/magazzini/${id}`, { method: "DELETE" });
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) throw new Error(data.error || "Eliminazione fallita");
|
||||||
|
await afterMutation();
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : "Errore");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filtered = stabilimenti.filter((s) =>
|
||||||
|
s.descrizione.toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalMagazzini = stabilimenti.reduce((n, s) => n + s.magazzini.length, 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mb-6">
|
||||||
|
<h1 className="text-2xl font-bold text-white">Stabilimenti e Magazzini</h1>
|
||||||
|
<p className="text-sm text-zinc-500">
|
||||||
|
Anagrafica sedi produttive e magazzini associati
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4 mb-6">
|
||||||
|
<div className="p-4 rounded-xl bg-zinc-900 border border-zinc-800">
|
||||||
|
<p className="text-sm text-zinc-500">Stabilimenti</p>
|
||||||
|
<p className="text-2xl font-bold text-white">{stabilimenti.length}</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 rounded-xl bg-zinc-900 border border-zinc-800">
|
||||||
|
<p className="text-sm text-zinc-500">Magazzini totali</p>
|
||||||
|
<p className="text-2xl font-bold text-teal-400">{totalMagazzini}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="mb-4 p-4 rounded-lg bg-red-500/10 border border-red-500/30 text-red-400 text-sm flex justify-between items-center">
|
||||||
|
<span>{error}</span>
|
||||||
|
<button type="button" onClick={() => setError(null)} className="text-red-300 hover:text-white">
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col md:flex-row gap-4 items-start md:items-center justify-between mb-6">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
placeholder="Cerca stabilimenti..."
|
||||||
|
className="w-full md:max-w-md px-4 py-2 rounded-lg bg-zinc-900 border border-zinc-800 text-white placeholder-zinc-500 focus:outline-none focus:border-blue-500"
|
||||||
|
/>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={openCreateStabilimento}
|
||||||
|
className="px-4 py-2 rounded-lg bg-blue-600 text-white font-medium hover:bg-blue-500"
|
||||||
|
>
|
||||||
|
Nuovo stabilimento
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => openCreateMagazzino()}
|
||||||
|
className="px-4 py-2 rounded-lg bg-teal-700 text-white font-medium hover:bg-teal-600"
|
||||||
|
>
|
||||||
|
Nuovo magazzino
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div className="flex items-center justify-center py-20 text-zinc-500">
|
||||||
|
<svg className="w-6 h-6 animate-spin mr-3" viewBox="0 0 24 24">
|
||||||
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
|
||||||
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||||
|
</svg>
|
||||||
|
Caricamento...
|
||||||
|
</div>
|
||||||
|
) : filtered.length === 0 ? (
|
||||||
|
<div className="p-12 rounded-xl bg-zinc-900 border border-zinc-800 text-center">
|
||||||
|
<h3 className="text-lg font-medium text-white mb-2">Nessuno stabilimento</h3>
|
||||||
|
<p className="text-zinc-500 mb-4">
|
||||||
|
{search ? "Nessun risultato per la ricerca" : "Aggiungi il primo stabilimento"}
|
||||||
|
</p>
|
||||||
|
{!search && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={openCreateStabilimento}
|
||||||
|
className="px-4 py-2 rounded-lg bg-blue-600 text-white hover:bg-blue-500"
|
||||||
|
>
|
||||||
|
Crea stabilimento
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{filtered.map((stab) => (
|
||||||
|
<div
|
||||||
|
key={stab.id}
|
||||||
|
className="rounded-xl bg-zinc-900 border border-zinc-800 overflow-hidden"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between p-4 border-b border-zinc-800">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span className="text-2xl">🏭</span>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold text-white">{stab.descrizione}</h2>
|
||||||
|
<p className="text-xs text-zinc-500">
|
||||||
|
{stab.magazzini.length} magazzin{stab.magazzini.length === 1 ? "o" : "i"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => openCreateMagazzino(stab.id)}
|
||||||
|
className="px-3 py-1.5 text-xs rounded-lg bg-teal-900/50 text-teal-300 border border-teal-800 hover:bg-teal-900"
|
||||||
|
title="Aggiungi magazzino"
|
||||||
|
>
|
||||||
|
+ Magazzino
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => openEditStabilimento(stab)}
|
||||||
|
className="p-2 rounded-lg text-zinc-400 hover:text-white hover:bg-zinc-800"
|
||||||
|
title="Modifica"
|
||||||
|
>
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleDeleteStabilimento(stab.id, stab.descrizione)}
|
||||||
|
className="p-2 rounded-lg text-red-400 hover:text-red-300 hover:bg-red-500/10"
|
||||||
|
title="Elimina"
|
||||||
|
>
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4">
|
||||||
|
{stab.magazzini.length === 0 ? (
|
||||||
|
<p className="text-sm text-zinc-500 italic">Nessun magazzino associato</p>
|
||||||
|
) : (
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{stab.magazzini.map((mag) => (
|
||||||
|
<li
|
||||||
|
key={mag.id}
|
||||||
|
className="flex items-center justify-between py-2 px-3 rounded-lg bg-zinc-950 border border-zinc-800"
|
||||||
|
>
|
||||||
|
<span className="text-sm text-zinc-200 flex items-center gap-2">
|
||||||
|
<span>📦</span>
|
||||||
|
{mag.descrizione}
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => openEditMagazzino(mag)}
|
||||||
|
className="p-1.5 rounded text-zinc-400 hover:text-white hover:bg-zinc-800"
|
||||||
|
title="Modifica"
|
||||||
|
>
|
||||||
|
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleDeleteMagazzino(mag.id, mag.descrizione)}
|
||||||
|
className="p-1.5 rounded text-red-400 hover:text-red-300 hover:bg-red-500/10"
|
||||||
|
title="Elimina"
|
||||||
|
>
|
||||||
|
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{stabModal && (
|
||||||
|
<FormModal
|
||||||
|
title={stabModal === "edit" ? "Modifica stabilimento" : "Nuovo stabilimento"}
|
||||||
|
onClose={() => setStabModal(null)}
|
||||||
|
onSubmit={handleStabilimentoSubmit}
|
||||||
|
submitLabel={stabModal === "edit" ? "Salva" : "Crea"}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs text-zinc-400 mb-1">Descrizione</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={stabDescrizione}
|
||||||
|
onChange={(e) => setStabDescrizione(e.target.value)}
|
||||||
|
required
|
||||||
|
className="w-full px-3 py-2 rounded-lg bg-zinc-950 border border-zinc-700 text-white focus:outline-none focus:border-blue-500"
|
||||||
|
placeholder="Es. Stabilimento Gonzaga"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormModal>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{magModal && (
|
||||||
|
<FormModal
|
||||||
|
title={magModal === "edit" ? "Modifica magazzino" : "Nuovo magazzino"}
|
||||||
|
onClose={() => setMagModal(null)}
|
||||||
|
onSubmit={handleMagazzinoSubmit}
|
||||||
|
submitLabel={magModal === "edit" ? "Salva" : "Crea"}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs text-zinc-400 mb-1">Stabilimento</label>
|
||||||
|
<select
|
||||||
|
value={magStabilimentoId}
|
||||||
|
onChange={(e) => setMagStabilimentoId(e.target.value)}
|
||||||
|
required
|
||||||
|
disabled={magModal === "create" && presetStabilimentoId !== null}
|
||||||
|
className="w-full px-3 py-2 rounded-lg bg-zinc-950 border border-zinc-700 text-white focus:outline-none focus:border-blue-500 disabled:opacity-60"
|
||||||
|
>
|
||||||
|
<option value="">Seleziona stabilimento...</option>
|
||||||
|
{stabilimenti.map((s) => (
|
||||||
|
<option key={s.id} value={s.id}>
|
||||||
|
{s.descrizione}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs text-zinc-400 mb-1">Descrizione</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={magDescrizione}
|
||||||
|
onChange={(e) => setMagDescrizione(e.target.value)}
|
||||||
|
required
|
||||||
|
className="w-full px-3 py-2 rounded-lg bg-zinc-950 border border-zinc-700 text-white focus:outline-none focus:border-blue-500"
|
||||||
|
placeholder="Es. Magazzino ricambi"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormModal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ interface StabilimentoContextValue {
|
||||||
setSelectedMagazzino: (magazzino: Magazzino | null) => void;
|
setSelectedMagazzino: (magazzino: Magazzino | null) => void;
|
||||||
filteredMagazzini: Magazzino[];
|
filteredMagazzini: Magazzino[];
|
||||||
resetSelection: () => void;
|
resetSelection: () => void;
|
||||||
|
reload: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StabilimentoContext = createContext<StabilimentoContextValue | null>(null);
|
const StabilimentoContext = createContext<StabilimentoContextValue | null>(null);
|
||||||
|
|
@ -31,32 +32,48 @@ export function StabilimentoProvider({ children }: { children: ReactNode }) {
|
||||||
const [selectedMagazzino, setSelectedMagazzinoState] = useState<Magazzino | null>(null);
|
const [selectedMagazzino, setSelectedMagazzinoState] = useState<Magazzino | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const reload = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const [stabRes, magRes] = await Promise.all([
|
||||||
|
fetch("/api/stabilimenti"),
|
||||||
|
fetch("/api/magazzini"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (stabRes.ok) {
|
||||||
|
const stabData = await stabRes.json();
|
||||||
|
const list: Stabilimento[] = stabData.stabilimenti || [];
|
||||||
|
setStabilimenti(list);
|
||||||
|
|
||||||
|
setSelectedStabilimentoState((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
return list.find((s) => s.id === prev.id) ?? null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (magRes.ok) {
|
||||||
|
const magData = await magRes.json();
|
||||||
|
const list: Magazzino[] = magData.magazzini || [];
|
||||||
|
setMagazzini(list);
|
||||||
|
|
||||||
|
setSelectedMagazzinoState((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
return list.find((m) => m.id === prev.id) ?? null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Errore caricamento stabilimenti/magazzini:", error);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
setLoading(true);
|
||||||
const [stabRes, magRes] = await Promise.all([
|
await reload();
|
||||||
fetch("/api/stabilimenti"),
|
setLoading(false);
|
||||||
fetch("/api/magazzini"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (stabRes.ok) {
|
|
||||||
const stabData = await stabRes.json();
|
|
||||||
setStabilimenti(stabData.stabilimenti || []);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (magRes.ok) {
|
|
||||||
const magData = await magRes.json();
|
|
||||||
setMagazzini(magData.magazzini || []);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Errore caricamento stabilimenti/magazzini:", error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, [reload]);
|
||||||
|
|
||||||
const setSelectedStabilimento = useCallback((stabilimento: Stabilimento | null) => {
|
const setSelectedStabilimento = useCallback((stabilimento: Stabilimento | null) => {
|
||||||
setSelectedStabilimentoState(stabilimento);
|
setSelectedStabilimentoState(stabilimento);
|
||||||
|
|
@ -88,6 +105,7 @@ export function StabilimentoProvider({ children }: { children: ReactNode }) {
|
||||||
setSelectedMagazzino,
|
setSelectedMagazzino,
|
||||||
filteredMagazzini,
|
filteredMagazzini,
|
||||||
resetSelection,
|
resetSelection,
|
||||||
|
reload,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
import type { RowDataPacket } from "mysql2";
|
import type { ResultSetHeader, RowDataPacket } from "mysql2";
|
||||||
|
|
||||||
import { db } from "@/src/lib/db";
|
import { db } from "@/src/lib/db";
|
||||||
import type { Stabilimento, Magazzino, StabilimentoWithMagazzini } from "@/src/types/stabilimento";
|
import type {
|
||||||
|
Stabilimento,
|
||||||
|
Magazzino,
|
||||||
|
StabilimentoWithMagazzini,
|
||||||
|
StabilimentoInput,
|
||||||
|
MagazzinoInput,
|
||||||
|
} from "@/src/types/stabilimento";
|
||||||
|
|
||||||
type StabilimentoRow = Stabilimento & RowDataPacket;
|
type StabilimentoRow = Stabilimento & RowDataPacket;
|
||||||
type MagazzinoRow = Magazzino & RowDataPacket;
|
type MagazzinoRow = Magazzino & RowDataPacket;
|
||||||
|
|
@ -87,3 +93,191 @@ export async function getMagazzinoById(id: number): Promise<Magazzino | null> {
|
||||||
stabilimento: row.stabilimento,
|
stabilimento: row.stabilimento,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cleanText(value: unknown): string {
|
||||||
|
return String(value ?? "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseStabilimentoInput(input: Partial<StabilimentoInput>): StabilimentoInput {
|
||||||
|
const descrizione = cleanText(input.descrizione);
|
||||||
|
if (!descrizione) {
|
||||||
|
throw new Error("La descrizione dello stabilimento è obbligatoria.");
|
||||||
|
}
|
||||||
|
return { descrizione };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseMagazzinoInput(input: Partial<MagazzinoInput>): MagazzinoInput {
|
||||||
|
const descrizione = cleanText(input.descrizione);
|
||||||
|
const id_stabilimento = Number(input.id_stabilimento);
|
||||||
|
|
||||||
|
if (!descrizione) {
|
||||||
|
throw new Error("La descrizione del magazzino è obbligatoria.");
|
||||||
|
}
|
||||||
|
if (!Number.isInteger(id_stabilimento) || id_stabilimento <= 0) {
|
||||||
|
throw new Error("Seleziona uno stabilimento valido per il magazzino.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { descrizione, id_stabilimento };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createStabilimento(input: Partial<StabilimentoInput>): Promise<Stabilimento> {
|
||||||
|
const { descrizione } = parseStabilimentoInput(input);
|
||||||
|
|
||||||
|
const [result] = await db.execute<ResultSetHeader>(
|
||||||
|
"INSERT INTO stabilimenti (descrizione) VALUES (:descrizione)",
|
||||||
|
{ descrizione }
|
||||||
|
);
|
||||||
|
|
||||||
|
const created = await getStabilimentoById(result.insertId);
|
||||||
|
if (!created) {
|
||||||
|
throw new Error("Errore nella creazione dello stabilimento.");
|
||||||
|
}
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateStabilimento(
|
||||||
|
id: number,
|
||||||
|
input: Partial<StabilimentoInput>
|
||||||
|
): Promise<Stabilimento> {
|
||||||
|
const existing = await getStabilimentoById(id);
|
||||||
|
if (!existing) {
|
||||||
|
throw new Error("Stabilimento non trovato.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { descrizione } = parseStabilimentoInput(input);
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
"UPDATE stabilimenti SET descrizione = :descrizione WHERE id = :id",
|
||||||
|
{ descrizione, id }
|
||||||
|
);
|
||||||
|
|
||||||
|
const updated = await getStabilimentoById(id);
|
||||||
|
if (!updated) {
|
||||||
|
throw new Error("Errore nell'aggiornamento dello stabilimento.");
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteStabilimento(id: number): Promise<void> {
|
||||||
|
const existing = await getStabilimentoById(id);
|
||||||
|
if (!existing) {
|
||||||
|
throw new Error("Stabilimento non trovato.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const [magRows] = await db.execute<RowDataPacket[]>(
|
||||||
|
"SELECT COUNT(*) AS cnt FROM magazzini WHERE id_stabilimento = :id",
|
||||||
|
{ id }
|
||||||
|
);
|
||||||
|
if (Number(magRows[0]?.cnt) > 0) {
|
||||||
|
throw new Error(
|
||||||
|
"Impossibile eliminare: esistono magazzini associati. Elimina prima i magazzini."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [macRows] = await db.execute<RowDataPacket[]>(
|
||||||
|
"SELECT COUNT(*) AS cnt FROM macchine_parti WHERE id_stabilimento = :id",
|
||||||
|
{ id }
|
||||||
|
);
|
||||||
|
if (Number(macRows[0]?.cnt) > 0) {
|
||||||
|
throw new Error(
|
||||||
|
"Impossibile eliminare: esistono macchine associate a questo stabilimento."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.execute("DELETE FROM stabilimenti WHERE id = :id", { id });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createMagazzino(input: Partial<MagazzinoInput>): Promise<Magazzino> {
|
||||||
|
const parsed = parseMagazzinoInput(input);
|
||||||
|
|
||||||
|
const stabilimento = await getStabilimentoById(parsed.id_stabilimento);
|
||||||
|
if (!stabilimento) {
|
||||||
|
throw new Error("Stabilimento non trovato.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const [result] = await db.execute<ResultSetHeader>(
|
||||||
|
"INSERT INTO magazzini (descrizione, id_stabilimento) VALUES (:descrizione, :id_stabilimento)",
|
||||||
|
{
|
||||||
|
descrizione: parsed.descrizione,
|
||||||
|
id_stabilimento: parsed.id_stabilimento,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const created = await getMagazzinoById(result.insertId);
|
||||||
|
if (!created) {
|
||||||
|
throw new Error("Errore nella creazione del magazzino.");
|
||||||
|
}
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateMagazzino(
|
||||||
|
id: number,
|
||||||
|
input: Partial<MagazzinoInput>
|
||||||
|
): Promise<Magazzino> {
|
||||||
|
const existing = await getMagazzinoById(id);
|
||||||
|
if (!existing) {
|
||||||
|
throw new Error("Magazzino non trovato.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = parseMagazzinoInput(input);
|
||||||
|
|
||||||
|
const stabilimento = await getStabilimentoById(parsed.id_stabilimento);
|
||||||
|
if (!stabilimento) {
|
||||||
|
throw new Error("Stabilimento non trovato.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
"UPDATE magazzini SET descrizione = :descrizione, id_stabilimento = :id_stabilimento WHERE id = :id",
|
||||||
|
{
|
||||||
|
descrizione: parsed.descrizione,
|
||||||
|
id_stabilimento: parsed.id_stabilimento,
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const updated = await getMagazzinoById(id);
|
||||||
|
if (!updated) {
|
||||||
|
throw new Error("Errore nell'aggiornamento del magazzino.");
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteMagazzino(id: number): Promise<void> {
|
||||||
|
const existing = await getMagazzinoById(id);
|
||||||
|
if (!existing) {
|
||||||
|
throw new Error("Magazzino non trovato.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const [giacRows] = await db.execute<RowDataPacket[]>(
|
||||||
|
"SELECT COUNT(*) AS cnt FROM giacenze WHERE id_magazzino = :id",
|
||||||
|
{ id }
|
||||||
|
);
|
||||||
|
if (Number(giacRows[0]?.cnt) > 0) {
|
||||||
|
throw new Error(
|
||||||
|
"Impossibile eliminare: il magazzino contiene giacenze di articoli."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [movRows] = await db.execute<RowDataPacket[]>(
|
||||||
|
`SELECT COUNT(*) AS cnt FROM movimenti
|
||||||
|
WHERE id_magazzino_origine = :id OR id_magazzino_destinazione = :id`,
|
||||||
|
{ id }
|
||||||
|
);
|
||||||
|
if (Number(movRows[0]?.cnt) > 0) {
|
||||||
|
throw new Error(
|
||||||
|
"Impossibile eliminare: il magazzino è referenziato in movimenti di magazzino."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [artRows] = await db.execute<RowDataPacket[]>(
|
||||||
|
"SELECT COUNT(*) AS cnt FROM articoli WHERE id_magazzino = :id",
|
||||||
|
{ id }
|
||||||
|
);
|
||||||
|
if (Number(artRows[0]?.cnt) > 0) {
|
||||||
|
throw new Error(
|
||||||
|
"Impossibile eliminare: articoli del catalogo sono collegati a questo magazzino."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.execute("DELETE FROM magazzini WHERE id = :id", { id });
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,12 @@ export interface Magazzino {
|
||||||
export interface StabilimentoWithMagazzini extends Stabilimento {
|
export interface StabilimentoWithMagazzini extends Stabilimento {
|
||||||
magazzini: Magazzino[];
|
magazzini: Magazzino[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StabilimentoInput {
|
||||||
|
descrizione: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MagazzinoInput {
|
||||||
|
descrizione: string;
|
||||||
|
id_stabilimento: number;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue