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 {
|
||||
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) {
|
||||
return NextResponse.json(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ interface RimuoviModalState {
|
|||
quantita: number;
|
||||
opzione: "carico" | "elimina";
|
||||
idMagazzino: number | null;
|
||||
articoloEsiste: boolean;
|
||||
}
|
||||
|
||||
export default function ArticoliMacchinaModal({
|
||||
|
|
@ -54,14 +55,25 @@ export default function ArticoliMacchinaModal({
|
|||
fetchMagazzini();
|
||||
}, [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({
|
||||
articolo,
|
||||
quantita: 1,
|
||||
opzione: "carico",
|
||||
opzione: articoloEsiste ? "carico" : "elimina",
|
||||
idMagazzino: null,
|
||||
articoloEsiste,
|
||||
});
|
||||
setError(null);
|
||||
};
|
||||
|
||||
const handleConfermaRimozione = async () => {
|
||||
|
|
@ -211,11 +223,18 @@ export default function ArticoliMacchinaModal({
|
|||
/>
|
||||
</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>
|
||||
<label className="block text-sm font-medium text-zinc-300 mb-2">
|
||||
Cosa fare con gli articoli rimossi?
|
||||
</label>
|
||||
<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">
|
||||
<input
|
||||
type="radio"
|
||||
|
|
@ -230,7 +249,8 @@ export default function ArticoliMacchinaModal({
|
|||
<p className="text-xs text-zinc-500">Gli articoli torneranno disponibili nel magazzino</p>
|
||||
</div>
|
||||
</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
|
||||
type="radio"
|
||||
name="opzione"
|
||||
|
|
|
|||
|
|
@ -41,7 +41,14 @@ export default function MacchineTree() {
|
|||
parentStabilimento: number | 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 [dropQuantita, setDropQuantita] = useState(1);
|
||||
|
|
@ -159,26 +166,76 @@ export default function MacchineTree() {
|
|||
await fetchNodes();
|
||||
};
|
||||
|
||||
const handleDelete = (id: number) => {
|
||||
const handleDelete = async (id: number) => {
|
||||
const node = nodes.find((n) => n.id === id);
|
||||
if (node) {
|
||||
setDeleteConfirm({ id, nome: node.nome });
|
||||
if (!node) return;
|
||||
|
||||
// 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 () => {
|
||||
if (!deleteConfirm) return;
|
||||
|
||||
// Se ci sono articoli ma non è stato selezionato un magazzino, non procedere
|
||||
if (deleteConfirm.hasArticoli && !deleteConfirm.idMagazzinoRientro) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/macchine/${deleteConfirm.id}`, {
|
||||
method: "DELETE",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
idMagazzinoRientro: deleteConfirm.idMagazzinoRientro,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw new Error("Errore nell'eliminazione");
|
||||
await fetchNodes();
|
||||
await Promise.all([fetchNodes(), fetchArticoliPerMacchina()]);
|
||||
window.dispatchEvent(new CustomEvent("articoli-updated"));
|
||||
} catch (err) {
|
||||
console.error("Errore eliminazione:", err);
|
||||
} finally {
|
||||
setDeleteConfirm(null);
|
||||
setDeleteMagazzini([]);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -458,24 +515,60 @@ export default function MacchineTree() {
|
|||
{/* Delete Confirmation */}
|
||||
{deleteConfirm && (
|
||||
<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="relative w-full max-w-sm bg-zinc-900 rounded-xl border border-zinc-800 shadow-2xl p-5">
|
||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={() => { setDeleteConfirm(null); setDeleteMagazzini([]); }} />
|
||||
<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>
|
||||
<p className="text-sm text-zinc-400 mb-4">
|
||||
Sei sicuro di voler eliminare <strong className="text-white">{deleteConfirm.nome}</strong>?
|
||||
<br />
|
||||
<span className="text-red-400">Verranno eliminati anche tutti i sotto-elementi.</span>
|
||||
</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">
|
||||
<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"
|
||||
>
|
||||
Annulla
|
||||
</button>
|
||||
<button
|
||||
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
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -162,7 +162,82 @@ export async function updateMacchina(
|
|||
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[]>(
|
||||
"SELECT id FROM macchine_parti WHERE id_padre = :id",
|
||||
{ id }
|
||||
|
|
@ -170,7 +245,7 @@ export async function deleteMacchina(id: number): Promise<boolean> {
|
|||
|
||||
if (childRows.length > 0) {
|
||||
for (const child of childRows) {
|
||||
await deleteMacchina(child.id);
|
||||
await deleteMacchina(child.id, idMagazzinoRientro, user);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue