« Back to History
beam_quality_edit.php
|
20260723_000646.php
Initial Domain Snapshot
Copy Code
<?php /* ========================================================= File: /erp/beam_quality_edit.php Purpose: Edit Beam Quality (multi-yarn rows) Scope : Page-only (no global/base edits) Updates: - Error pe POST data preserve (no reset) - Uniqueness changed to (company_id, name, pattern) - Tar = SUM(total_tar[]) live + server - Weight[100m] = (denier/90) * total_tar (grams) live + server - Total Weight (per 100 m) summary (g & kg) shown ========================================================= */ error_reporting(E_ALL); ini_set('display_errors',1); /* buffer so redirects work even if header echoed */ if (!ob_get_level()) { ob_start(); } /* ====== Auth/Bootstrap ====== */ require_once __DIR__ . '/modules/auth/page_acl.php'; $ctx = page_require_access('beam_quality_edit'); $company_id = (int)$ctx['company_id']; $user_id = (int)$ctx['user']['id']; $u = $ctx['user']; $pdo = (isset($ctx['pdo']) && $ctx['pdo'] instanceof PDO) ? $ctx['pdo'] : null; if (!$pdo) { require __DIR__ . '/core/db.php'; } require_once __DIR__ . '/helpers/activity_helper.php'; /* helpers */ function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function cols(PDO $pdo,$t){ $c=[]; try{ foreach($pdo->query("DESCRIBE `$t`") as $r){ $c[$r['Field']]=true; } }catch(Throwable $e){} return $c; } function addcol(PDO $pdo,$t,$c,$ddl){ $C=cols($pdo,$t); if(!isset($C[$c])){ $pdo->exec("ALTER TABLE `$t` ADD $ddl"); } } function dropcol(PDO $pdo,$t,$c){ $C=cols($pdo,$t); if(isset($C[$c])){ $pdo->exec("ALTER TABLE `$t` DROP COLUMN `$c`"); } } function canonical_stock($raw){ $raw = strtoupper(trim((string)$raw)); static $map = ['YARN'=>'Yarn','TFO'=>'TFO','TF0'=>'TFO','TFI'=>'TFO','TPM'=>'TPM','TP'=>'TPM','ZARI'=>'Zari']; return $map[$raw] ?? 'Yarn'; } /* denier parse + weight calc */ function parse_denier_to_float($s): float { $s = trim((string)$s); if ($s === '') return 0.0; if (strpos($s, '/') !== false) { $left = trim(explode('/', $s, 2)[0]); if (preg_match('/\d+(\.\d+)?/', $left, $m)) return (float)$m[0]; } if (preg_match('/\d+(\.\d+)?/', $s, $m)) return (float)$m[0]; return 0.0; } function weight_100m_from_denier_and_tar($denier, $tar): float { // grams $d = max(0.0, (float)$denier); $t = max(0.0, (float)$tar); return ($d / 90.0) * $t; } /* ====== Schema ensure (page-local) ====== */ $pdo->exec("CREATE TABLE IF NOT EXISTS beam_qualities ( id BIGINT PRIMARY KEY AUTO_INCREMENT, company_id BIGINT NOT NULL, name VARCHAR(150) NOT NULL, pattern VARCHAR(150) DEFAULT NULL, tar VARCHAR(100) DEFAULT NULL, warping_rate DECIMAL(12,3) NOT NULL DEFAULT 0, created_by BIGINT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_bq_company (company_id), UNIQUE KEY uniq_bq_company_name (company_id, name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); $pdo->exec("CREATE TABLE IF NOT EXISTS beam_quality_yarns ( id BIGINT PRIMARY KEY AUTO_INCREMENT, beam_quality_id BIGINT NOT NULL, seq_no INT NOT NULL, stock_type VARCHAR(20) NOT NULL DEFAULT 'Yarn', yarn_type VARCHAR(100) NOT NULL, denier VARCHAR(100) NOT NULL, color VARCHAR(100) NOT NULL, total_tar DECIMAL(12,3) DEFAULT 0, tpm DECIMAL(12,3) DEFAULT 0, weight DECIMAL(12,3) DEFAULT 0, /* grams per 100 m */ wastage DECIMAL(12,3) DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_bqy_bq (beam_quality_id), CONSTRAINT fk_bqy_bq FOREIGN KEY (beam_quality_id) REFERENCES beam_qualities(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); try{ addcol($pdo,'beam_quality_yarns','stock_type',"COLUMN `stock_type` VARCHAR(20) NOT NULL DEFAULT 'Yarn' AFTER `seq_no`"); addcol($pdo,'beam_qualities','tar', "COLUMN `tar` VARCHAR(100) NULL AFTER `pattern`"); addcol($pdo,'beam_qualities','warping_rate',"COLUMN `warping_rate` DECIMAL(12,3) NOT NULL DEFAULT 0 AFTER `tar`"); dropcol($pdo,'beam_qualities','jala_no'); /* uniqueness: allow same name with different pattern */ try{ $pdo->exec("ALTER TABLE beam_qualities DROP INDEX uniq_bq_company_name"); }catch(Throwable $e){} try{ $pdo->exec("ALTER TABLE beam_qualities ADD UNIQUE KEY uniq_bq_comp_name_pat (company_id, name, pattern)"); }catch(Throwable $e){} }catch(Throwable $e){} /* ====== Load header + lines ====== */ $id = (int)($_GET['id'] ?? 0); if ($id <= 0){ http_response_code(400); exit('Missing id'); } $st = $pdo->prepare("SELECT * FROM beam_qualities WHERE id=:id AND company_id=:c"); $st->execute([':id'=>$id, ':c'=>$company_id]); $hdr = $st->fetch(PDO::FETCH_ASSOC); if(!$hdr){ http_response_code(404); exit('Not found or no permission'); } $st = $pdo->prepare("SELECT * FROM beam_quality_yarns WHERE beam_quality_id=:id ORDER BY seq_no,id"); $st->execute([':id'=>$id]); $lines = $st->fetchAll(PDO::FETCH_ASSOC); foreach($lines as &$ln){ $ln['stock_type'] = canonical_stock($ln['stock_type'] ?? 'Yarn'); } unset($ln); /* initial sums */ $hdr_tar_sum = 0.0; $weight_sum_g = 0.0; foreach($lines as $ln){ $v = isset($ln['total_tar']) ? (string)$ln['total_tar'] : ''; if ($v !== '' && is_numeric($v)) $hdr_tar_sum += (float)$v; $w = isset($ln['weight']) ? (float)$ln['weight'] : 0.0; $weight_sum_g += $w; } /* ====== Seed dropdowns ====== */ $stockTypes = ['Yarn','TFO','TPM','Zari']; $ph = implode(',', array_fill(0,count($stockTypes),'?')); $stmt = $pdo->prepare(" SELECT TRIM(stock_type) AS stock_type, TRIM(yarn_types) AS yarn_type, TRIM(denier) AS denier, TRIM(color) AS color FROM yarn_company_data WHERE company_id=? AND stock_type IN ($ph) AND (COALESCE(yarn_types,'')<>'' OR COALESCE(denier,'')<>'' OR COALESCE(color,'')<>'') "); $params = array_merge([$company_id], $stockTypes); $stmt->execute($params); $rws = $stmt->fetchAll(PDO::FETCH_ASSOC); $seed=[]; foreach($stockTypes as $stt){ $seed[$stt]=['types'=>[],'deniers'=>[],'colors'=>[]]; } foreach($rws as $r){ $s = canonical_stock($r['stock_type'] ?? 'Yarn'); if(!isset($seed[$s])) $seed[$s]=['types'=>[], 'deniers'=>[], 'colors'=>[]]; if(!empty($r['yarn_type'])) $seed[$s]['types'][$r['yarn_type']] = true; if(!empty($r['denier'])) $seed[$s]['deniers'][$r['denier']] = true; if(!empty($r['color'])) $seed[$s]['colors'][$r['color']] = true; } foreach($seed as $k=>$g){ $seed[$k]['types'] = array_values(array_keys($g['types'])); $seed[$k]['deniers'] = array_values(array_keys($g['deniers'])); $seed[$k]['colors'] = array_values(array_keys($g['colors'])); } /* ========================================================= POST -> Update OR show errors with POST values preserved ========================================================= */ $errors=[]; if($_SERVER['REQUEST_METHOD']==='POST'){ $name = trim($_POST['name'] ?? ''); $pattern = trim($_POST['pattern'] ?? ''); $warping_rate = trim($_POST['warping_rate'] ?? '0'); $stp = $_POST['stock_type'] ?? []; $yt = $_POST['yarn_type'] ?? []; $dn = $_POST['denier'] ?? []; $cl = $_POST['color'] ?? []; $tt = $_POST['total_tar'] ?? []; $tp = $_POST['tpm'] ?? []; $wg = $_POST['wastage'] ?? []; if($name===''){ $errors[]='Name is required.'; } $N = max(count($stp),count($yt),count($dn),count($cl),count($tt),count($tp),count($wg)); $has=false; for($i=0;$i<$N;$i++){ if((trim($yt[$i]??'')!=='')||(trim($dn[$i]??'')!=='')||(trim($cl[$i]??'')!=='')){ $has=true; break; } } if(!$has){ $errors[]='Add at least one Yarn row.'; } /* server sums */ $tar_sum = 0.0; $weight_sum_g = 0.0; if(!$errors){ try{ $pdo->beginTransaction(); /* update header */ $up=$pdo->prepare("UPDATE beam_qualities SET name=:n, pattern=:p, tar=:t, warping_rate=:wr WHERE id=:id AND company_id=:c"); /* compute tar */ for($i=0;$i<$N;$i++){ $v = isset($tt[$i]) ? trim((string)$tt[$i]) : ''; if ($v !== '' && is_numeric($v)) { $tar_sum += (float)$v; } } $tar_str = number_format($tar_sum, 3, '.', ''); $up->execute([ ':n'=>$name, ':p'=>($pattern!=='' ? $pattern : null), ':t'=>$tar_str, ':wr'=>(is_numeric($warping_rate)?$warping_rate:0), ':id'=>$id, ':c'=>$company_id ]); /* replace lines */ $pdo->prepare("DELETE FROM beam_quality_yarns WHERE beam_quality_id=:id") ->execute([':id'=>$id]); $ins=$pdo->prepare("INSERT INTO beam_quality_yarns (beam_quality_id, seq_no, stock_type, yarn_type, denier, color, total_tar, tpm, weight, wastage) VALUES (:bq,:seq,:st,:yt,:dn,:cl,:tt,:tp,:wt,:wg)"); $seq=1; $rows_added=0; for($i=0;$i<$N;$i++){ $st_i=canonical_stock($stp[$i]??'Yarn'); $yt_i=trim($yt[$i]??''); $dn_i=trim($dn[$i]??''); $cl_i=trim($cl[$i]??''); $tt_i_raw = trim((string)($tt[$i]??'0')); $tp_i_raw = trim((string)($tp[$i]??'0')); $wg_i_raw = trim((string)($wg[$i]??'0')); if($yt_i===''&&$dn_i===''&&$cl_i===''&&$tt_i_raw===''&&$tp_i_raw===''&&$wg_i_raw===''){ continue; } $tt_i = ($tt_i_raw!=='' && is_numeric($tt_i_raw)) ? (float)$tt_i_raw : 0.0; $tp_i = ($tp_i_raw!=='' && is_numeric($tp_i_raw)) ? (float)$tp_i_raw : 0.0; $wg_i = ($wg_i_raw!=='' && is_numeric($wg_i_raw)) ? (float)$wg_i_raw : 0.0; $den_i_num = parse_denier_to_float($dn_i); $w100 = weight_100m_from_denier_and_tar($den_i_num, $tt_i); // grams $weight_sum_g += $w100; $ins->execute([ ':bq'=>$id, ':seq'=>$seq++, ':st'=>$st_i, ':yt'=>$yt_i, ':dn'=>$dn_i, ':cl'=>$cl_i, ':tt'=>$tt_i, ':tp'=>$tp_i, ':wt'=>$w100, ':wg'=>$wg_i ]); $rows_added++; } if($rows_added===0){ throw new Exception('No valid yarn rows to save.'); } $pdo->commit(); activity_update('beam', 'beam_quality_update', $id, 'Updated beam quality'); /* redirect */ $redir = '/erp/beam_quality_list.php?updated=1'; if (ob_get_level()) { ob_end_clean(); } header("Location: $redir", true, 303); exit; }catch(Throwable $e){ if($pdo->inTransaction()) $pdo->rollBack(); $errors[]='Save failed: '.$e->getMessage(); } } /* ---- If errors: preserve POST back to form ---- */ if($errors){ $hdr['name'] = $name; $hdr['pattern'] = $pattern; $hdr['warping_rate'] = is_numeric($warping_rate)?$warping_rate:0; /* rebuild lines from POST for UI */ $lines = []; $N = max(count($stp),count($yt),count($dn),count($cl),count($tt),count($tp),count($wg)); $hdr_tar_sum = 0.0; $weight_sum_g = 0.0; for($i=0;$i<$N;$i++){ $st_i=canonical_stock($stp[$i]??'Yarn'); $yt_i=trim($yt[$i]??''); $dn_i=trim($dn[$i]??''); $cl_i=trim($cl[$i]??''); $tt_i = (is_numeric($tt[$i]??null)?(float)$tt[$i]:0.0); $tp_i = (is_numeric($tp[$i]??null)?(float)$tp[$i]:0.0); $wg_i = (is_numeric($wg[$i]??null)?(float)$wg[$i]:0.0); if($yt_i===''&&$dn_i===''&&$cl_i===''&&$tt_i==0.0&&$tp_i==0.0&&$wg_i==0.0){ continue; } $den_i_num = parse_denier_to_float($dn_i); $w100 = weight_100m_from_denier_and_tar($den_i_num, $tt_i); // grams $hdr_tar_sum += $tt_i; $weight_sum_g += $w100; $lines[] = [ 'stock_type'=>$st_i,'yarn_type'=>$yt_i,'denier'=>$dn_i,'color'=>$cl_i, 'total_tar'=>$tt_i,'tpm'=>$tp_i,'weight'=>$w100,'wastage'=>$wg_i ]; } } } /* ====== Render ====== */ ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Edit Beam Quality</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> :root{ --primary:#34A853; --bg:#F5FFF7; --text:#202124; --muted:#5F6368; --card:#FFFFFF; --shadow:0 6px 18px rgba(0,0,0,.06); --radius:16px; --danger:#b00020; } body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Arial;background:var(--bg);color:var(--text);} .wrap{max-width:1150px;margin:20px auto;padding:16px;} .card{background:var(--card);border-radius:var(--radius);box-shadow:var(--shadow);padding:18px;} h1{margin:0 0 12px;} .row{display:grid;grid-template-columns:1fr 1fr;gap:12px;} label{font-size:12px;color:var(--muted);display:block;margin-bottom:6px;} input,select{width:100%;padding:10px;border:1px solid #e5e7eb;border-radius:10px} input.readonly{background:#f6f8fa} table{width:100%;border-collapse:separate;border-spacing:0 8px;} th,td{padding:6px} th{font-size:12px;color:#5F6368;text-align:left;} .tr-card{background:#fff;border:1px solid #eef2f7;border-radius:12px;} .btn{padding:10px 14px;border-radius:12px;border:0;cursor:pointer} .btn-primary{background:#34A853;color:#fff} .btn-ghost{border:1px dashed #cdd9e3;background:transparent} .btn-danger{background:#b00020;color:#fff} .toolbar{display:flex;gap:10px;justify-content:space-between;align-items:center;margin-top:12px} .error{background:#ffebee;color:#b00020;padding:10px 12px;border-radius:10px;margin-bottom:12px} .hint{font-size:11px;color:#6b7280;margin-top:4px} .note{font-size:12px;color:#2e7d32} </style> </head> <body> <?php $__header = __DIR__ . '/partials/header.php'; if (file_exists($__header)) { if (!isset($company) || !is_array($company)) { $company = ['id'=>$company_id, 'name'=>($u['company_name'] ?? '')]; } include $__header; } ?> <div class="wrap"><div class="card"> <div class="toolbar"> <h1>Edit Beam Quality</h1> <div><a class="btn btn-ghost" href="/erp/beam_quality_list.php">← Back to List</a></div> </div> <?php if(!empty($errors)): ?> <div class="error"> <strong>Please fix:</strong> <ul style="margin:6px 0 0 18px"> <?php foreach($errors as $e){ echo '<li>'.h($e).'</li>'; } ?> </ul> </div> <?php endif; ?> <form method="post" autocomplete="off" id="bqForm"> <div class="row"> <div> <label>Name</label> <input type="text" name="name" value="<?=h($hdr['name'])?>" required> <div class="hint">You can reuse same <b>Name</b> with different <b>Pattern</b> (e.g., “N Red Patta 10”, “N Red Patta 8”).</div> </div> <div> <label>Pattern</label> <input type="text" name="pattern" value="<?=h($hdr['pattern'] ?? '')?>"> </div> <div> <label>Tar (Auto Sum of Total Tar)</label> <input type="text" name="tar" id="tar_sum" value="<?=h(number_format($hdr_tar_sum,3,'.',''))?>" readonly class="readonly"> <div class="hint">Auto from yarn rows; saved to header.</div> </div> <div> <label>Warping Rate</label> <input type="number" step="0.001" min="0" name="warping_rate" value="<?=h(number_format((float)($hdr['warping_rate'] ?? 0),3,'.',''))?>"> </div> <div> <label>Total Weight (per 100 m)</label> <?php $wkg = $weight_sum_g/1000.0; ?> <input type="text" id="weight_sum" value="<?=h(number_format($weight_sum_g,3,'.','').' g ('.number_format($wkg,3,'.','').' kg)')?>" readonly class="readonly"> <div class="hint">Informational: sum of row “Weight (per 100 m)”.</div> </div> </div> <h3 style="margin-top:18px;">Yarns</h3> <table id="yarnTable"> <thead> <tr> <th>Stock Type</th><th>Yarn Type</th><th>Denier</th><th>Color</th> <th>Total Tar</th><th>TPM</th><th>Weight (per 100 m)</th><th>Wastage</th><th>Actions</th> </tr> </thead> <tbody id="tbodyYarns"></tbody> </table> <div class="toolbar"> <button type="button" class="btn btn-ghost" id="btnAddRow">+ Add Yarn</button> <div><button type="submit" class="btn btn-primary">Update</button></div> </div> </form> </div></div> <script> const seed = <?= json_encode($seed, JSON_UNESCAPED_UNICODE) ?>; const STOCK_TYPES = <?= json_encode(array_values($stockTypes)) ?>; const existing = <?= json_encode($lines, JSON_UNESCAPED_UNICODE) ?>; const tbody = document.getElementById('tbodyYarns'); const addBtn = document.getElementById('btnAddRow'); const tarSumEl = document.getElementById('tar_sum'); const weightSumEl = document.getElementById('weight_sum'); function escapeHtml(s){return (''+s).replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));} function opts(list){let h='<option value=""></option>'; for(const v of list){h+=`<option value="${escapeHtml(v)}">${escapeHtml(v)}</option>`;} return h;} function stockOpts(){let h=''; for(const st of STOCK_TYPES){h+=`<option value="${escapeHtml(st)}">${escapeHtml(st)}</option>`;} return h;} function numberInput(name,val='',readonly=false){ const v=(val==null?'':String(val)); const ro = readonly ? 'readonly class="readonly"' : ''; return `<input type="number" name="${name}[]" step="0.001" min="0" value="${escapeHtml(v)}" ${ro}>`; } function populateByStock(stock, ytSel, dnSel, clSel){ const grp=seed[stock]||{types:[],deniers:[],colors:[]}; ytSel.innerHTML=opts(grp.types); dnSel.innerHTML=opts(grp.deniers); clSel.innerHTML=opts(grp.colors); } function parseDenier(str){ if(!str) return 0; const s = String(str).trim(); if (s.includes('/')) { const left = s.split('/',1)[0].trim(); const m = left.match(/\d+(\.\d+)?/); return m ? parseFloat(m[0]) : 0; } const m = s.match(/\d+(\.\d+)?/); return m ? parseFloat(m[0]) : 0; } function recalcTarSum(){ let sum = 0; document.querySelectorAll('input[name="total_tar[]"]').forEach(inp=>{ const v = parseFloat(inp.value); if(!isNaN(v)) sum += v; }); tarSumEl.value = sum.toFixed(3); } function recalcWeightSum(){ let g = 0; document.querySelectorAll('input[name="weight[]"]').forEach(inp=>{ const v = parseFloat(inp.value); if(!isNaN(v)) g += v; }); weightSumEl.value = `${g.toFixed(3)} g (${(g/1000).toFixed(3)} kg)`; } function recalcRowWeight(tr){ const denSel = tr.querySelector('select[name="denier[]"]'); const tarInp = tr.querySelector('input[name="total_tar[]"]'); const wgtInp = tr.querySelector('input[name="weight[]"]'); const den = parseDenier(denSel.value); const tar = parseFloat(tarInp.value); const t = isNaN(tar) ? 0 : tar; const grams = (den/90.0) * t; wgtInp.value = isFinite(grams) ? grams.toFixed(3) : ''; } function hookRow(tr){ const denSel = tr.querySelector('select[name="denier[]"]'); const tarInp = tr.querySelector('input[name="total_tar[]"]'); denSel.addEventListener('change', ()=>{ recalcRowWeight(tr); recalcWeightSum(); }); tarInp.addEventListener('input', ()=>{ recalcRowWeight(tr); recalcTarSum(); recalcWeightSum(); }); // initial compute recalcRowWeight(tr); recalcTarSum(); recalcWeightSum(); } function makeRow(defaultStock='Yarn', preset={}){ const tr=document.createElement('tr'); tr.className='tr-card'; tr.innerHTML=`<td><select name="stock_type[]" class="stkSel">${stockOpts()}</select></td> <td><select name="yarn_type[]" class="ytSel"></select></td> <td><select name="denier[]" class="dnSel"></select></td> <td><select name="color[]" class="clSel"></select></td> <td>${numberInput('total_tar', preset.total_tar)}</td> <td>${numberInput('tpm', preset.tpm)}</td> <td>${numberInput('weight', preset.weight, true)}</td> <td>${numberInput('wastage', preset.wastage)}</td> <td><button type="button" class="btn btn-danger" onclick="removeRow(this)">Delete</button></td>`; tbody.appendChild(tr); const stkSel=tr.querySelector('.stkSel'), ytSel=tr.querySelector('.ytSel'), dnSel=tr.querySelector('.dnSel'), clSel=tr.querySelector('.clSel'); const st=preset.stock_type||defaultStock; stkSel.value=st; populateByStock(st, ytSel, dnSel, clSel); ytSel.value=preset.yarn_type||''; dnSel.value=preset.denier||''; clSel.value=preset.color||''; stkSel.addEventListener('change',()=>{ const curr=stkSel.value||'Yarn'; populateByStock(curr, ytSel, dnSel, clSel); ytSel.value=''; dnSel.value=''; clSel.value=''; recalcRowWeight(tr); recalcWeightSum(); }); hookRow(tr); return tr; } function removeRow(btn){ const tr=btn.closest('tr'); if(tr && tbody.children.length>1){ tr.remove(); } else if(tr){ tr.querySelectorAll('input,select').forEach(el=>el.value=''); } recalcTarSum(); recalcWeightSum(); } if(Array.isArray(existing)&&existing.length){ for(const r of existing){ makeRow(r.stock_type||'Yarn', r); } } else { makeRow('Yarn',{}); } addBtn.addEventListener('click',()=>makeRow('Yarn',{})); // Safety: recompute on submit too document.getElementById('bqForm').addEventListener('submit', ()=>{ document.querySelectorAll('#tbodyYarns tr').forEach(tr=>recalcRowWeight(tr)); recalcTarSum(); recalcWeightSum(); }); </script> </body> </html>