« Back to History
beam_quality_add.php
|
20260722_120325.php
Initial Domain Snapshot
Copy Code
<?php /* ========================================================= File: /erp/beam_quality_ad_new.php Purpose: Create Beam Quality (composition) with multi-yarn rows SAME as beam_quality_add.php but weight calculation uses `new_denier` from yarn_weight_data (if present) Scope: Page-only; includes shared header ========================================================= */ error_reporting(E_ALL); ini_set('display_errors', 1); if (!ob_get_level()) { ob_start(); } /* ========================================================= SECTION 1 — Auth + Bootstrap (PAGE-ONLY) ========================================================= */ require_once __DIR__ . '/modules/auth/page_acl.php'; $ctx = page_require_access('beam_quality_add'); // keep same ACL or change as needed $u = $ctx['user']; $company_id = (int)$ctx['company_id']; $user_id = (int)$u['id']; $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 table_columns(PDO $pdo, string $table): array { $cols = []; try { foreach($pdo->query("DESCRIBE `$table`") as $r){ $cols[$r['Field']] = true; } } catch(Throwable $e){} return $cols; } function add_column_if_missing(PDO $pdo, string $table, string $col, string $ddl){ $cols = table_columns($pdo, $table); if (!isset($cols[$col])) { $pdo->exec("ALTER TABLE `$table` ADD $ddl"); } } function drop_column_if_exists(PDO $pdo, string $table, string $col){ $cols = table_columns($pdo, $table); if (isset($cols[$col])) { $pdo->exec("ALTER TABLE `$table` DROP COLUMN `$col`"); } } /* Canonicalize stock types to keys used by UI */ 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'; } /* Parse denier numeric (fallback) */ function parse_denier_to_float($s): float { $s = trim((string)$s); if ($s === '') return 0.0; if (strpos($s, '/') !== false) { $parts = explode('/', $s, 2); $left = trim($parts[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; } /* Weight of 100 m for a single yarn = Denier / 90 (in grams). For multiple ends ("Total Tar" = number of yarn pcs), multiply by Tar. */ function weight_100m_from_denier_and_tar($denier, $tar): float { $d = max(0.0, (float)$denier); $t = max(0.0, (float)$tar); return ($d / 90.0) * $t; // grams } /* ========================================================= SECTION 2 — Schema ensure + self-heal (LOCAL TO PAGE) ========================================================= */ $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, 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; "); /* defensive heals */ try { add_column_if_missing($pdo,'beam_quality_yarns','stock_type',"COLUMN `stock_type` VARCHAR(20) NOT NULL DEFAULT 'Yarn' AFTER `seq_no`"); add_column_if_missing($pdo,'beam_qualities','warping_rate',"COLUMN `warping_rate` DECIMAL(12,3) NOT NULL DEFAULT 0 AFTER `pattern`"); add_column_if_missing($pdo,'beam_qualities','tar', "COLUMN `tar` VARCHAR(100) NULL AFTER `pattern`"); drop_column_if_exists($pdo,'beam_qualities','jala_no'); } catch(Throwable $e){} /* ========================================================= SECTION 3 — Seed dropdowns from yarn_company_data Also fetch yarn_weight_data (denier -> new_denier) for mapping ========================================================= */ $stockTypes = ['Yarn','TFO','TPM','Zari']; $ph = implode(',', array_fill(0, count($stockTypes), '?')); $sql = " 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,'')<>'') "; $stmt = $pdo->prepare($sql); $params = array_merge([$company_id], $stockTypes); $stmt->execute($params); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $seed = []; foreach($stockTypes as $st){ $seed[$st]=['types'=>[], 'deniers'=>[], 'colors'=>[]]; } foreach($rows as $r){ $st = canonical_stock($r['stock_type'] ?? 'Yarn'); if(!isset($seed[$st])) $seed[$st]=['types'=>[], 'deniers'=>[], 'colors'=>[]]; if(!empty($r['yarn_type'])) $seed[$st]['types'][$r['yarn_type']] = true; if(!empty($r['denier'])) $seed[$st]['deniers'][$r['denier']] = true; if(!empty($r['color'])) $seed[$st]['colors'][$r['color']] = true; } foreach($seed as $k=>$grp){ $seed[$k]['types'] = array_values(array_keys($grp['types'])); $seed[$k]['deniers'] = array_values(array_keys($grp['deniers'])); $seed[$k]['colors'] = array_values(array_keys($grp['colors'])); } /* Fetch yarn_weight_data mapping for this company */ $denier_map = []; // keys => numeric new_denier floats or strings try { $s = $pdo->prepare("SELECT TRIM(denier) AS denier, new_denier FROM yarn_weight_data WHERE company_id = :c"); $s->execute([':c'=>$company_id]); foreach($s->fetchAll(PDO::FETCH_ASSOC) as $r){ $k = strtoupper(trim((string)$r['denier'])); if ($k === '') continue; $nd = is_numeric($r['new_denier']) ? (float)$r['new_denier'] : (float)($r['new_denier'] ?? 0); // store by exact denier text and also by numeric-left part if available $denier_map[$k] = $nd; if (preg_match('/\d+(\.\d+)?/', $k, $m)) { $dennum = $m[0]; if (!isset($denier_map[$dennum])) $denier_map[$dennum] = $nd; } } } catch(Throwable $e){ // ignore; mapping stays empty } /* helper to get new_denier (server-side) */ function lookup_new_denier(PDO $pdo, int $company_id, string $denier_str, array $denier_map): float { $raw = strtoupper(trim((string)$denier_str)); if ($raw === '') return 0.0; // 1) exact map if (isset($denier_map[$raw])) return (float)$denier_map[$raw]; // 2) numeric-left part if (strpos($raw, '/') !== false) { $left = trim(explode('/', $raw, 2)[0]); if (isset($denier_map[$left])) return (float)$denier_map[$left]; if (preg_match('/\d+(\.\d+)?/', $left, $m)) { $num = $m[0]; if (isset($denier_map[$num])) return (float)$denier_map[$num]; } } // 3) search numeric part anywhere if (preg_match('/\d+(\.\d+)?/', $raw, $m)) { $num = $m[0]; if (isset($denier_map[$num])) return (float)$denier_map[$num]; // else fall back to numeric parsed denier return (float)$m[0]; } return 0.0; } /* ========================================================= SECTION 4 — POST handler (Transactional save) + STICKY VALUES ========================================================= */ $errors = []; $saved = isset($_GET['saved']) && $_GET['saved']=='1'; $sticky = ['name'=>'','pattern'=>'','tar'=>'','warping_rate'=>'']; $sticky_lines = []; if($_SERVER['REQUEST_METHOD']==='POST'){ $name = trim($_POST['name'] ?? ''); $pattern = trim($_POST['pattern'] ?? ''); $warping_rate = trim($_POST['warping_rate'] ?? '0'); $st = $_POST['stock_type'] ?? []; $yt = $_POST['yarn_type'] ?? []; $dn = $_POST['denier'] ?? []; $cl = $_POST['color'] ?? []; $tt = $_POST['total_tar'] ?? []; $tp = $_POST['tpm'] ?? []; $wg = $_POST['wastage'] ?? []; $N = max(count($st),count($yt),count($dn),count($cl),count($tt),count($tp),count($wg)); $tar_sum = 0.0; for($i=0;$i<$N;$i++){ $st_i = trim($st[$i] ?? 'Yarn'); $yt_i = trim($yt[$i] ?? ''); $dn_i = trim($dn[$i] ?? ''); $cl_i = trim($cl[$i] ?? ''); $tt_i = trim((string)($tt[$i] ?? '')); $tp_i = trim((string)($tp[$i] ?? '')); $wg_i = trim((string)($wg[$i] ?? '')); $tar_i_num = ($tt_i!=='' && is_numeric($tt_i)) ? (float)$tt_i : 0.0; // SERVER-SIDE: use lookup_new_denier to get the denier value for weight calculation $den_i_num = lookup_new_denier($pdo, $company_id, $dn_i, $denier_map); $w100 = weight_100m_from_denier_and_tar($den_i_num, $tar_i_num); // grams if ($tar_i_num > 0) { $tar_sum += $tar_i_num; } $sticky_lines[] = [ 'stock_type' => $st_i, 'yarn_type' => $yt_i, 'denier' => $dn_i, 'color' => $cl_i, 'total_tar' => ($tt_i==='') ? '' : (string)$tar_i_num, 'tpm' => ($tp_i==='') ? '' : $tp_i, 'weight' => number_format($w100, 3, '.', ''), 'wastage' => ($wg_i==='') ? '' : $wg_i, ]; } $sticky = [ 'name' => $name, 'pattern' => $pattern, 'tar' => number_format($tar_sum, 3, '.', ''), 'warping_rate' => $warping_rate, ]; if($name===''){ $errors[]='Name is required.'; } $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.'; } if(!$errors){ try{ $pdo->beginTransaction(); $display_name = $name; if ($pattern !== '') { $display_name = trim($name.' '.$pattern); } $insH = $pdo->prepare("INSERT INTO beam_qualities (company_id, name, pattern, tar, warping_rate, created_by) VALUES (:c,:n,:p,:t,:wr,:u)"); $insH->execute([ ':c'=>$company_id, ':n'=>$display_name, ':p'=>$pattern!=='' ? $pattern : null, ':t'=> number_format($tar_sum, 3, '.', ''), ':wr'=> is_numeric($warping_rate)?$warping_rate:0, ':u'=>$user_id ]); $bq_id = (int)$pdo->lastInsertId(); $insL = $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 = trim($st[$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; // Use lookup_new_denier AGAIN to compute weight before saving $den_i_num = lookup_new_denier($pdo, $company_id, $dn_i, $denier_map); $w100 = weight_100m_from_denier_and_tar($den_i_num, $tt_i); $insL->execute([ ':bq'=>$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_create('beam', 'beam_quality_create', $bq_id, 'Created beam quality'); $redir = '/erp/beam_quality_list.php?saved=1'; if (!headers_sent()) { if (ob_get_level()) { ob_end_clean(); } header("Location: $redir", true, 303); exit; } else { echo "<script>location.href='".h($redir)."';</script>"; echo '<noscript><meta http-equiv="refresh" content="0;url='.h($redir).'"></noscript>'; exit; } }catch(Throwable $e){ if($pdo->inTransaction()) $pdo->rollBack(); $errors[]='Save failed: '.$e->getMessage(); } } } /* ========================================================= SECTION 5 — HTML (WITH SHARED HEADER) ========================================================= */ if (!isset($company) || !is_array($company)) { $company = ['id'=>$company_id, 'name'=>($u['company_name'] ?? '')]; } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"><title>Create Beam Quality (NEW DENIER) • Mister Manager</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; --ok:#1e8e3e; --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; outline:none} input:focus, select:focus{border-color:#c7ead1; box-shadow:0 0 0 3px rgba(52,168,83,.12)} table{width:100%; border-collapse:separate; border-spacing:0 8px;} th,td{padding:6px} th{font-size:12px; color:var(--muted); 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:var(--primary); color:#fff} .btn-ghost{background:transparent; border:1px dashed #cdd9e3} .btn-danger{background:var(--danger); color:#fff} .toolbar{display:flex; gap:10px; justify-content:flex-end; margin-top:12px} .pill{display:inline-block; padding:8px 12px; border-radius:999px; background:#e9f6ee; color:#1e8e3e; font-size:12px; margin-bottom:10px} .error{background:#ffebee; color:#b00020; padding:10px 12px; border-radius:10px; margin-bottom:12px} .success{background:#e8f5e9; color:#1e8e3e; padding:10px 12px; border-radius:10px; margin-bottom:12px} .actions-col{white-space:nowrap} .hint{font-size:11px; color:#6b7280; margin-top:4px} .readonly{background:#f6f8fa} </style> </head> <body> <?php require __DIR__ . '/partials/header.php'; ?> <div class="wrap"><div class="card"> <h1>Create Beam Quality (uses yarn_weight_data)</h1> <div class="pill">Company ID: <?= (int)$company_id ?></div> <?php if ($saved): ?><div class="success">Beam Quality saved successfully.</div><?php endif; ?> <?php if ($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" id="bqForm" autocomplete="off"> <div class="row"> <div><label>Name</label> <input type="text" name="name" id="name_inp" required placeholder="e.g., N Red Patta" value="<?= h($sticky['name']) ?>"> </div> <div><label>Pattern</label> <input type="text" name="pattern" id="pattern_inp" placeholder="e.g., 8" value="<?= h($sticky['pattern']) ?>"> </div> <div><label>Display Name (auto) — Name + Pattern</label> <input type="text" id="display_name_preview" class="readonly" readonly value="<?= h(trim(($sticky['name'] ?? '').' '.($sticky['pattern'] ?? ''))) ?>"> <div class="hint">यह वही text है जो सेव होते समय <strong>Name</strong> में स्टोर होगा.</div> </div> <div><label>Warping Rate</label> <input type="number" step="0.001" min="0" name="warping_rate" placeholder="0.000" value="<?= h($sticky['warping_rate']) ?>"> </div> <div><label>Tar (Auto Sum of Total Tar)</label> <input type="text" name="tar" id="tar_sum" readonly class="readonly" value="<?= h($sticky['tar']) ?>"> <div class="hint">Auto-calculates from the Yarn rows below.</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 class="actions-col">Actions</th> </tr></thead> <tbody id="tbodyYarns"></tbody> </table> <div class="toolbar"> <button type="button" class="btn btn-ghost" id="btnAddRow">+ Add Yarn</button> <button type="submit" class="btn btn-primary">Save Beam Quality</button> </div> </form> </div></div> <!-- ========================================================= SECTION 6 — JS (row builder + seed-driven dropdowns; STICKY) Modified: uses denierMap from yarn_weight_data for client-side weight calc ========================================================= --> <script> const seed = <?= json_encode($seed, JSON_UNESCAPED_UNICODE) ?>; const STOCK_TYPES = <?= json_encode(array_values($stockTypes)) ?>; const existing = <?= json_encode($sticky_lines, JSON_UNESCAPED_UNICODE) ?>; // denier_map: keys are uppercase denier text or numeric-left part => numeric new_denier const denierMap = <?= json_encode($denier_map, JSON_UNESCAPED_UNICODE) ?>; const tbody = document.getElementById('tbodyYarns'); const addBtn = document.getElementById('btnAddRow'); const tarSumEl = document.getElementById('tar_sum'); const nameInp = document.getElementById('name_inp'); const patternInp = document.getElementById('pattern_inp'); const dispPrev = document.getElementById('display_name_preview'); function refreshDisplayName(){ const n = (nameInp.value||'').trim(); const p = (patternInp.value||'').trim(); dispPrev.value = (p ? (n+' '+p) : n).trim(); } nameInp.addEventListener('input', refreshDisplayName); patternInp.addEventListener('input', refreshDisplayName); refreshDisplayName(); 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, value='', placeholder='0', readonly=false){ const v = (value==null?'':String(value)); const ph = placeholder==null?'0':String(placeholder); const ro = readonly ? 'readonly class="readonly"' : ''; return `<input type="number" name="${name}[]" step="0.001" min="0" placeholder="${escapeHtml(ph)}" value="${escapeHtml(v)}" ${ro}>`; } function canonStock(s){ const k = String(s||'').trim().toUpperCase(); if (k==='TFO' || k==='TF0' || k==='TFI') return 'TFO'; if (k==='TPM' || k==='TP') return 'TPM'; if (k==='ZARI') return 'Zari'; if (k==='YARN') return 'Yarn'; return 'Yarn'; } function populateByStock(stock, ytSel, dnSel, clSel){ const key = canonStock(stock); const grp = seed[key] || {types:[], deniers:[], colors:[]}; ytSel.innerHTML = opts(grp.types); dnSel.innerHTML = opts(grp.deniers); clSel.innerHTML = opts(grp.colors); } function hookRowForTarAndWeight(tr){ const tarInp = tr.querySelector('input[name="total_tar[]"]'); const denSel = tr.querySelector('select[name="denier[]"]'); const wgtInp = tr.querySelector('input[name="weight[]"]'); function parseDenierUseMap(str){ if (!str) return 0; const s = String(str).trim(); const keyExact = s.toUpperCase(); if (denierMap && denierMap.hasOwnProperty(keyExact)) { const v = parseFloat(denierMap[keyExact]); if (!isNaN(v)) return v; } // try left of '/' if (s.includes('/')) { const left = s.split('/',1)[0].trim(); if (denierMap && denierMap.hasOwnProperty(left)) { const v = parseFloat(denierMap[left]); if (!isNaN(v)) return v; } const m = left.match(/\d+(\.\d+)?/); if (m) return parseFloat(m[0]); } // numeric anywhere const m = s.match(/\d+(\.\d+)?/); return m ? parseFloat(m[0]) : 0; } function recalcWeight(){ const denVal = parseDenierUseMap(denSel.value || ''); const tarVal = parseFloat(tarInp.value); const t = isNaN(tarVal) ? 0 : tarVal; const grams = (denVal/90.0) * t; wgtInp.value = isFinite(grams) ? grams.toFixed(3) : ''; } 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); } tarInp.addEventListener('input', ()=>{ recalcWeight(); recalcTarSum(); }); denSel.addEventListener('change', ()=>{ recalcWeight(); }); // initial compute recalcWeight(); recalcTarSum(); } 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 ?? '', '0.000', true)}</td> <td>${numberInput('wastage', preset.wastage ?? '')}</td> <td class="actions-col"><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 = canonStock(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 = canonStock(stkSel.value || 'Yarn'); populateByStock(curr, ytSel, dnSel, clSel); ytSel.value=''; dnSel.value=''; clSel.value=''; }); hookRowForTarAndWeight(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=''); } 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); } if (Array.isArray(existing) && existing.length){ for(const r of existing){ makeRow(r.stock_type || 'Yarn', r); } } else { for(let i=0;i<4;i++) makeRow('Yarn', {}); } addBtn.addEventListener('click',()=>makeRow('Yarn', {})); document.getElementById('bqForm').addEventListener('submit', ()=>{ document.querySelectorAll('#tbodyYarns tr').forEach(tr=>{ const evt = new Event('input'); const tarInp = tr.querySelector('input[name="total_tar[]"]'); if (tarInp) tarInp.dispatchEvent(evt); }); refreshDisplayName(); }); </script> </body> </html>