carico degli articoli delle macchine/parti/gruppi quando vengono eliminati.
This commit is contained in:
parent
573cdd476f
commit
15a08b006d
4 changed files with 233 additions and 30 deletions
|
|
@ -102,7 +102,22 @@ export async function DELETE(request: Request, { params }: RouteParams) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const deleted = await deleteMacchina(macchinaId);
|
// Leggi il body per ottenere il magazzino di rientro (opzionale)
|
||||||
|
let idMagazzinoRientro: number | undefined;
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
if (body.idMagazzinoRientro) {
|
||||||
|
idMagazzinoRientro = parseInt(body.idMagazzinoRientro, 10);
|
||||||
|
if (isNaN(idMagazzinoRientro)) {
|
||||||
|
idMagazzinoRientro = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Body vuoto o non JSON, continua senza magazzino
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = session.user.email || session.user.name || "unknown";
|
||||||
|
const deleted = await deleteMacchina(macchinaId, idMagazzinoRientro, user);
|
||||||
|
|
||||||
if (!deleted) {
|
if (!deleted) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ interface RimuoviModalState {
|
||||||
quantita: number;
|
quantita: number;
|
||||||
opzione: "carico" | "elimina";
|
opzione: "carico" | "elimina";
|
||||||
idMagazzino: number | null;
|
idMagazzino: number | null;
|
||||||
|
articoloEsiste: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ArticoliMacchinaModal({
|
export default function ArticoliMacchinaModal({
|
||||||
|
|
@ -54,14 +55,25 @@ export default function ArticoliMacchinaModal({
|
||||||
fetchMagazzini();
|
fetchMagazzini();
|
||||||
}, [macchinaStabilimentoId]);
|
}, [macchinaStabilimentoId]);
|
||||||
|
|
||||||
const handleRimuoviClick = (articolo: ArticoloAssegnatoMacchina) => {
|
const handleRimuoviClick = async (articolo: ArticoloAssegnatoMacchina) => {
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
// Verifica se l'articolo esiste ancora nel catalogo
|
||||||
|
let articoloEsiste = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/articoli/${articolo.id_articolo}`);
|
||||||
|
articoloEsiste = res.ok;
|
||||||
|
} catch {
|
||||||
|
articoloEsiste = false;
|
||||||
|
}
|
||||||
|
|
||||||
setRimuoviModal({
|
setRimuoviModal({
|
||||||
articolo,
|
articolo,
|
||||||
quantita: 1,
|
quantita: 1,
|
||||||
opzione: "carico",
|
opzione: articoloEsiste ? "carico" : "elimina",
|
||||||
idMagazzino: null,
|
idMagazzino: null,
|
||||||
|
articoloEsiste,
|
||||||
});
|
});
|
||||||
setError(null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfermaRimozione = async () => {
|
const handleConfermaRimozione = async () => {
|
||||||
|
|
@ -211,11 +223,18 @@ export default function ArticoliMacchinaModal({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{!rimuoviModal.articoloEsiste && (
|
||||||
|
<div className="p-3 rounded-lg bg-yellow-900/30 border border-yellow-700 text-yellow-400 text-sm">
|
||||||
|
Questo articolo non esiste più nel catalogo. Verrà eliminato senza possibilità di rientro in magazzino.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-zinc-300 mb-2">
|
<label className="block text-sm font-medium text-zinc-300 mb-2">
|
||||||
Cosa fare con gli articoli rimossi?
|
Cosa fare con gli articoli rimossi?
|
||||||
</label>
|
</label>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
{rimuoviModal.articoloEsiste && (
|
||||||
<label className="flex items-center gap-3 p-3 rounded-lg bg-zinc-800 border border-zinc-700 cursor-pointer hover:border-teal-600">
|
<label className="flex items-center gap-3 p-3 rounded-lg bg-zinc-800 border border-zinc-700 cursor-pointer hover:border-teal-600">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
|
|
@ -230,7 +249,8 @@ export default function ArticoliMacchinaModal({
|
||||||
<p className="text-xs text-zinc-500">Gli articoli torneranno disponibili nel magazzino</p>
|
<p className="text-xs text-zinc-500">Gli articoli torneranno disponibili nel magazzino</p>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<label className="flex items-center gap-3 p-3 rounded-lg bg-zinc-800 border border-zinc-700 cursor-pointer hover:border-red-600">
|
)}
|
||||||
|
<label className={`flex items-center gap-3 p-3 rounded-lg bg-zinc-800 border cursor-pointer hover:border-red-600 ${!rimuoviModal.articoloEsiste ? 'border-red-600' : 'border-zinc-700'}`}>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="opzione"
|
name="opzione"
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,14 @@ export default function MacchineTree() {
|
||||||
parentStabilimento: number | null;
|
parentStabilimento: number | null;
|
||||||
}>({ parentId: null, parentStabilimento: null });
|
}>({ parentId: null, parentStabilimento: null });
|
||||||
|
|
||||||
const [deleteConfirm, setDeleteConfirm] = useState<{ id: number; nome: string } | null>(null);
|
const [deleteConfirm, setDeleteConfirm] = useState<{
|
||||||
|
id: number;
|
||||||
|
nome: string;
|
||||||
|
stabilimentoId: number | null;
|
||||||
|
hasArticoli: boolean;
|
||||||
|
idMagazzinoRientro: number | null;
|
||||||
|
} | null>(null);
|
||||||
|
const [deleteMagazzini, setDeleteMagazzini] = useState<{ id: number; descrizione: string }[]>([]);
|
||||||
|
|
||||||
const [dropModal, setDropModal] = useState<DropModalState | null>(null);
|
const [dropModal, setDropModal] = useState<DropModalState | null>(null);
|
||||||
const [dropQuantita, setDropQuantita] = useState(1);
|
const [dropQuantita, setDropQuantita] = useState(1);
|
||||||
|
|
@ -159,26 +166,76 @@ export default function MacchineTree() {
|
||||||
await fetchNodes();
|
await fetchNodes();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
const node = nodes.find((n) => n.id === id);
|
const node = nodes.find((n) => n.id === id);
|
||||||
if (node) {
|
if (!node) return;
|
||||||
setDeleteConfirm({ id, nome: node.nome });
|
|
||||||
|
// Calcola se questa macchina o i suoi discendenti hanno articoli
|
||||||
|
const getDescendantIds = (nodeId: number): number[] => {
|
||||||
|
const children = nodes.filter((n) => n.id_padre === nodeId);
|
||||||
|
let ids = [nodeId];
|
||||||
|
for (const child of children) {
|
||||||
|
ids = [...ids, ...getDescendantIds(child.id)];
|
||||||
}
|
}
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
const allIds = getDescendantIds(id);
|
||||||
|
const hasArticoli = allIds.some((mid) => {
|
||||||
|
const arts = articlesByMacchina[mid] || [];
|
||||||
|
return arts.some((a) => a.quantita > 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Se ci sono articoli, carica i magazzini dello stabilimento
|
||||||
|
if (hasArticoli && node.id_stabilimento) {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/magazzini");
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
const filtered = data.magazzini.filter(
|
||||||
|
(m: { id: number; descrizione: string; id_stabilimento: number }) =>
|
||||||
|
m.id_stabilimento === node.id_stabilimento
|
||||||
|
);
|
||||||
|
setDeleteMagazzini(filtered);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Errore caricamento magazzini:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setDeleteConfirm({
|
||||||
|
id,
|
||||||
|
nome: node.nome,
|
||||||
|
stabilimentoId: node.id_stabilimento,
|
||||||
|
hasArticoli,
|
||||||
|
idMagazzinoRientro: null,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmDelete = async () => {
|
const confirmDelete = async () => {
|
||||||
if (!deleteConfirm) return;
|
if (!deleteConfirm) return;
|
||||||
|
|
||||||
|
// Se ci sono articoli ma non è stato selezionato un magazzino, non procedere
|
||||||
|
if (deleteConfirm.hasArticoli && !deleteConfirm.idMagazzinoRientro) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/macchine/${deleteConfirm.id}`, {
|
const res = await fetch(`/api/macchine/${deleteConfirm.id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
idMagazzinoRientro: deleteConfirm.idMagazzinoRientro,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error("Errore nell'eliminazione");
|
if (!res.ok) throw new Error("Errore nell'eliminazione");
|
||||||
await fetchNodes();
|
await Promise.all([fetchNodes(), fetchArticoliPerMacchina()]);
|
||||||
|
window.dispatchEvent(new CustomEvent("articoli-updated"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Errore eliminazione:", err);
|
console.error("Errore eliminazione:", err);
|
||||||
} finally {
|
} finally {
|
||||||
setDeleteConfirm(null);
|
setDeleteConfirm(null);
|
||||||
|
setDeleteMagazzini([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -458,24 +515,60 @@ export default function MacchineTree() {
|
||||||
{/* Delete Confirmation */}
|
{/* Delete Confirmation */}
|
||||||
{deleteConfirm && (
|
{deleteConfirm && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={() => setDeleteConfirm(null)} />
|
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={() => { setDeleteConfirm(null); setDeleteMagazzini([]); }} />
|
||||||
<div className="relative w-full max-w-sm bg-zinc-900 rounded-xl border border-zinc-800 shadow-2xl p-5">
|
<div className="relative w-full max-w-md bg-zinc-900 rounded-xl border border-zinc-800 shadow-2xl p-5">
|
||||||
<h3 className="text-lg font-semibold text-white mb-2">Conferma eliminazione</h3>
|
<h3 className="text-lg font-semibold text-white mb-2">Conferma eliminazione</h3>
|
||||||
<p className="text-sm text-zinc-400 mb-4">
|
<p className="text-sm text-zinc-400 mb-4">
|
||||||
Sei sicuro di voler eliminare <strong className="text-white">{deleteConfirm.nome}</strong>?
|
Sei sicuro di voler eliminare <strong className="text-white">{deleteConfirm.nome}</strong>?
|
||||||
<br />
|
<br />
|
||||||
<span className="text-red-400">Verranno eliminati anche tutti i sotto-elementi.</span>
|
<span className="text-red-400">Verranno eliminati anche tutti i sotto-elementi.</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{deleteConfirm.hasArticoli && (
|
||||||
|
<div className="mb-4 p-4 rounded-lg bg-yellow-900/20 border border-yellow-700/30">
|
||||||
|
<p className="text-sm text-yellow-300 mb-3">
|
||||||
|
Questa macchina contiene articoli assegnati. Seleziona il magazzino dove far rientrare gli articoli:
|
||||||
|
</p>
|
||||||
|
{deleteMagazzini.length === 0 ? (
|
||||||
|
<p className="text-xs text-red-400">
|
||||||
|
Nessun magazzino disponibile per questo stabilimento. Gli articoli andranno persi.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<select
|
||||||
|
value={deleteConfirm.idMagazzinoRientro || ""}
|
||||||
|
onChange={(e) => setDeleteConfirm({
|
||||||
|
...deleteConfirm,
|
||||||
|
idMagazzinoRientro: e.target.value ? parseInt(e.target.value) : null,
|
||||||
|
})}
|
||||||
|
className="w-full px-3 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white text-sm focus:outline-none focus:ring-2 focus:ring-yellow-500"
|
||||||
|
>
|
||||||
|
<option value="">Seleziona magazzino...</option>
|
||||||
|
{deleteMagazzini.map((mag) => (
|
||||||
|
<option key={mag.id} value={mag.id}>
|
||||||
|
{mag.descrizione}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
{deleteMagazzini.length > 0 && !deleteConfirm.idMagazzinoRientro && (
|
||||||
|
<p className="text-xs text-yellow-400 mt-2">
|
||||||
|
Devi selezionare un magazzino per procedere
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-end gap-3">
|
<div className="flex items-center justify-end gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setDeleteConfirm(null)}
|
onClick={() => { setDeleteConfirm(null); setDeleteMagazzini([]); }}
|
||||||
className="px-4 py-2 text-sm font-medium text-zinc-300 hover:text-white rounded-lg hover:bg-zinc-800 transition-colors"
|
className="px-4 py-2 text-sm font-medium text-zinc-300 hover:text-white rounded-lg hover:bg-zinc-800 transition-colors"
|
||||||
>
|
>
|
||||||
Annulla
|
Annulla
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={confirmDelete}
|
onClick={confirmDelete}
|
||||||
className="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-500 transition-colors"
|
disabled={deleteConfirm.hasArticoli && deleteMagazzini.length > 0 && !deleteConfirm.idMagazzinoRientro}
|
||||||
|
className="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-500 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
Elimina
|
Elimina
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,82 @@ export async function updateMacchina(
|
||||||
return getMacchinaById(id);
|
return getMacchinaById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteMacchina(id: number): Promise<boolean> {
|
export async function deleteMacchina(
|
||||||
|
id: number,
|
||||||
|
idMagazzinoRientro?: number,
|
||||||
|
user: string = "system"
|
||||||
|
): Promise<boolean> {
|
||||||
|
// Recupera gli articoli assegnati a questa macchina
|
||||||
|
const [articoliRows] = await db.execute<(RowDataPacket & {
|
||||||
|
idart: number;
|
||||||
|
qtain: number;
|
||||||
|
qtaout: number;
|
||||||
|
descrizione: string;
|
||||||
|
codice: string;
|
||||||
|
})[]>(
|
||||||
|
`SELECT idart, qtain, qtaout, descrizione, codice
|
||||||
|
FROM art_macchine_parti
|
||||||
|
WHERE idmacchina = :id AND (qtain - qtaout) > 0`,
|
||||||
|
{ id }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Se c'è un magazzino di rientro, fai il carico degli articoli
|
||||||
|
if (idMagazzinoRientro && articoliRows.length > 0) {
|
||||||
|
for (const art of articoliRows) {
|
||||||
|
const quantita = art.qtain - art.qtaout;
|
||||||
|
if (quantita <= 0) continue;
|
||||||
|
|
||||||
|
// Verifica se l'articolo esiste ancora nel catalogo
|
||||||
|
const [existsRows] = await db.execute<(RowDataPacket & { id: number })[]>(
|
||||||
|
"SELECT id FROM articoli WHERE id = :idart LIMIT 1",
|
||||||
|
{ idart: art.idart }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existsRows.length > 0) {
|
||||||
|
// L'articolo esiste, registra il movimento di carico
|
||||||
|
await db.execute(
|
||||||
|
`INSERT INTO movimenti (id_articolo, articolo_codice, articolo_descrizione, quantita, tipo_movimento, causale, utente, id_magazzino_destinazione)
|
||||||
|
VALUES (:id_articolo, :articolo_codice, :articolo_descrizione, :quantita, 1, :causale, :utente, :id_magazzino_destinazione)`,
|
||||||
|
{
|
||||||
|
id_articolo: art.idart,
|
||||||
|
articolo_codice: art.codice,
|
||||||
|
articolo_descrizione: art.descrizione,
|
||||||
|
quantita,
|
||||||
|
causale: "Rientro da eliminazione macchina",
|
||||||
|
utente: user,
|
||||||
|
id_magazzino_destinazione: idMagazzinoRientro,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Aggiorna la giacenza
|
||||||
|
const [existingGiacenza] = await db.execute<(RowDataPacket & { id: number })[]>(
|
||||||
|
"SELECT id FROM giacenze WHERE articolo_id = :articoloId AND id_magazzino = :magazzinoId",
|
||||||
|
{ articoloId: art.idart, magazzinoId: idMagazzinoRientro }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingGiacenza.length > 0) {
|
||||||
|
await db.execute(
|
||||||
|
"UPDATE giacenze SET quantita = quantita + :quantita WHERE articolo_id = :articoloId AND id_magazzino = :magazzinoId",
|
||||||
|
{ quantita, articoloId: art.idart, magazzinoId: idMagazzinoRientro }
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await db.execute(
|
||||||
|
"INSERT INTO giacenze (articolo_id, id_magazzino, quantita) VALUES (:articoloId, :magazzinoId, :quantita)",
|
||||||
|
{ articoloId: art.idart, magazzinoId: idMagazzinoRientro, quantita }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Se l'articolo non esiste più, non facciamo nulla (gli articoli vengono persi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elimina gli articoli assegnati a questa macchina
|
||||||
|
await db.execute(
|
||||||
|
"DELETE FROM art_macchine_parti WHERE idmacchina = :id",
|
||||||
|
{ id }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Elimina ricorsivamente i figli
|
||||||
const [childRows] = await db.execute<MacchinaRow[]>(
|
const [childRows] = await db.execute<MacchinaRow[]>(
|
||||||
"SELECT id FROM macchine_parti WHERE id_padre = :id",
|
"SELECT id FROM macchine_parti WHERE id_padre = :id",
|
||||||
{ id }
|
{ id }
|
||||||
|
|
@ -170,7 +245,7 @@ export async function deleteMacchina(id: number): Promise<boolean> {
|
||||||
|
|
||||||
if (childRows.length > 0) {
|
if (childRows.length > 0) {
|
||||||
for (const child of childRows) {
|
for (const child of childRows) {
|
||||||
await deleteMacchina(child.id);
|
await deleteMacchina(child.id, idMagazzinoRientro, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue