« Back to History
mesr_tfo.php
|
20260723_000646.php
Initial Domain Snapshot
Copy Code
<?php /* mesr_tfo.php - Final fixed version - Safe session handling - cone_lookup & add_lookup AJAX endpoints - Correct column alignment for all tables - Add-row, remove-row, compute, totals, enter-navigation, and save */ error_reporting(E_ALL); ini_set('display_errors',1); require_once __DIR__ . '/modules/auth/page_acl.php'; $ctx = page_require_access('mesr_tfo'); $u = $ctx['user'] ?? null; $company_id = (int)($ctx['company_id'] ?? 0); $pdo = $ctx['pdo'] ?? null; if (!$pdo) require_once __DIR__ . '/core/db.php'; /* ------------------ AJAX HANDLERS (run BEFORE header include) ------------------ */ /* cone_lookup */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'cone_lookup') { header('Content-Type: application/json; charset=utf-8'); $den = trim($_POST['denier'] ?? ''); $col = trim($_POST['color'] ?? ''); try { $q = $pdo->prepare("SELECT cone_weight FROM mesr_matrix WHERE company_id = :cid AND matrix_type = 'cone_weight' AND LOWER(TRIM(denier)) = LOWER(TRIM(:denier)) AND LOWER(TRIM(color)) = LOWER(TRIM(:color)) LIMIT 1"); $q->execute([':cid'=>$company_id, ':denier'=>$den, ':color'=>$col]); $r = $q->fetch(PDO::FETCH_ASSOC); if ($r && $r['cone_weight'] !== null) { echo json_encode(['success'=>true,'cone_weight'=> (float)$r['cone_weight'] ]); exit; } $q2 = $pdo->prepare("SELECT cone_weight FROM mesr_matrix WHERE company_id = :cid AND matrix_type = 'cone_weight' AND (denier IS NULL OR TRIM(denier) = '') AND LOWER(TRIM(color)) = LOWER(TRIM(:color)) LIMIT 1"); $q2->execute([':cid'=>$company_id, ':color'=>$col]); $r2 = $q2->fetch(PDO::FETCH_ASSOC); if ($r2 && $r2['cone_weight'] !== null) { echo json_encode(['success'=>true,'cone_weight'=> (float)$r2['cone_weight'] ]); exit; } echo json_encode(['success'=>false,'msg'=>'not_found']); exit; } catch (Exception $e) { echo json_encode(['success'=>false,'msg'=>'error','error'=>$e->getMessage()]); exit; } } /* add_lookup */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'add_lookup') { header('Content-Type: application/json; charset=utf-8'); $type = trim($_POST['type'] ?? ''); $value = trim($_POST['value'] ?? ''); if (!$type || !$value) { echo json_encode(['success'=>false,'msg'=>'missing']); exit; } try { if ($type === 'denier') { $ins = $pdo->prepare("INSERT INTO mesr_matrix (company_id, matrix_type, denier, created_by, created_at) VALUES (:cid,'denier',:val,:uid,NOW())"); $ins->execute([':cid'=>$company_id, ':val'=>$value, ':uid'=>$u['id'] ?? null]); echo json_encode(['success'=>true]); exit; } elseif ($type === 'color') { $ins = $pdo->prepare("INSERT INTO mesr_matrix (company_id, matrix_type, color, created_by, created_at) VALUES (:cid,'color',:val,:uid,NOW())"); $ins->execute([':cid'=>$company_id, ':val'=>$value, ':uid'=>$u['id'] ?? null]); echo json_encode(['success'=>true]); exit; } else { echo json_encode(['success'=>false,'msg'=>'unknown']); exit; } } catch (Exception $e) { echo json_encode(['success'=>false,'msg'=>'db','error'=>$e->getMessage()]); exit; } } /* save handler (normal POST) */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save') { $token = $_POST['csrf_token'] ?? ''; if (!$token || !hash_equals($_SESSION['csrf_token'] ?? '', $token)) { echo "<div style='color:red;padding:10px;'>CSRF token mismatch.</div>"; exit; } $month = trim($_POST['month'] ?? ''); $location = trim($_POST['location'] ?? ''); $remark = trim($_POST['remark'] ?? ''); $sections_json = $_POST['sections_json'] ?? '[]'; if (!$month || !$location) { echo "<div style='color:orange;padding:10px;'>Please provide month and location.</div>"; exit; } $stock_type = $location . ' TFO'; try { $ins = $pdo->prepare("INSERT INTO month_end_stock_report (company_id, month, stock_type, yarns_json, remark, created_by, created_at) VALUES (:cid, :month, :st, :yj, :remark, :uid, NOW())"); $ins->execute([ ':cid'=>$company_id, ':month'=>$month, ':st'=>$stock_type, ':yj'=>$sections_json, ':remark'=>$remark, ':uid'=>$u['id'] ?? null ]); echo "<div style='color:green;padding:10px;'>Saved successfully.</div>"; } catch (PDOException $ex) { echo "<div style='color:red;padding:10px;'>DB error: " . htmlspecialchars($ex->getMessage()) . "</div>"; } exit; } /* ------------------ END AJAX ------------------ */ /* include header/footer robustly for normal page rendering */ $header_paths = [ __DIR__ . '/erp/partials/header.php', __DIR__ . '/partials/header.php', __DIR__ . '/header.php' ]; foreach ($header_paths as $hp) { if (file_exists($hp)) { require_once $hp; break; } } /* load lookups */ $deniers = []; $colors = []; try { $p = $pdo->prepare("SELECT DISTINCT TRIM(denier) AS denier FROM mesr_matrix WHERE company_id = :cid AND denier IS NOT NULL AND TRIM(denier) <> '' ORDER BY denier"); $p->execute([':cid'=>$company_id]); $deniers = array_column($p->fetchAll(PDO::FETCH_ASSOC), 'denier'); $q = $pdo->prepare("SELECT DISTINCT TRIM(color) AS color FROM mesr_matrix WHERE company_id = :cid AND color IS NOT NULL AND TRIM(color) <> '' ORDER BY color"); $q->execute([':cid'=>$company_id]); $colors = array_column($q->fetchAll(PDO::FETCH_ASSOC), 'color'); } catch (Exception $e) { // ignore } /* ensure default denier */ if (!in_array('110 Lichi', $deniers)) array_unshift($deniers, '110 Lichi'); /* CSRF token set */ if (!isset($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = bin2hex(random_bytes(16)); $csrf = $_SESSION['csrf_token']; ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>MESR - TFO Entry</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> body{font-family:Arial,Helvetica,sans-serif;background:#fff;padding:18px;} .card{border:1px solid #ddd;border-radius:6px;padding:16px;max-width:1200px;margin:8px auto;} .form-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;} .form-group{display:flex;flex-direction:column;margin-bottom:8px;} label{font-weight:600;margin-bottom:6px;} input.form-control, select.form-control, textarea.form-control{padding:8px;border:1px solid #ccc;border-radius:4px;width:100%;} .btn{display:inline-block;padding:8px 12px;border-radius:6px;border:0;cursor:pointer;background:#2d6cdf;color:#fff;} .small-btn{padding:6px 8px;font-size:12px;margin:6px 0;background:#2d6cdf;color:#fff;border-radius:5px;border:0;cursor:pointer;} .table{width:100%;border-collapse:collapse;margin-top:8px;} .table th,.table td{border:1px solid #eee;padding:8px;font-size:13px;text-align:left;vertical-align:middle;} .section{border:1px dashed #ccc;padding:10px;margin:12px 0;border-radius:6px;background:#fbfbfb;} .total{font-weight:700;} </style> </head> <body> <div class="card"> <h3>TFO Entry</h3> <form id="mainForm" method="post" action=""> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf) ?>"> <input type="hidden" name="action" value="save"> <div class="form-grid"> <div class="form-group"> <label for="month">Month</label> <input id="month" name="month" type="month" class="form-control" required> </div> <div class="form-group"> <label for="location">Location</label> <select id="location" name="location" class="form-control" required> <option value="">-- Select --</option> <option value="MST1">MST1</option> <option value="MST2A">MST2A</option> <option value="MST2B">MST2B</option> </select> </div> <div class="form-group"> <label for="remark">Remark</label> <input id="remark" name="remark" class="form-control"> </div> </div> <!-- TPM Completed Yarn --> <div class="section"> <h4>TPM Completed Yarn</h4> <button type="button" id="add_tpm" class="small-btn">+ Add Row</button> <table class="table" id="tbl_tpm"> <thead> <tr><th>#</th><th>Denier</th><th>Color</th><th>Cones</th><th>Weight (kg)</th><th>Action</th></tr> </thead> <tbody></tbody> <tfoot><tr><td colspan="3" style="text-align:right;">Total Kg</td><td id="total_tpm" class="total">0</td><td></td><td></td></tr></tfoot> </table> </div> <!-- Yarn in Winding Machine --> <div class="section"> <h4>Yarn in Winding Machine</h4> <button type="button" id="add_winding" class="small-btn">+ Add Row</button> <table class="table" id="tbl_winding"> <thead> <tr><th>#</th><th>Denier</th><th>Color</th><th>Cones</th><th>Cone Weight (kg)</th><th>Weight (kg)</th><th>Action</th></tr> </thead> <tbody></tbody> <tfoot><tr><td colspan="5" style="text-align:right;">Total Kg</td><td id="total_winding" class="total">0</td><td></td></tr></tfoot> </table> </div> <!-- Yarn Running in Machine --> <div class="section"> <h4>Yarn Running in Machine</h4> <button type="button" id="add_running" class="small-btn">+ Add Row</button> <table class="table" id="tbl_running"> <thead> <tr><th>#</th><th>Denier</th><th>Color</th><th>Cones</th><th>Weight (kg)</th><th>Action</th></tr> </thead> <tbody></tbody> <tfoot><tr><td colspan="4" style="text-align:right;">Total Kg</td><td id="total_running" class="total">0</td><td></td></tr></tfoot> </table> </div> <!-- Yarn in Closed Box --> <div class="section"> <h4>Yarn in Closed Box</h4> <button type="button" id="add_closed" class="small-btn">+ Add Row</button> <table class="table" id="tbl_closed"> <thead> <tr><th>#</th><th>Denier</th><th>Color</th><th>Boxes</th><th>Box Weight (kg)</th><th>Weight (kg)</th><th>Action</th></tr> </thead> <tbody></tbody> <tfoot><tr><td colspan="5" style="text-align:right;">Total Kg</td><td id="total_closed" class="total">0</td><td></td></tr></tfoot> </table> </div> <input type="hidden" name="sections_json" id="sections_json"> <div style="margin-top:12px;"> <button type="submit" class="btn">SAVE</button> </div> </form> </div> <script> /* Full client-side logic: add rows, compute, totals, enter-navigation, add-lookup */ const PRELOADED_DENIERS = <?= json_encode($deniers, JSON_UNESCAPED_UNICODE) ?>; const PRELOADED_COLORS = <?= json_encode($colors, JSON_UNESCAPED_UNICODE) ?>; document.addEventListener('DOMContentLoaded', function(){ function escapeHtml(s){ return (s===null||s===undefined)?'':String(s).replaceAll('&','&').replaceAll('<','<').replaceAll('>','>'); } function denierSelectHtml(){ let s = '<select class="form-control item_denier">'; PRELOADED_DENIERS.forEach(d => { s += '<option value="'+escapeHtml(d)+'">'+escapeHtml(d)+'</option>'; }); s += '</select>'; return s; } function colorSelectHtml(){ let s = '<select class="form-control item_color"><option value=""></option>'; PRELOADED_COLORS.forEach(c => { s += '<option value="'+escapeHtml(c)+'">'+escapeHtml(c)+'</option>'; }); s += '</select>'; return s; } function renumber(tbody){ Array.from(tbody.querySelectorAll('tr')).forEach((tr,i)=> { tr.children[0].textContent = i+1; }); } function addRowTo(tableId, cellsHtmlArray){ const tbody = document.querySelector('#'+tableId+' tbody'); if (!tbody) return; const tr = document.createElement('tr'); tr.innerHTML = '<td></td>' + cellsHtmlArray.join('') + '<td><button type="button" class="remove-row small-btn">X</button></td>'; tbody.appendChild(tr); renumber(tbody); tr.querySelectorAll('input,select').forEach(el=>{ el.addEventListener('input', ()=> computeRow(tr, tableId)); el.addEventListener('change', ()=> computeRow(tr, tableId)); }); tr.querySelector('.remove-row').addEventListener('click', function(){ tr.remove(); renumber(tbody); recalcTotals(); }); computeRow(tr, tableId); return tr; } function getConeWeightRemote(denier, color){ const fd = new FormData(); fd.append('action','cone_lookup'); fd.append('denier', denier); fd.append('color', color); return fetch('', { method:'POST', body: fd }).then(r=>r.json()).catch(()=>null); } function computeRow(tr, tableId){ const weightCell = tr.querySelector('.item_weight'); if (!weightCell) return; if (tableId === 'tbl_tpm') { const cones = parseFloat(tr.querySelector('.item_cones')?.value || 0); const den = tr.querySelector('.item_denier')?.value || ''; const col = tr.querySelector('.item_color')?.value || ''; if (!den || !col || cones <= 0) { weightCell.textContent = '0'; recalcTotals(); return; } getConeWeightRemote(den, col).then(j=>{ if (j && j.success) weightCell.textContent = (parseFloat(j.cone_weight || 0) * cones).toFixed(3); else weightCell.textContent = '0'; recalcTotals(); }); return; } if (tableId === 'tbl_winding') { const cones = parseFloat(tr.querySelector('.item_cones')?.value || 0); const coneWInput = tr.querySelector('.item_cone_weight'); const coneW = parseFloat(coneWInput?.value || 0); if (cones <= 0) { weightCell.textContent = '0'; recalcTotals(); return; } if (coneW > 0) { weightCell.textContent = (coneW * cones).toFixed(3); recalcTotals(); return; } const den = tr.querySelector('.item_denier')?.value || ''; const col = tr.querySelector('.item_color')?.value || ''; if (!den || !col) { weightCell.textContent = '0'; recalcTotals(); return; } getConeWeightRemote(den, col).then(j=>{ if (j && j.success) { if (coneWInput) coneWInput.value = parseFloat(j.cone_weight).toFixed(3); weightCell.textContent = (parseFloat(j.cone_weight || 0) * cones).toFixed(3); } else weightCell.textContent = '0'; recalcTotals(); }); return; } if (tableId === 'tbl_running') { const cones = parseFloat(tr.querySelector('.item_cones')?.value || 0); const manualW = parseFloat(tr.querySelector('.item_manual_weight')?.value || 0); if (manualW > 0) { weightCell.textContent = manualW.toFixed(3); recalcTotals(); return; } const den = tr.querySelector('.item_denier')?.value || ''; const col = tr.querySelector('.item_color')?.value || ''; if (!den || !col || cones <= 0) { weightCell.textContent = '0'; recalcTotals(); return; } getConeWeightRemote(den, col).then(j=>{ if (j && j.success) weightCell.textContent = (parseFloat(j.cone_weight || 0) * cones).toFixed(3); else weightCell.textContent = '0'; recalcTotals(); }); return; } if (tableId === 'tbl_closed') { const boxes = parseFloat(tr.querySelector('.item_boxes')?.value || 0); const boxW = parseFloat(tr.querySelector('.item_box_weight')?.value || 0); if (boxes <= 0 || boxW <= 0) { weightCell.textContent = '0'; recalcTotals(); return; } weightCell.textContent = (boxes * boxW).toFixed(3); recalcTotals(); return; } weightCell.textContent = '0'; recalcTotals(); } function recalcTotals(){ function sum(sel){ let s=0; document.querySelectorAll(sel).forEach(el=>{ const v=parseFloat(el.textContent||0); if(!isNaN(v)) s+=v; }); return s.toFixed(3); } document.getElementById('total_tpm').textContent = sum('#tbl_tpm .item_weight'); document.getElementById('total_winding').textContent = sum('#tbl_winding .item_weight'); document.getElementById('total_running').textContent = sum('#tbl_running .item_weight'); document.getElementById('total_closed').textContent = sum('#tbl_closed .item_weight'); } /* Add row handlers - exact alignment with headers */ document.getElementById('add_tpm').addEventListener('click', function(e){ e.preventDefault(); const cells = [ '<td>' + denierSelectHtml() + '</td>', '<td>' + colorSelectHtml() + '</td>', '<td><input type="number" min="0" class="form-control item_cones" value="0"></td>', '<td class="item_weight">0</td>' ]; addRowTo('tbl_tpm', cells); }); document.getElementById('add_winding').addEventListener('click', function(e){ e.preventDefault(); const cells = [ '<td>' + denierSelectHtml() + '</td>', '<td>' + colorSelectHtml() + '</td>', '<td><input type="number" min="0" class="form-control item_cones" value="0"></td>', '<td><input type="number" step="0.0001" min="0" class="form-control item_cone_weight" value="0"></td>', '<td class="item_weight">0</td>' ]; addRowTo('tbl_winding', cells); }); document.getElementById('add_running').addEventListener('click', function(e){ e.preventDefault(); const cells = [ '<td>' + denierSelectHtml() + '</td>', '<td>' + colorSelectHtml() + '</td>', '<td><input type="number" min="0" class="form-control item_cones" value="0"></td>', '<td><input type="number" step="0.0001" min="0" class="form-control item_manual_weight" value="0"></td>', '<td class="item_weight">0</td>' ]; addRowTo('tbl_running', cells); }); document.getElementById('add_closed').addEventListener('click', function(e){ e.preventDefault(); const cells = [ '<td>' + denierSelectHtml() + '</td>', '<td>' + colorSelectHtml() + '</td>', '<td><input type="number" min="0" class="form-control item_boxes" value="0"></td>', '<td><input type="number" step="0.0001" min="0" class="form-control item_box_weight" value="0"></td>', '<td class="item_weight">0</td>' ]; addRowTo('tbl_closed', cells); }); /* delegated add-lookup (+Add) - attach to document */ document.body.addEventListener('click', function(e){ if (e.target && e.target.classList.contains('denier-add')) { const val = prompt('Enter new denier (e.g. 110 Lichi)'); if (!val) return; if (!PRELOADED_DENIERS.includes(val)) PRELOADED_DENIERS.push(val); document.querySelectorAll('.item_denier').forEach(s=>{ const exists = Array.from(s.options).some(o=>o.value.trim().toLowerCase()===val.trim().toLowerCase()); if (!exists) { const o = document.createElement('option'); o.value = val; o.text = val; s.appendChild(o); } }); const fd = new FormData(); fd.append('action','add_lookup'); fd.append('type','denier'); fd.append('value', val); fetch('', { method:'POST', body: fd }).catch(()=>{}); } if (e.target && e.target.classList.contains('color-add')) { const val = prompt('Enter new color (e.g. Gold)'); if (!val) return; if (!PRELOADED_COLORS.includes(val)) PRELOADED_COLORS.push(val); document.querySelectorAll('.item_color').forEach(s=>{ const exists = Array.from(s.options).some(o=>o.value.trim().toLowerCase()===val.trim().toLowerCase()); if (!exists) { const o = document.createElement('option'); o.value = val; o.text = val; s.appendChild(o); } }); const fd = new FormData(); fd.append('action','add_lookup'); fd.append('type','color'); fd.append('value', val); fetch('', { method:'POST', body: fd }).catch(()=>{}); } }); /* Enter navigation */ document.addEventListener('keydown', function(e){ if (e.key !== 'Enter') return; const el = e.target; if (!el) return; if (!el.closest('table')) return; e.preventDefault(); const row = el.closest('tr'); const inputs = Array.from(row.querySelectorAll('input, select')); const idx = inputs.indexOf(el); if (idx < 0) return; if (idx < inputs.length - 1) { const next = inputs[idx+1]; if (next) next.focus(); return; } const tbody = row.parentElement; const tid = tbody.closest('table').id; if (tid === 'tbl_tpm') document.getElementById('add_tpm').click(); if (tid === 'tbl_winding') document.getElementById('add_winding').click(); if (tid === 'tbl_running') document.getElementById('add_running').click(); if (tid === 'tbl_closed') document.getElementById('add_closed').click(); setTimeout(()=>{ const newRow = tbody.lastElementChild; const first = newRow.querySelector('input, select'); if (first) first.focus(); },80); }); /* Before submit: collect table data into sections_json */ document.getElementById('mainForm').addEventListener('submit', function(e){ function collect(tblId, mapping){ const out = []; document.querySelectorAll('#'+tblId+' tbody tr').forEach(tr=>{ const obj = {}; mapping.forEach(m=>{ const sel = tr.querySelector('.item_' + m); if (m === 'weight') { obj[m] = parseFloat(tr.querySelector('.item_weight')?.textContent || 0); } else { obj[m] = sel ? sel.value : ''; } }); out.push(obj); }); return out; } const data = { tpm: collect('tbl_tpm', ['denier','color','cones','weight']), winding: collect('tbl_winding', ['denier','color','cones','cone_weight','weight']), running: collect('tbl_running', ['denier','color','cones','weight']), closed: collect('tbl_closed', ['denier','color','boxes','box_weight','weight']) }; document.getElementById('sections_json').value = JSON.stringify(data); // allow normal submit }); /* initialise one empty row each */ document.getElementById('add_tpm').click(); document.getElementById('add_winding').click(); document.getElementById('add_running').click(); document.getElementById('add_closed').click(); }); // DOMContentLoaded </script> </body> </html>