« Back to History
month_end_stock_entry.php
|
20260723_000646.php
Initial Domain Snapshot
Copy Code
<?php /* month_end_stock_entry.php Same as previous final file but with robust Save request handling: - posts to window.location.href - disables Save button while sending - shows server response in notice area */ error_reporting(E_ALL); ini_set('display_errors',1); require_once __DIR__ . '/modules/auth/page_acl.php'; $ctx = page_require_access('month_end_stock_entry'); $u = $ctx['user'] ?? null; $company_id = (int)($ctx['company_id'] ?? 0); $pdo = $ctx['pdo'] ?? null; if (!$pdo) { require_once __DIR__ . '/core/db.php'; } $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); /* CSRF */ $csrf = $_SESSION['csrf_token'] ?? bin2hex(random_bytes(16)); $_SESSION['csrf_token'] = $csrf; /* OPTIONS */ $OPTIONS = [ 'location' => ['MST1','MST2A','MST2B','MST2-TOP','ZARI MACHINE'], 'machine_groups' => [ 'MST1' => ['Bobin Machine','Warping Machine','Palti Machine'], 'MST2A' => ['Bobin Machine'], 'MST2B' => ['Bobin Machine'], 'MST2-TOP' => ['Warping Machine','Palti Machine','TFO'], 'ZARI MACHINE' => ['Zari Machine'] ], 'machine_types' => [ 'Bobin Machine' => ['In Bobin','Open Box','Closed Box','In Machine'], 'Warping Machine' => ['Beam Stock','Beam Quality','Yarn in Machine','Yarn in Closed Box','Yarn in Open Box'], 'Palti Machine' => ['Total Zari Roll'], 'TFO' => ['TFO Standard Section'], 'Zari Machine' => [] ] ]; /* Preload lookups */ $deniers = []; $colors = []; try { $q = $pdo->prepare("SELECT value FROM denier_lookup WHERE company_id = :cid ORDER BY value"); $q->execute([':cid'=>$company_id]); $deniers = array_column($q->fetchAll(PDO::FETCH_ASSOC), 'value'); $q2 = $pdo->prepare("SELECT value FROM color_lookup WHERE company_id = :cid ORDER BY value"); $q2->execute([':cid'=>$company_id]); $colors = array_column($q2->fetchAll(PDO::FETCH_ASSOC), 'value'); } catch (Exception $e) {} /* -------------------- beam fetch -------------------- */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'fetch') { header('Content-Type: application/json; charset=utf-8'); $bid = (int)($_POST['beam_quality_id'] ?? 0); $total_meter = (float)($_POST['total_meter'] ?? 0); if (!$bid || !($total_meter > 0)) { echo json_encode(['success'=>false,'msg'=>'Missing beam_quality_id or total_meter']); exit; } $sql = "SELECT y.id, y.seq_no, y.stock_type, y.yarn_type, y.denier, y.color, y.total_tar, y.tpm, y.wastage FROM beam_quality_yarns y JOIN beam_qualities q ON q.id = y.beam_quality_id WHERE y.beam_quality_id = :bid AND q.company_id = :cid ORDER BY y.seq_no ASC, y.id ASC"; $st = $pdo->prepare($sql); $st->execute([':bid'=>$bid, ':cid'=>$company_id]); $rows = $st->fetchAll(PDO::FETCH_ASSOC); if (!$rows) { echo json_encode(['success'=>false,'msg'=>'No yarn rows found for selected quality.']); exit; } // optional denier corrections $map = []; $map_short = []; try { $st2 = $pdo->prepare("SELECT stock_type, yarn_type, denier, new_denier FROM yarn_weight_data WHERE 1"); $st2->execute(); foreach ($st2->fetchAll(PDO::FETCH_ASSOC) as $r) { $key = strtolower(trim($r['stock_type'])) . '||' . strtolower(trim($r['yarn_type'])) . '||' . trim($r['denier']); $map[$key] = $r['new_denier']; $k2 = strtolower(trim($r['stock_type'])) . '||' . strtolower(trim($r['yarn_type'])); if (!isset($map_short[$k2])) $map_short[$k2] = $r['new_denier']; } } catch (Exception $e) {} $out = []; foreach ($rows as $r) { $k = strtolower(trim($r['stock_type'])) . '||' . strtolower(trim($r['yarn_type'])) . '||' . trim($r['denier']); $k2 = strtolower(trim($r['stock_type'])) . '||' . strtolower(trim($r['yarn_type'])); $new_denier = $map[$k] ?? $map_short[$k2] ?? (float)$r['denier']; $meters_for_yarn = $total_meter * (float)$r['total_tar']; $weight_kg = ($meters_for_yarn * (float)$new_denier) / 9000000.0; $out[] = [ 'id' => (int)$r['id'], 'seq_no' => (int)$r['seq_no'], 'stock_type' => $r['stock_type'], 'yarn_type' => $r['yarn_type'], 'denier' => $r['denier'], 'color' => $r['color'], 'total_tar' => (string)$r['total_tar'], 'tpm' => (string)$r['tpm'], 'wastage' => (string)$r['wastage'], 'new_denier' => (float)$new_denier, 'meters' => round($meters_for_yarn, 3), 'weight_kg' => round($weight_kg, 6), ]; } echo json_encode(['success'=>true,'data'=>$out], JSON_UNESCAPED_UNICODE); exit; } /* -------------------- cone lookup / add lookup / save (same logic as before) -------------------- */ /* cone_lookup */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'cone_lookup') { header('Content-Type: application/json; charset=utf-8'); $color = trim($_POST['color'] ?? ''); $denier = trim($_POST['denier'] ?? ''); if (!$color || !$denier) { echo json_encode(['success'=>false,'msg'=>'Missing color/denier']); exit; } try { $q = $pdo->prepare("SELECT cone_weight FROM cone_weight_matrix WHERE company_id = :cid AND LOWER(color)=LOWER(:color) AND LOWER(denier)=LOWER(:denier) LIMIT 1"); $q->execute([':cid'=>$company_id, ':color'=>$color, ':denier'=>$denier]); $r = $q->fetch(PDO::FETCH_ASSOC); if ($r && $r['cone_weight']) { echo json_encode(['success'=>true,'cone_weight'=> (float)$r['cone_weight'] ]); exit; } $q2 = $pdo->prepare("SELECT cone_weight FROM cone_weight_matrix WHERE company_id = :cid AND LOWER(color)=LOWER(:color) LIMIT 1"); $q2->execute([':cid'=>$company_id, ':color'=>$color]); $r2 = $q2->fetch(PDO::FETCH_ASSOC); if ($r2 && $r2['cone_weight']) { echo json_encode(['success'=>true,'cone_weight'=> (float)$r2['cone_weight'] ]); exit; } } catch (Exception $e) {} echo json_encode(['success'=>false,'msg'=>'No cone weight found']); 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 type/value']); exit; } try { if ($type === 'denier') { $ins = $pdo->prepare("INSERT INTO denier_lookup (company_id, value) VALUES (:cid, :val)"); $ins->execute([':cid'=>$company_id, ':val'=>$value]); } elseif ($type === 'color') { $ins = $pdo->prepare("INSERT INTO color_lookup (company_id, value) VALUES (:cid, :val)"); $ins->execute([':cid'=>$company_id, ':val'=>$value]); } else { echo json_encode(['success'=>false,'msg'=>'Unknown lookup type']); exit; } echo json_encode(['success'=>true,'msg'=>'Saved']); exit; } catch (Exception $e) { echo json_encode(['success'=>false,'msg'=>'DB insert failed']); exit; } } /* save: validate JSON and insert; on duplicate merge (unchanged) */ 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'] ?? ''); $stock_type = trim($_POST['stock_type_h'] ?? $_POST['stock_type'] ?? ''); $remark = trim($_POST['remark_h'] ?? $_POST['remark'] ?? ''); $raw_yarns_json = $_POST['yarns_json'] ?? ''; if (!$month || !$stock_type) { echo "<div style='color:orange;padding:10px;'>Please fill month and stock type.</div>"; exit; } if ($raw_yarns_json === '') { $final_json_string = json_encode([], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); } else { $dec = json_decode($raw_yarns_json, true); if ($dec === null && json_last_error() !== JSON_ERROR_NONE) { echo "<div style='color:orange;padding:10px;'>Invalid JSON in yarns_json: " . htmlspecialchars(json_last_error_msg()) . "</div>"; exit; } $final_json_string = json_encode($dec, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); } $payload = json_decode($final_json_string, true); $beam_quality_id = $payload['beam_quality_id'] ?? null; $total_meter = $payload['total_meter'] ?? null; try { $ins = $pdo->prepare(" INSERT INTO month_end_stock_report (company_id, month, stock_type, beam_quality_id, total_meter, yarns_json, remark, created_by) VALUES (:cid, :month, :stock_type, :bqid, :tm, :yj, :remark, :created_by) "); $ins->bindValue(':cid', $company_id, PDO::PARAM_INT); $ins->bindValue(':month', $month, PDO::PARAM_STR); $ins->bindValue(':stock_type', $stock_type, PDO::PARAM_STR); if ($beam_quality_id !== null && $beam_quality_id !== '') $ins->bindValue(':bqid', $beam_quality_id, PDO::PARAM_INT); else $ins->bindValue(':bqid', null, PDO::PARAM_NULL); $ins->bindValue(':tm', $total_meter !== null && $total_meter !== '' ? $total_meter : null); $ins->bindValue(':yj', $final_json_string, PDO::PARAM_STR); $ins->bindValue(':remark', $remark, PDO::PARAM_STR); $ins->bindValue(':created_by', $u['id'] ?? null, PDO::PARAM_INT); $ins->execute(); echo "<div style='color:green;padding:10px;'>Entry saved successfully (new row inserted).</div>"; exit; } catch (PDOException $ex) { $msg = $ex->getMessage(); $sqlState = $ex->getCode(); if (strpos($msg, 'Duplicate') !== false || $sqlState === '23000') { try { if ($beam_quality_id) { $find = $pdo->prepare("SELECT id, yarns_json FROM month_end_stock_report WHERE company_id = :cid AND month = :month AND stock_type = :st AND (beam_quality_id = :bq OR (beam_quality_id IS NULL AND :bq IS NULL)) LIMIT 1"); $find->execute([':cid'=>$company_id, ':month'=>$month, ':st'=>$stock_type, ':bq'=>$beam_quality_id]); } else { $find = $pdo->prepare("SELECT id, yarns_json FROM month_end_stock_report WHERE company_id = :cid AND month = :month AND stock_type = :st LIMIT 1"); $find->execute([':cid'=>$company_id, ':month'=>$month, ':st'=>$stock_type]); } $existing = $find->fetch(PDO::FETCH_ASSOC); if ($existing) { $ex_id = (int)$existing['id']; $existing_json = $existing['yarns_json'] ?: '[]'; $dec_existing = json_decode($existing_json, true); if ($dec_existing === null) $dec_existing = []; if (isset($dec_existing['entries']) && is_array($dec_existing['entries'])) { $merged = $dec_existing; } else { $merged = ['entries' => [$dec_existing]]; } $newEntry = json_decode($final_json_string, true); $newEntry['__merged_at'] = date('c'); $newEntry['__merged_by'] = $u['id'] ?? null; $merged['entries'][] = $newEntry; $upd = $pdo->prepare("UPDATE month_end_stock_report SET yarns_json = :yj, remark = :remark, created_by = :created_by WHERE id = :id"); $upd->execute([ ':yj' => json_encode($merged, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES), ':remark' => $remark, ':created_by' => $u['id'] ?? null, ':id' => $ex_id ]); echo "<div style='color:green;padding:10px;'>Duplicate detected: merged new entry into existing row (id={$ex_id}).</div>"; exit; } else { echo "<div style='color:red;padding:10px;'>Duplicate error but existing row not found. DB message: " . htmlspecialchars($msg) . "</div>"; exit; } } catch (Exception $e2) { echo "<div style='color:red;padding:10px;'>Merge failed: " . htmlspecialchars($e2->getMessage()) . "</div>"; exit; } } else { echo "<div style='color:red;padding:10px;'>DB error: " . htmlspecialchars($msg) . "</div>"; exit; } } } /* -------------------- HTML/UI -------------------- */ ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Month-end Stock Entry — Final</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:14px;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;} .section{border:1px dashed #ccc;padding:10px;margin:8px 0;border-radius:6px;background:#fafafa;} .btn{display:inline-block;padding:7px 10px;border-radius:6px;border:0;cursor:pointer;} .btn-primary{background:#2d6cdf;color:#fff;} .btn-success{background:#2da54b;color:#fff;} .small-btn{padding:6px 8px;font-size:12px;margin-left:6px;} .table{width:100%;border-collapse:collapse;margin-top:8px;} .table th,.table td{border:1px solid #eee;padding:6px;font-size:13px;} .mt-2{margin-top:12px;} .notice{padding:8px;border-radius:6px;margin-bottom:10px;background:#f7f7f7;border:1px solid #eee;} #beamFetchMsg{font-size:13px;color:#666;margin-left:8px;} </style> </head> <body> <div class="card"> <h3>Month-end Stock Entry — Final</h3> <form id="mainForm" method="post" action=""> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf) ?>"> <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"> <option value="">-- Select Location --</option> <?php foreach($OPTIONS['location'] as $loc): ?> <option value="<?= htmlspecialchars($loc) ?>"><?= htmlspecialchars($loc) ?></option> <?php endforeach; ?> </select> </div> <div class="form-group"> <label for="machine_group">Machine Group</label> <select id="machine_group" name="machine_group" class="form-control"><option value="">-- Select --</option></select> </div> <div class="form-group"> <label for="machine_type">Machine Type</label> <select id="machine_type" name="machine_type" class="form-control"><option value="">-- Select --</option></select> </div> <div class="form-group"> <label for="stock_type">Stock Type</label> <select id="stock_type" name="stock_type" class="form-control" required> <option value="">-- Select --</option> <option>Stock in installed beam</option> <option>Stock in beam stock</option> <option>Stock in open box</option> <option>Stock in bobin</option> </select> </div> <div class="form-group"> <label for="beam_quality_id">Beam Quality (if beam)</label> <select id="beam_quality_id" name="beam_quality_id" class="form-control"> <option value="">-- Select Quality --</option> <?php $stmt = $pdo->prepare("SELECT id,name FROM beam_qualities WHERE company_id = :cid ORDER BY name"); $stmt->execute([':cid'=>$company_id]); foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $q): ?> <option value="<?= (int)$q['id'] ?>"><?= htmlspecialchars($q['name']) ?></option> <?php endforeach; ?> </select> </div> <div class="form-group"> <label for="total_meter">Total Meter (for beam)</label> <input id="total_meter" name="total_meter" type="number" step="0.001" class="form-control"> </div> <div class="form-group"> <label for="remark">Remark</label> <input id="remark" name="remark" class="form-control"> </div> </div> <div class="mt-2"> <button id="btnPrepare" type="button" class="btn btn-primary">Prepare Details</button> <input type="hidden" name="action" value="save" id="save_action"> <input type="hidden" name="yarns_json" id="save_yarns_json"> <input type="hidden" name="month" id="save_month"> <input type="hidden" name="location_h" id="save_location"> <input type="hidden" name="machine_group_h" id="save_machine_group"> <input type="hidden" name="machine_type_h" id="save_machine_type"> <input type="hidden" name="stock_type_h" id="save_stock_type"> <input type="hidden" name="beam_quality_id_h" id="save_beam_quality_id"> <input type="hidden" name="total_meter_h" id="save_total_meter"> <input type="hidden" name="remark_h" id="save_remark"> <button id="btnSave" type="button" class="btn btn-success">Save Entry</button> </div> </form> <div id="detailArea" style="margin-top:14px; display:none;"> <div id="beamArea" style="display:none;"> <h4>Yarn Composition & Calculations (Beam)</h4> <div style="margin-bottom:8px;"> <button id="btnFetchBeam" type="button" class="btn btn-primary small-btn">Fetch Beam Rows</button> <span id="beamFetchMsg"></span> </div> <table class="table" id="beamTable"><thead><tr> <th>#</th><th>Stock Type</th><th>Yarn Type</th><th>Color</th><th>Orig Denier</th><th>New Denier</th> <th>Total Tar</th><th>Meters</th><th>Weight (kg)</th><th>TPM</th><th>Wastage</th> </tr></thead><tbody></tbody></table> </div> <div id="otherSections" style="display:none;"> <div class="section"> <strong>Other sections</strong> <div>Bobin / Warping / Palti UI omitted for brevity.</div> </div> </div> </div> </div> <script> const OPTIONS = <?= json_encode($OPTIONS, JSON_UNESCAPED_UNICODE) ?>; const PRELOADED_DENIERS = <?= json_encode($deniers, JSON_UNESCAPED_UNICODE) ?>; const PRELOADED_COLORS = <?= json_encode($colors, JSON_UNESCAPED_UNICODE) ?>; (function(){ function $(id){ return document.getElementById(id); } function escapeHtml(s){ return (s===null||s===undefined)?'':String(s).replaceAll('&','&').replaceAll('<','<').replaceAll('>','>'); } function createOption(val){ const o = document.createElement('option'); o.value = val; o.textContent = val; return o; } document.addEventListener('DOMContentLoaded', function(){ const locationEl = $('location'), mgEl = $('machine_group'), mtEl = $('machine_type'); const btnPrepare = $('btnPrepare'), btnSave = $('btnSave'), btnFetchBeam = $('btnFetchBeam'); const detailArea = $('detailArea'), beamArea = $('beamArea'), otherSections = $('otherSections'); const beamTableBody = document.querySelector('#beamTable tbody'); const beamFetchMsg = $('beamFetchMsg'); function clearSelect(sel, placeholder){ sel.innerHTML = ''; sel.appendChild(new Option(placeholder||'-- Select --','')); } function getKeyInsensitive(obj, key) { if (!obj) return undefined; if (Object.prototype.hasOwnProperty.call(obj, key)) return obj[key]; const keys = Object.keys(obj); const found = keys.find(k => k.toLowerCase() === String(key).toLowerCase()); return found ? obj[found] : undefined; } locationEl.addEventListener('change', function(){ clearSelect(mgEl,'-- Select Group --'); clearSelect(mtEl,'-- Select Machine Type --'); const groups = getKeyInsensitive(OPTIONS.machine_groups, this.value); if (Array.isArray(groups)) groups.forEach(g => mgEl.appendChild(createOption(g))); }); mgEl.addEventListener('change', function(){ clearSelect(mtEl,'-- Select Machine Type --'); const types = getKeyInsensitive(OPTIONS.machine_types, this.value); if (Array.isArray(types)) types.forEach(t => mtEl.appendChild(createOption(t))); }); function renderBeamRows(rows){ beamTableBody.innerHTML = ''; rows.forEach((r,i) => { const tr = document.createElement('tr'); tr.innerHTML = '<td>' + (i+1) + '</td>' + '<td>' + escapeHtml(r.stock_type) + '</td>' + '<td>' + escapeHtml(r.yarn_type) + '</td>' + '<td>' + escapeHtml(r.color) + '</td>' + '<td>' + escapeHtml(String(r.denier)) + '</td>' + '<td>' + escapeHtml(String(r.new_denier)) + '</td>' + '<td>' + escapeHtml(String(r.total_tar)) + '</td>' + '<td>' + escapeHtml(String(r.meters)) + '</td>' + '<td>' + escapeHtml(String(r.weight_kg)) + '</td>' + '<td>' + escapeHtml(String(r.tpm ?? '')) + '</td>' + '<td>' + escapeHtml(String(r.wastage ?? '')) + '</td>'; beamTableBody.appendChild(tr); }); } function fetchBeamData(){ const bq = $('beam_quality_id').value; const tm = parseFloat($('total_meter').value || 0); if (!bq) { alert('Select beam quality first.'); return; } if (!(tm > 0)) { alert('Enter total meter for beam'); return; } beamFetchMsg.textContent = 'Fetching...'; const fd = new FormData(); fd.append('action','fetch'); fd.append('beam_quality_id', bq); fd.append('total_meter', tm); fetch(window.location.href, { method: 'POST', body: fd }) .then(r => r.json()) .then(j => { if (!j.success) { beamFetchMsg.textContent = 'No data: ' + (j.msg || ''); renderBeamRows([]); return; } renderBeamRows(j.data || []); beamFetchMsg.textContent = 'Rows loaded: ' + (j.data ? j.data.length : 0); }) .catch(err => { console.error('fetchBeamData error', err); beamFetchMsg.textContent = 'Fetch error (see console).'; }); } if (btnFetchBeam) btnFetchBeam.addEventListener('click', function(e){ e.preventDefault(); fetchBeamData(); }); btnPrepare.addEventListener('click', function(e){ e.preventDefault(); if (!$('month').value || !$('stock_type').value) return alert('Select month and stock type first.'); $('save_month').value = $('month').value; $('save_location').value = $('location').value; $('save_machine_group').value = $('machine_group').value; $('save_machine_type').value = $('machine_type').value; $('save_stock_type').value = $('stock_type').value; $('save_beam_quality_id').value = $('beam_quality_id').value; $('save_total_meter').value = $('total_meter').value; $('save_remark').value = $('remark').value; detailArea.style.display = 'block'; const mt = ($('machine_type').value || '').toLowerCase(); if (mt.includes('beam')) { beamArea.style.display = 'block'; otherSections.style.display = 'none'; fetchBeamData(); } else { beamArea.style.display = 'none'; otherSections.style.display = 'block'; } }); btnSave.addEventListener('click', function(e){ e.preventDefault(); if (!$('month').value) return alert('Select month first.'); if (!$('stock_type').value) return alert('Select stock type.'); let payload = {}; payload.location = $('save_location').value || $('location').value || ''; payload.machine_group = $('save_machine_group').value || $('machine_group').value || ''; payload.machine_type = $('save_machine_type').value || $('machine_type').value || ''; payload.stock_type = $('save_stock_type').value || $('stock_type').value || ''; payload.month = $('save_month').value || $('month').value || ''; payload.remark = $('save_remark').value || $('remark').value || ''; payload.created_by = <?= (int)($u['id'] ?? 0) ?>; payload.meta = { prepared_at: new Date().toISOString(), prepared_by: <?= (int)($u['id'] ?? 0) ?> }; if (window.PREPARED_JSON) { try { payload.sections = JSON.parse(window.PREPARED_JSON); } catch(e) { payload.sections = window.PREPARED_JSON; } } else { if (beamArea.style.display !== 'none') { payload.sections = payload.sections || {}; payload.sections.beam = []; document.querySelectorAll('#beamTable tbody tr').forEach(tr => { const td = tr.querySelectorAll('td'); payload.sections.beam.push({ stock_type: td[1].textContent, yarn_type: td[2].textContent, color: td[3].textContent, denier: td[4].textContent, new_denier: td[5].textContent, total_tar: td[6].textContent, meters: td[7].textContent, weight_kg: td[8].textContent }); }); } else { payload.sections = payload.sections || []; } } payload.beam_quality_id = $('beam_quality_id').value || null; payload.total_meter = $('total_meter').value || null; // IMPORTANT: use native JS stringify only (no PHP constants) $('save_yarns_json').value = JSON.stringify(payload); $('save_month').value = $('month').value; $('save_stock_type').value = $('stock_type').value; // disable Save button while request is in progress btnSave.disabled = true; btnSave.textContent = 'Saving...'; const fd = new FormData(document.getElementById('mainForm')); fd.set('action','save'); fetch(window.location.href, { method: 'POST', body: fd }) .then(r => r.text()) .then(html => { let notice = document.querySelector('.notice'); if (!notice) { notice = document.createElement('div'); notice.className = 'notice'; document.querySelector('.card').insertBefore(notice, document.querySelector('.card').firstChild.nextSibling); } notice.innerHTML = html; }) .catch(err => { console.error('save error', err); let notice = document.querySelector('.notice'); if (!notice) { notice = document.createElement('div'); notice.className = 'notice'; document.querySelector('.card').insertBefore(notice, document.querySelector('.card').firstChild.nextSibling); } notice.innerHTML = '<div style="color:red">Save failed — see console for details.</div>'; }) .finally(() => { btnSave.disabled = false; btnSave.textContent = 'Save Entry'; }); }); }); })(); </script> </body> </html>