Se încarcă...
`); w.document.close(); setTimeout(() => w.print(), 500); } function showToast(msg) { let t = document.getElementById('toast-msg'); if (!t) { t = document.createElement('div'); t.id = 'toast-msg'; t.style.cssText = 'position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:#1e293b;color:#fff;padding:10px 20px;border-radius:8px;font-size:13px;z-index:9999;box-shadow:0 4px 12px rgba(0,0,0,0.4);transition:opacity 0.3s'; document.body.appendChild(t); } t.textContent = msg; t.style.opacity = '1'; setTimeout(() => t.style.opacity = '0', 2500); } // ============================================================ // RECALC TOATE // ============================================================ async function recalcToate() { const btn = event.target; btn.disabled = true; btn.textContent = '⏳ Se recalculează...'; const masiniIds = [...new Set(DB.comenzi.filter(c => c.status !== 'finalizat').map(c => c.masina_id))]; for (const mId of masiniIds) { await recalcLant(mId, today()); } await reload('comenzi'); renderComenzi(); renderGantt(); btn.disabled = false; btn.textContent = '🔄 Recalculează'; showToast('✅ Toate comenzile recalculate!'); } // ============================================================ // STAGNARE // ============================================================ function toggleStagnareAltul() { const val = document.getElementById('stagnare-motiv').value; document.getElementById('stagnare-altul-wrap').style.display = val === 'Altul' ? 'block' : 'none'; } function stagnareIgnora() { window._pendingComandaSave = false; closeModal('modal-stagnare'); // Continue saving without recording stagnare _doSaveComanda(); } async function stagnareSalveaza() { const motiv = document.getElementById('stagnare-motiv').value; if (!motiv) { alert('Selectează motivul stagnării!'); return; } const motivFinal = motiv === 'Altul' ? (document.getElementById('stagnare-altul').value.trim() || 'Altul') : motiv; const masinaId = parseInt(document.getElementById('stagnare-masina-id').value); const masinaNume = document.getElementById('stagnare-masina-nume').value; const dataStart = document.getElementById('stagnare-data-start').value; const dataEnd = document.getElementById('stagnare-data-end').value; const zile = parseFloat(document.getElementById('stagnare-zile').value); const note = document.getElementById('stagnare-note').value; const durataOre = Math.round(zile * 24 * 10) / 10; await sb.from('opriri_istoric').insert({ firma_id: CURRENT_FIRMA_ID, masina_id: masinaId, masina_nume: masinaNume, motiv: motivFinal, data_start: dataStart + 'T00:00:00', data_end: dataEnd + 'T00:00:00', durata_ore: durataOre, buc_la_oprire: 0, note: note }); closeModal('modal-stagnare'); showToast(`✅ Stagnare salvată: ${zile} zile — ${motivFinal}`); window._pendingComandaSave = false; await _doSaveComanda(); } // ============================================================ // MENTENANTA // ============================================================ async function loadMentenanta() { const [tipuriRes, istoricRes] = await Promise.all([ sb.from('mentenanta_tipuri').select('*').order('masina_id'), sb.from('mentenanta_istoric').select('*').order('data_interventie', {ascending: false}) ]); DB.ment_tipuri = tipuriRes.data || []; DB.ment_istoric = istoricRes.data || []; } function calcOreActuale(masinaId) { // Sum hours from productie_zilnica for this machine // Each day: ture * ore_tura from masina const masina = getMasinaById(masinaId); if (!masina) return 0; const orePerZi = (masina.ture || 1) * (masina.ore_tura || 8); const zileLucrate = DB.productie.filter(p => p.masina_id == masinaId && p.buc_injectate > 0); // Count unique dates const dateLucrate = new Set(zileLucrate.map(p => p.data_productie?.split('T')[0])); return dateLucrate.size * orePerZi; } async function renderMentenanta() { if (!DB.ment_tipuri) await loadMentenanta(); // Summary cards per masina const summaryEl = document.getElementById('ment-summary'); if (summaryEl) { summaryEl.innerHTML = DB.masini.map(m => { const tipuri = DB.ment_tipuri.filter(t => t.masina_id == m.id); const oreActuale = calcOreActuale(m.id); const urgente = tipuri.filter(t => { const oreRamase = t.interval_ore - (oreActuale - (t.ore_la_ultima || 0)); return oreRamase <= 0; }).length; const aproape = tipuri.filter(t => { const oreRamase = t.interval_ore - (oreActuale - (t.ore_la_ultima || 0)); return oreRamase > 0 && oreRamase <= t.interval_ore * 0.2; }).length; const statusCol = urgente > 0 ? 'var(--red)' : aproape > 0 ? 'var(--orange)' : 'var(--green)'; const statusTxt = urgente > 0 ? `🔴 ${urgente} depășit` : aproape > 0 ? `⚠️ ${aproape} se apropie` : '✅ Ok'; return `
${m.nume}
Ore funcționare: ${oreActuale}h
${statusTxt}
${tipuri.length} intervenții configurate
`; }).join(''); } // Tipuri table const tbody = document.getElementById('ment-tipuri-tbody'); if (!tbody) return; if (!DB.ment_tipuri.length) { tbody.innerHTML = 'Nicio intervenție configurată. Apasă + Adaugă Intervenție.'; } else { tbody.innerHTML = DB.ment_tipuri.map(t => { const m = getMasinaById(t.masina_id); const oreActuale = calcOreActuale(t.masina_id); const oreRamase = t.interval_ore - (oreActuale - (t.ore_la_ultima || 0)); const pctRamas = Math.max(0, oreRamase / t.interval_ore * 100); const isUrgent = oreRamase <= 0; const isAproape = oreRamase > 0 && pctRamas <= 20; const statusHtml = isUrgent ? '🔴 DEPĂȘIT' : isAproape ? '⚠️ Se apropie' : '✅ Ok'; const barCol = isUrgent ? 'var(--red)' : isAproape ? 'var(--orange)' : 'var(--green)'; return ` ${m ? ` ${m.nume}` : '—'} ${t.denumire} ${t.interval_ore}h ${oreActuale}h
${isUrgent ? '+'+Math.abs(Math.round(oreRamase))+'h' : Math.round(oreRamase)+'h'}
${statusHtml} ${t.data_ultima ? fmtDate(t.data_ultima) : '—'}
`; }).join(''); } // Istoric table const istoricTbody = document.getElementById('ment-istoric-tbody'); if (!istoricTbody) return; if (!DB.ment_istoric.length) { istoricTbody.innerHTML = 'Nicio intervenție înregistrată încă.'; } else { istoricTbody.innerHTML = DB.ment_istoric.map(i => { const m = getMasinaById(i.masina_id); return ` ${fmtDate(i.data_interventie)} ${m ? ` ${i.masina_nume||m.nume}` : i.masina_nume||'—'} ${i.denumire_interventie||'—'} ${i.ore_la_interventie||'—'}h ${i.efectuat_de||'—'} ${i.cost ? parseFloat(i.cost).toFixed(2)+' lei' : '—'} ${i.note||'—'} `; }).join(''); } } function openMentTipModal(id) { clearForm('modal-ment-tip', ['ment-tip-id','ment-denumire','ment-interval','ment-ore-ultima','ment-data-ultima','ment-nota']); document.getElementById('ment-ore-ultima').value = 0; // Populate masina select const sel = document.getElementById('ment-masina'); sel.innerHTML = DB.masini.map(m => ``).join(''); document.getElementById('ment-tip-title').textContent = '+ Intervenție Programată'; if (id) { const t = DB.ment_tipuri.find(x => x.id === id); if (t) { document.getElementById('ment-tip-title').textContent = '✏️ Editare Intervenție'; document.getElementById('ment-tip-id').value = t.id; document.getElementById('ment-masina').value = t.masina_id; document.getElementById('ment-denumire').value = t.denumire; document.getElementById('ment-interval').value = t.interval_ore; document.getElementById('ment-ore-ultima').value = t.ore_la_ultima || 0; document.getElementById('ment-data-ultima').value = t.data_ultima || ''; document.getElementById('ment-nota').value = t.nota || ''; } } openModal('modal-ment-tip'); } function editMentTip(id) { openMentTipModal(id); } async function saveMentTip() { const denumire = document.getElementById('ment-denumire').value.trim(); const interval = parseFloat(document.getElementById('ment-interval').value); const masinaId = parseInt(document.getElementById('ment-masina').value); if (!denumire || !interval || !masinaId) { alert('Completați câmpurile obligatorii!'); return; } const masina = getMasinaById(masinaId); const data = { masina_id: masinaId, denumire, interval_ore: interval, ore_la_ultima: parseFloat(document.getElementById('ment-ore-ultima').value) || 0, data_ultima: document.getElementById('ment-data-ultima').value || null, nota: document.getElementById('ment-nota').value }; const id = document.getElementById('ment-tip-id').value; if (id) await sb.from('mentenanta_tipuri').update(data).eq('id', id); else await sb.from('mentenanta_tipuri').insert(data); closeModal('modal-ment-tip'); await loadMentenanta(); renderMentenanta(); showToast('✅ Intervenție salvată!'); } async function deleteMentTip(id) { if (!confirm('Șterge această intervenție programată?')) return; await sb.from('mentenanta_tipuri').delete().eq('id', id); await loadMentenanta(); renderMentenanta(); } function openMentEfectuat(tipId) { const t = DB.ment_tipuri.find(x => x.id === tipId); if (!t) return; const m = getMasinaById(t.masina_id); const oreActuale = calcOreActuale(t.masina_id); document.getElementById('ment-ef-tip-id').value = tipId; document.getElementById('ment-ef-data').value = today(); document.getElementById('ment-ef-operator').value = ''; document.getElementById('ment-ef-cost').value = ''; document.getElementById('ment-ef-note').value = ''; document.getElementById('ment-ef-info').innerHTML = ` ${m?.nume || '—'} — ${t.denumire}
Ore actuale mașină: ${oreActuale}h `; openModal('modal-ment-efectuat'); } async function saveMentEfectuat() { const tipId = parseInt(document.getElementById('ment-ef-tip-id').value); const data = document.getElementById('ment-ef-data').value; if (!data) { alert('Data este obligatorie!'); return; } const t = DB.ment_tipuri.find(x => x.id === tipId); if (!t) return; const m = getMasinaById(t.masina_id); const oreActuale = calcOreActuale(t.masina_id); // Save to istoric await sb.from('mentenanta_istoric').insert({ tip_id: tipId, masina_id: t.masina_id, masina_nume: m?.nume || '', denumire_interventie: t.denumire, data_interventie: data, ore_la_interventie: oreActuale, efectuat_de: document.getElementById('ment-ef-operator').value, cost: parseFloat(document.getElementById('ment-ef-cost').value) || null, note: document.getElementById('ment-ef-note').value }); // Reset ore_la_ultima to current hours (reset counter) await sb.from('mentenanta_tipuri').update({ ore_la_ultima: oreActuale, data_ultima: data }).eq('id', tipId); closeModal('modal-ment-efectuat'); await loadMentenanta(); renderMentenanta(); showToast('✅ Intervenție înregistrată! Contorul a fost resetat.'); } // ============================================================ // SPLIT & ANULARE PARTIALA // ============================================================ function openSplit(id) { const c = DB.comenzi.find(x => x.id === id); if (!c) return; document.getElementById('split-comanda-id').value = id; document.getElementById('split-buc-finalizate').value = c.buc_produse || 0; document.getElementById('split-actiune').value = 'noua'; const bucProduse = c.buc_produse || 0; const bucRamase = c.buc_totale - bucProduse; document.getElementById('split-info').innerHTML = `
📋 ${c.cod_produs} — ${c.masina_nume}
Total comandat: ${c.buc_totale.toLocaleString()} buc
Produse până acum: ${bucProduse.toLocaleString()} buc
Rămase: ${bucRamase.toLocaleString()} buc
`; document.getElementById('split-buc-finalizate').oninput = function() { const v = parseInt(this.value) || 0; const rest = c.buc_totale - v; document.getElementById('split-rest-info').textContent = rest > 0 ? `Vor rămâne ${rest.toLocaleString()} bucăți pentru a doua parte` : rest === 0 ? 'Toată comanda se finalizează' : '⚠️ Valoare prea mare!'; }; openModal('modal-split'); } async function executaSplit() { const id = parseInt(document.getElementById('split-comanda-id').value); const bucFinalizate = parseInt(document.getElementById('split-buc-finalizate').value); const actiune = document.getElementById('split-actiune').value; const c = DB.comenzi.find(x => x.id === id); if (!c || !bucFinalizate || bucFinalizate <= 0 || bucFinalizate > c.buc_totale) { alert('Valoare invalidă!'); return; } const bucRest = c.buc_totale - bucFinalizate; // 1. Update current command to finalizat with bucFinalizate await sb.from('comenzi').update({ buc_totale: bucFinalizate, buc_produse: bucFinalizate, status: 'finalizat', stop_data_estimata: today(), updated_at: new Date().toISOString() }).eq('id', id); // 2. Create new command for rest (if not anulat) if (actiune === 'noua' && bucRest > 0) { const r = getRetetaById(c.reteta_id); const m = getMasinaById(c.masina_id); const res = r && m ? calcStopData(today(), bucRest, r, m) : null; await sb.from('comenzi').insert({ firma_id: CURRENT_FIRMA_ID, reteta_id: c.reteta_id, cod_produs: c.cod_produs, descriere: c.descriere, client: c.client, masina_id: c.masina_id, masina_nume: c.masina_nume, buc_totale: bucRest, buc_produse: 0, start_data: today(), stop_data: res?.stopData, stop_data_estimata: res?.stopData, ore_estimata: res?.ore, prioritate: c.prioritate || 5, note: `Split din comanda #${id}. ${c.note || ''}`.trim(), status: 'activ', data_fixa: false, updated_at: new Date().toISOString() }); showToast(`✅ Split efectuat: ${bucFinalizate.toLocaleString()} finalizate + ${bucRest.toLocaleString()} buc comandă nouă`); } else { showToast(`✅ Comanda finalizată cu ${bucFinalizate.toLocaleString()} bucăți. Restul de ${bucRest.toLocaleString()} anulat.`); } closeModal('modal-split'); await reload('comenzi'); renderComenzi(); renderGantt(); } function openAnularePartiala(id) { const c = DB.comenzi.find(x => x.id === id); if (!c) return; document.getElementById('anulare-comanda-id').value = id; document.getElementById('anulare-buc-noi').value = c.buc_totale; document.getElementById('anulare-motiv').value = ''; document.getElementById('anulare-info').innerHTML = `
📋 ${c.cod_produs} — ${c.masina_nume}
Cantitate curentă: ${c.buc_totale.toLocaleString()} buc
Produse: ${(c.buc_produse||0).toLocaleString()} buc
Noua cantitate trebuie să fie ≥ bucățile deja produse (${(c.buc_produse||0).toLocaleString()})
`; document.getElementById('anulare-buc-noi').oninput = function() { const v = parseInt(this.value) || 0; const diff = c.buc_totale - v; const el = document.getElementById('anulare-diff-info'); if (v < (c.buc_produse||0)) { el.style.color = 'var(--red)'; el.textContent = `⚠️ Nu poate fi mai mic decât bucățile produse (${(c.buc_produse||0).toLocaleString()})`; } else if (diff > 0) { el.style.color = 'var(--orange)'; el.textContent = `Se anulează ${diff.toLocaleString()} bucăți. Materialul se eliberează din stoc.`; } else if (diff < 0) { el.style.color = 'var(--green)'; el.textContent = `Se adaugă ${Math.abs(diff).toLocaleString()} bucăți la comandă.`; } else { el.textContent = 'Cantitate neschimbată.'; } }; openModal('modal-anulare'); } async function executaAnularePartiala() { const id = parseInt(document.getElementById('anulare-comanda-id').value); const bucNoi = parseInt(document.getElementById('anulare-buc-noi').value); const motiv = document.getElementById('anulare-motiv').value; const c = DB.comenzi.find(x => x.id === id); if (!c || !bucNoi || bucNoi < (c.buc_produse||0)) { alert('Cantitate invalidă — nu poate fi mai mică decât bucățile produse!'); return; } const r = getRetetaById(c.reteta_id); const m = getMasinaById(c.masina_id); const bucRamase = bucNoi - (c.buc_produse||0); const isFinalizat = bucNoi <= (c.buc_produse||0); const res = !isFinalizat && r && m ? calcStopData(today(), bucRamase, r, m) : null; await sb.from('comenzi').update({ buc_totale: bucNoi, status: isFinalizat ? 'finalizat' : c.status, stop_data_estimata: isFinalizat ? today() : (res?.stopData || c.stop_data_estimata), note: motiv ? `${c.note ? c.note+' | ' : ''}Anulare parțială: ${motiv}` : c.note, updated_at: new Date().toISOString() }).eq('id', id); const diff = c.buc_totale - bucNoi; if (diff > 0) { showToast(`✅ Comandă redusă cu ${diff.toLocaleString()} buc. Material eliberat în stoc.`); } else if (diff < 0) { showToast(`✅ Comandă mărită cu ${Math.abs(diff).toLocaleString()} buc.`); } closeModal('modal-anulare'); await reload('comenzi'); await recalcLant(c.masina_id, today()); await reload('comenzi'); renderComenzi(); renderGantt(); } // ============================================================ // RESET COMPLET // ============================================================ const RESET_PAROLA = 'Fanton2026!'; async function executaReset() { const parola = document.getElementById('reset-parola').value; const confirmare = document.getElementById('reset-confirmare').value; const statusEl = document.getElementById('reset-status'); if (parola !== RESET_PAROLA) { statusEl.style.color = '#dc2626'; statusEl.textContent = '❌ Parolă incorectă!'; return; } if (confirmare !== 'RESET') { statusEl.style.color = '#dc2626'; statusEl.textContent = '❌ Scrie exact RESET cu majuscule!'; return; } statusEl.style.color = 'var(--orange)'; statusEl.textContent = '⏳ Se creează backup...'; try { const [c1, c2, c3, c4] = await Promise.all([ sb.from('comenzi').select('*'), sb.from('productie_zilnica').select('*'), sb.from('intrari_material').select('*'), sb.from('opriri_istoric').select('*'), ]); const backup = { export_date: new Date().toISOString(), comenzi: c1.data || [], productie_zilnica: c2.data || [], intrari_material: c3.data || [], opriri_istoric: c4.data || [], }; const blob = new Blob([JSON.stringify(backup, null, 2)], {type: 'application/json'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'injectplan_backup_' + new Date().toISOString().split('T')[0] + '.json'; a.click(); URL.revokeObjectURL(url); statusEl.textContent = '⏳ Se șterg datele...'; await new Promise(r => setTimeout(r, 1200)); // Delete operational data - NOT retete, materiale, masini await sb.from('productie_zilnica').delete().neq('id', 0); await sb.from('opriri_istoric').delete().neq('id', 0); await sb.from('intrari_material').delete().neq('id', 0); await sb.from('comenzi').delete().neq('id', 0); // Reset reglaje too // await sb.from('reglaje').delete().neq('id', 0); // uncomment if needed statusEl.style.color = '#15803d'; statusEl.textContent = '✅ Reset complet! Se reîncarcă...'; await new Promise(r => setTimeout(r, 1500)); closeModal('modal-reset'); window.location.reload(); } catch(err) { statusEl.style.color = '#dc2626'; statusEl.textContent = '❌ Eroare: ' + err.message; } } // ============================================================ // AUTO UPDATE STATUSURI // ============================================================ async function autoUpdateStatusuri() { const tdayStr = today(); // 1. Activate planificat commands that have started const deActualizat = DB.comenzi.filter(c => c.status === 'planificat' && c.start_data && c.start_data.split('T')[0] <= tdayStr ); for (const c of deActualizat) { await sb.from('comenzi').update({ status: 'activ', updated_at: new Date().toISOString() }).eq('id', c.id); c.status = 'activ'; } // 2. No automatic DB recalc - Gantt handles visual extension // DB is updated only when operator saves production or reschedules } // ============================================================ // START // ============================================================ init();