« Back to History
taka_edit.php
|
20260723_000646.php
Initial Domain Snapshot
Copy Code
<?php /* ============================================================================= File: taka_edit.php Purpose: View + Edit + Update + Safe Delete (archive) for production_entry Includes: - Machine No / Khata Name / Quality Name in grid - Karigar list from loom_karigar_master (khata-wise) - Qualities from quality_rates - Meter Total lock + lines sum must equal original total - Delete moves row into production_entry_deleted (auto-creates) Scope: Page-local only (no global/base changes) Presentation edits only: moved inline styles to main.css and added header/footer includes. ============================================================================= */ error_reporting(E_ALL); ini_set('display_errors', 1); /* ====================================================================== SECTION 0: Auth + PDO bootstrap (scoped) ====================================================================== */ require __DIR__ . '/modules/auth/auth.php'; require_login(); $u = auth_user(); $company_id = (int)$u['company_id']; $user_id = (int)$u['id']; $pdo = $GLOBALS['pdo'] ?? null; if (!$pdo) { require __DIR__ . '/core/db.php'; } /* ====================================================================== SECTION 1: Helpers ====================================================================== */ function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function gv($k,$d=null){ return isset($_GET[$k])?trim((string)$_GET[$k]):$d; } function pv($k,$d=null){ return isset($_POST[$k])?trim((string)$_POST[$k]):$d; } $alerts = []; $errors = []; /* small helpers for metadata tables */ function _table_exists(PDO $pdo, $name){ try{ $st=$pdo->prepare("SHOW TABLES LIKE ?"); $st->execute([$name]); return (bool)$st->fetchColumn(); }catch(Throwable $e){ return false; } } function _col_exists(PDO $pdo,$tbl,$col){ try{ $st=$pdo->prepare("SHOW COLUMNS FROM `$tbl` LIKE ?"); $st->execute([$col]); return (bool)$st->fetch(); }catch(Throwable $e){ return false; } } function _fetch_map(PDO $pdo, $sql, $params=[]){ $map=[]; try{ $st=$pdo->prepare($sql); $st->execute($params); foreach($st as $r){ $map[(int)$r['id']] = (string)$r['name']; } }catch(Throwable $e){} return $map; } /* ====================================================================== SECTION 1B: Ensure archive table exists (page-local safety) ====================================================================== */ try { $pdo->exec(" CREATE TABLE IF NOT EXISTS `production_entry_deleted` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `company_id` BIGINT NOT NULL, `original_id` BIGINT NOT NULL, `entry_date` date DEFAULT NULL, `machine_id` bigint DEFAULT NULL, `khata_id` bigint DEFAULT NULL, `quality_id` bigint DEFAULT NULL, `taka_no` int DEFAULT 0, `pbn` int DEFAULT 0, `sbn` int DEFAULT 0, `extra_mtr` decimal(10,2) DEFAULT 0, `lines_json` longtext, `meter_total` decimal(10,2) DEFAULT 0, `weight` decimal(10,2) DEFAULT 0, `remark` varchar(255) DEFAULT NULL, `created_by` bigint DEFAULT NULL, `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT NULL, `deleted_by` bigint NOT NULL, `deleted_at` datetime NOT NULL, `delete_reason` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_company_original` (`company_id`,`original_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); } catch(Throwable $e){ $errors[] = "Archive table ensure failed: ".$e->getMessage(); } /* ====================================================================== SECTION 2: CSRF token ====================================================================== */ if (session_status() !== PHP_SESSION_ACTIVE) session_start(); if (empty($_SESSION['csrf'])) { $_SESSION['csrf'] = bin2hex(random_bytes(16)); } $CSRF = $_SESSION['csrf']; /* ====================================================================== SECTION 3: POST handlers — update + delete (with archive) ====================================================================== */ if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { if (pv('csrf') !== $CSRF) { throw new Exception('Invalid CSRF token'); } $action = pv('action',''); if ($action === 'delete') { $id = (int)pv('id',0); $reason = trim((string)pv('delete_reason','')); if ($id<=0) throw new Exception('Invalid record id for delete'); // fetch original row (scoped) $st = $pdo->prepare("SELECT * FROM production_entry WHERE company_id=? AND id=? LIMIT 1"); $st->execute([$company_id,$id]); $row = $st->fetch(PDO::FETCH_ASSOC); if (!$row) throw new Exception('Record not found'); // insert into archive (robust to missing pbn/sbn) $ins = $pdo->prepare(" INSERT INTO production_entry_deleted (company_id, original_id, entry_date, machine_id, khata_id, quality_id, taka_no, pbn, sbn, extra_mtr, lines_json, meter_total, weight, remark, created_by, created_at, updated_at, deleted_by, deleted_at, delete_reason) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) "); // ensure source row has safe values (in case those columns were dropped) $src_pbn = array_key_exists('pbn', $row) ? $row['pbn'] : 0; $src_sbn = array_key_exists('sbn', $row) ? $row['sbn'] : 0; $src_lines = (isset($row['lines_json']) && $row['lines_json'] !== null && $row['lines_json'] !== '') ? $row['lines_json'] : '[]'; $ok1 = $ins->execute([ (int)$row['company_id'], (int)$row['id'], $row['entry_date'] ?? null, $row['machine_id'] ?? null, $row['khata_id'] ?? null, $row['quality_id'] ?? null, $row['taka_no'] ?? 0, $src_pbn, $src_sbn, $row['extra_mtr'] ?? 0, $src_lines, $row['meter_total'] ?? 0, $row['weight'] ?? 0, $row['remark'] ?? null, $row['created_by'] ?? null, $row['created_at'] ?? null, $row['updated_at'] ?? null, $user_id, date('Y-m-d H:i:s'), ($reason!==''?$reason:null) ]); if (!$ok1) throw new Exception('Archive insert failed'); // delete original $del = $pdo->prepare("DELETE FROM production_entry WHERE company_id=? AND id=? LIMIT 1"); $del->execute([$company_id,$id]); if ($del->rowCount() !== 1) throw new Exception('Delete failed'); $alerts[] = "Taka #{$id} deleted and archived."; } if ($action === 'update') { $id = (int)pv('id', 0); if ($id<=0) throw new Exception('Invalid record id'); // Normalize entry_date $entry_date = pv('entry_date',''); if ($entry_date) { $ts = strtotime(str_replace('/','-',$entry_date)); if (!$ts) throw new Exception('Invalid entry_date'); $entry_date = date('Y-m-d',$ts); } $machine_id = (int)pv('machine_id', 0); $khata_id = (int)pv('khata_id', 0); $quality_id = (int)pv('quality_id', 0); // header/global quality $taka_no = (int)pv('taka_no', 0); $pbn = (int)pv('pbn', 0); $sbn = (int)pv('sbn', 0); $extra_mtr = (float)pv('extra_mtr', 0); /* ---- METER TOTAL LOCK ---- */ $meter_total_post = (float)pv('meter_total', 0); $meter_total_orig = (float)pv('orig_meter_total', $meter_total_post); if (abs($meter_total_post - $meter_total_orig) > 0.0001) { throw new Exception('Meter Total is locked and cannot be changed.'); } $meter_total = $meter_total_orig; $weight = (float)pv('weight', 0); $remark = pv('remark',''); /* ---- lines_json validation ---- */ $lines_json = pv('lines_json',''); $sum_lines = 0.0; if ($lines_json !== '') { $arr = json_decode($lines_json, true); if (json_last_error() !== JSON_ERROR_NONE || !is_array($arr)) { throw new Exception('lines_json must be a valid JSON array'); } foreach ($arr as &$x) { if (isset($x['karigar_id'])) $x['karigar_id'] = (int)$x['karigar_id']; if (isset($x['meter'])) $x['meter'] = (float)$x['meter']; if (isset($x['date'])) { $ts = strtotime(str_replace('/','-',$x['date'])); if ($ts) $x['date'] = date('Y-m-d',$ts); } if (isset($x['quality_id'])) unset($x['quality_id']); // global only $sum_lines += (float)($x['meter'] ?? 0); } unset($x); if (abs($sum_lines - $meter_total) > 0.01) { throw new Exception('Lines total (' . number_format($sum_lines,2) . ') must equal Meter Total (' . number_format($meter_total,2) . ').'); } $lines_json = json_encode($arr, JSON_UNESCAPED_UNICODE); } else { if (abs($meter_total) > 0.01) { throw new Exception('Lines cannot be empty when Meter Total is non-zero.'); } $lines_json = json_encode([], JSON_UNESCAPED_UNICODE); } // build update SET parts depending on which columns exist $set = [ "entry_date = ?", "machine_id = ?", "khata_id = ?", "quality_id = ?", "taka_no = ?", // pbn / sbn appended conditionally "extra_mtr = ?", "lines_json = ?", "meter_total = ?", "weight = ?", "remark = ?", "updated_at = NOW()" ]; $paramValues = []; // core fields in same order as set array above (we will insert pbn/sbn at right spot) $paramValues[] = $entry_date ?: null; $paramValues[] = $machine_id ?: null; $paramValues[] = $khata_id ?: null; $paramValues[] = $quality_id ?: null; $paramValues[] = $taka_no; // insert pbn / sbn into set if columns exist $has_pbn = _col_exists($pdo,'production_entry','pbn'); $has_sbn = _col_exists($pdo,'production_entry','sbn'); if ($has_pbn) { // append pbn into set and paramValues right after taka_no array_splice($set, 5, 0, "pbn = ?"); array_splice($paramValues, 5, 0, [$pbn]); } if ($has_sbn) { // find position after pbn if present else after taka_no $pos = ($has_pbn ? 6 : 5); array_splice($set, $pos, 0, "sbn = ?"); array_splice($paramValues, $pos, 0, [$sbn]); } // continue adding the rest (note indexes shift if pbn/sbn present) $paramValues[] = $extra_mtr; $paramValues[] = $lines_json; $paramValues[] = $meter_total; $paramValues[] = $weight; $paramValues[] = $remark; // final SQL $sql = "UPDATE production_entry SET " . implode(", ", $set) . " WHERE company_id=? AND id=?"; $paramValues[] = $company_id; $paramValues[] = $id; $st = $pdo->prepare($sql); $ok = $st->execute($paramValues); if ($ok) $alerts[] = "Record #{$id} updated."; else throw new Exception('Update failed (no rows affected)'); } } catch(Throwable $e){ $errors[] = $e->getMessage(); $alerts[] = "ERROR: ".$e->getMessage(); } } /* ====================================================================== SECTION 3B: AJAX endpoints (karigars + qualities) ====================================================================== */ function _jexit($arr,$code=200){ if (!headers_sent()) { header('Content-Type: application/json; charset=utf-8'); http_response_code($code); } echo json_encode($arr, JSON_UNESCAPED_UNICODE); exit; } if (gv('ajax') === 'karigars') { $kid = (int)gv('khata_id',0); $opts = []; try { if (_table_exists($pdo,'loom_karigar_master')) { $nameCol = _col_exists($pdo,'loom_karigar_master','name') ? 'name' : (_col_exists($pdo,'loom_karigar_master','karigar_name') ? 'karigar_name' : null); $khataName = null; foreach (['khata_master','loom_khata_master','khatas'] as $kt){ if (_table_exists($pdo,$kt) && _col_exists($pdo,$kt,'id') && _col_exists($pdo,$kt,'name')){ $st=$pdo->prepare("SELECT name FROM `$kt` WHERE company_id=? AND id=? LIMIT 1"); $st->execute([$company_id,$kid]); $khataName = $st->fetchColumn(); if ($khataName) break; } } if ($nameCol) { $hasKhataId = _col_exists($pdo,'loom_karigar_master','khata_id'); $hasKhataTx = _col_exists($pdo,'loom_karigar_master','khata'); if ($kid>0 && $hasKhataId) { $st = $pdo->prepare("SELECT id, $nameCol AS name FROM loom_karigar_master WHERE company_id=? AND khata_id=? ORDER BY $nameCol"); $st->execute([$company_id,$kid]); $opts = $st->fetchAll(PDO::FETCH_ASSOC); } if (!$opts && $kid>0 && $hasKhataTx && $khataName) { $st = $pdo->prepare("SELECT id, $nameCol AS name FROM loom_karigar_master WHERE company_id=? AND khata=? ORDER BY $nameCol"); $st->execute([$company_id,$khataName]); $opts = $st->fetchAll(PDO::FETCH_ASSOC); } if (!$opts) { $st = $pdo->prepare("SELECT id, $nameCol AS name FROM loom_karigar_master WHERE company_id=? ORDER BY $nameCol"); $st->execute([$company_id]); $opts = $st->fetchAll(PDO::FETCH_ASSOC); } } } } catch(Throwable $e){} _jexit(['ok'=>true,'options'=>$opts]); } if (gv('ajax') === 'qualities') { $opts = []; try { if (_table_exists($pdo,'quality_rates')) { $sql = "SELECT COALESCE(NULLIF(quality_id,0), id) AS id, quality_name AS name FROM quality_rates WHERE company_id=? ORDER BY quality_name"; $st = $pdo->prepare($sql); $st->execute([$company_id]); $opts = $st->fetchAll(PDO::FETCH_ASSOC); } } catch(Throwable $e){} _jexit(['ok'=>true,'options'=>$opts]); } /* ====================================================================== SECTION 4: Filters + Pagination (GET) ====================================================================== */ $page = max(1, (int)gv('page', 1)); $per_page = min(100, max(10, (int)gv('pp', 25))); $off = ($page - 1) * $per_page; $where = ["company_id = :cid"]; $args = [':cid' => $company_id]; if ($idf = (int)gv('id', 0)) { $where[] = "id = :id"; $args[':id'] = $idf; } if (($mid = gv('machine_id','')) !== '') { $where[] = "machine_id = :mid"; $args[':mid'] = (int)$mid; } if (($kid = gv('khata_id','')) !== '') { $where[] = "khata_id = :kid"; $args[':kid'] = (int)$kid; } if ($qid = (int)gv('quality_id', 0)) { $where[] = "quality_id = :qid"; $args[':qid'] = $qid; } /* >>> ADDED: Taka No filter <<< */ if (($tn = gv('taka_no','')) !== '') { $where[] = "taka_no = :taka_no"; $args[':taka_no'] = (int)$tn; } if ($df = gv('date_from','')) { $ts = strtotime(str_replace('/','-',$df)); if ($ts){ $where[]="entry_date >= :df"; $args[':df']=date('Y-m-d',$ts); } } if ($dt = gv('date_to','')) { $ts = strtotime(str_replace('/','-',$dt)); if ($ts){ $where[]="entry_date <= :dt"; $args[':dt']=date('Y-m-d',$ts); } } if ($q = gv('q','')) { $where[] = "(remark LIKE :q OR CAST(id AS CHAR) LIKE :q)"; $args[':q'] = '%'.$q.'%'; } $W = implode(' AND ', $where); // Count $total = 0; try { $stmt = $pdo->prepare("SELECT COUNT(*) FROM production_entry WHERE $W"); $stmt->execute($args); $total = (int)$stmt->fetchColumn(); } catch(Throwable $e){ $errors[] = $e->getMessage(); } /* --------------------------- FETCH ROWS — dynamic select --------------------------- */ $rows = []; try { // build select cols depending on which columns exist $selectCols = ['id','company_id','entry_date','machine_id','khata_id','quality_id','taka_no','extra_mtr','lines_json','meter_total','weight','remark','created_by','created_at','updated_at']; if (_col_exists($pdo,'production_entry','pbn')) $selectCols[] = 'pbn'; if (_col_exists($pdo,'production_entry','sbn')) $selectCols[] = 'sbn'; $sql = "SELECT " . implode(", ", $selectCols) . " FROM production_entry WHERE $W ORDER BY entry_date DESC, id DESC LIMIT :off, :pp"; $stmt = $pdo->prepare($sql); foreach($args as $k=>$v){ $stmt->bindValue($k, $v); } $stmt->bindValue(':off', $off, PDO::PARAM_INT); $stmt->bindValue(':pp', $per_page, PDO::PARAM_INT); $stmt->execute(); $raw = $stmt->fetchAll(PDO::FETCH_ASSOC); // Normalize rows so downstream code can always reference keys foreach ($raw as $r) { $r['pbn'] = array_key_exists('pbn', $r) ? $r['pbn'] : 0; $r['sbn'] = array_key_exists('sbn', $r) ? $r['sbn'] : 0; $r['lines_json'] = (array_key_exists('lines_json',$r) && $r['lines_json'] !== null && $r['lines_json'] !== '') ? $r['lines_json'] : '[]'; $r['extra_mtr'] = array_key_exists('extra_mtr',$r) ? $r['extra_mtr'] : 0; $r['meter_total'] = array_key_exists('meter_total',$r) ? $r['meter_total'] : 0; $r['weight'] = array_key_exists('weight',$r) ? $r['weight'] : 0; $rows[] = $r; } } catch(Throwable $e){ $errors[] = $e->getMessage(); } /* ---- Recently deleted (for quick verify) ---- */ $deleted_rows = []; try{ $st = $pdo->prepare("SELECT id, original_id, entry_date, machine_id, khata_id, quality_id, meter_total, deleted_by, deleted_at, delete_reason FROM production_entry_deleted WHERE company_id=? ORDER BY deleted_at DESC LIMIT 20"); $st->execute([$company_id]); $deleted_rows = $st->fetchAll(PDO::FETCH_ASSOC); }catch(Throwable $e){ /* optional */ } /* ====================================================================== SECTION 5: Lookup maps (Machine No / Khata Name / Quality Name) ====================================================================== */ $MACHINES = []; if (_table_exists($pdo,'machines')) { $col = _col_exists($pdo,'machines','machine_no') ? 'machine_no' : (_col_exists($pdo,'machines','mc_no') ? 'mc_no' : (_col_exists($pdo,'machines','machine_code') ? 'machine_code' : (_col_exists($pdo,'machines','code') ? 'code' : null))); if ($col) { $MACHINES = _fetch_map($pdo, "SELECT id, $col AS name FROM machines WHERE company_id=?", [$company_id]); } } $KHATAS = []; foreach (['khata_master','loom_khata_master','khatas'] as $kt) { if (_table_exists($pdo,$kt) && _col_exists($pdo,$kt,'id') && _col_exists($pdo,$kt,'name')) { $KHATAS = _fetch_map($pdo, "SELECT id, name FROM `$kt` WHERE company_id=? ORDER BY name", [$company_id]); if ($KHATAS) break; } } $QUALS = []; if (_table_exists($pdo,'quality_rates')) { $sql = "SELECT COALESCE(NULLIF(quality_id,0), id) AS id, quality_name AS name FROM quality_rates WHERE company_id=? ORDER BY quality_name"; $QUALS = _fetch_map($pdo, $sql, [$company_id]); } ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Loom Production — View & Edit</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Presentation: styles moved to main.css (paste the CSS block below into /erp/public/assets/css/main.css) --> <link rel="stylesheet" href="/erp/public/assets/css/main.css?v=20251002"> <!-- COPY the following CSS block into /erp/public/assets/css/main.css (namespace: page: loom-production-manage) --- BEGIN CSS --- /* page: loom-production-manage added: 2025-10-02 author: assistant */ :root{ --pri:#34A853; --bg:#F5FFF7; --muted:#5F6368; --accent:#A7D8DE; --hl:#FFF9C4; --sage:#D0E8D0; } body{margin:0; font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial; background:var(--bg); color:#222;} header{padding:14px 18px; background:var(--pri); color:#fff; display:flex; align-items:center; justify-content:space-between;} header h1{margin:0; font-size:18px;} .wrap{padding:16px;} .card{background:#fff; border:1px solid #e7e7e7; border-radius:12px; padding:14px; box-shadow:0 1px 2px rgba(0,0,0,.04); margin-bottom:14px;} .row{display:flex; flex-wrap:wrap; gap:10px;} .row .col{flex:1 1 180px; min-width:180px;} input,select,textarea{width:100%; padding:8px 10px; border:1px solid #d0d0d0; border-radius:10px; outline:none; background:#fff;} input:focus,select:focus,textarea:focus{border-color:var(--pri); box-shadow:0 0 0 2px rgba(52,168,83,.15);} .btn{display:inline-block; padding:8px 12px; border-radius:10px; border:1px solid var(--pri); color:#fff; background:var(--pri); text-decoration:none; cursor:pointer;} .btn.outline{background:#fff; color:var(--pri);} .btn.muted{border-color:var(--muted); background:#fff; color:#5F6368;} .btn.danger{border-color:#b00020; background:#b00020;} .btn.danger.outline{background:#fff; color:#b00020; border-color:#b00020;} .tbl{width:100%; border-collapse:separate; border-spacing:0; overflow:auto;} .tbl th,.tbl td{padding:8px 10px; border-bottom:1px solid #eee; text-align:left; vertical-align:top;} .tbl th{background:var(--sage); position:sticky; top:0; z-index:1;} .pill{display:inline-block; padding:2px 8px; background:var(--accent); border-radius:999px; font-size:12px;} .note{background:var(--hl); padding:8px 10px; border-radius:10px;} dialog{border:none; border-radius:14px; width:920px; max-width:95vw;} dialog::backdrop{background:rgba(0,0,0,.3);} .actions{display:flex; gap:8px; align-items:center; justify-content:flex-end; padding-top:8px;} .small{font-size:12px; color:#666;} .readonly{background:#f7f7f7;} .flex-left{display:flex; gap:8px; align-items:center;} /* --- END CSS --- */ --> </head> <body> <?php if (is_file(__DIR__.'/partials/header.php')) include __DIR__.'/partials/header.php'; ?> <header> <h1 style="margin:0;">Loom Production — View & Edit</h1> <div class="small">Company: <b><?php echo h($company_id); ?></b></div> </header> <div class="wrap"> <!-- Alerts --> <div class="alerts"> <?php foreach($alerts as $m): ?> <div class="a <?php echo (strpos($m,'ERROR:')===0?'err':'ok'); ?>" style="padding:8px 10px;border-radius:10px;margin-bottom:8px;<?php echo (strpos($m,'ERROR:')===0?'background:#fdecea;border:1px solid #f5c6cb;':'background:#e9f7ef;border:1px solid #c6efce;'); ?>"> <?php echo h($m); ?> </div> <?php endforeach; ?> </div> <!-- Filters --> <form class="card" method="get" action=""> <div class="row"> <div class="col"><label>ID <input type="number" name="id" value="<?php echo h(gv('id','')); ?>"> </label></div> <div class="col"><label>Machine ID <input type="number" name="machine_id" value="<?php echo h(gv('machine_id','')); ?>"> </label></div> <div class="col"><label>Khata ID <input type="number" name="khata_id" value="<?php echo h(gv('khata_id','')); ?>"> </label></div> <div class="col"><label>Quality ID <input type="number" name="quality_id" value="<?php echo h(gv('quality_id','')); ?>"> </label></div> <!-- ADDED: Taka No filter --> <div class="col"><label>Taka No <input type="number" name="taka_no" value="<?php echo h(gv('taka_no','')); ?>"> </label></div> <div class="col"><label>Date From <input type="text" name="date_from" placeholder="YYYY-MM-DD" value="<?php echo h(gv('date_from','')); ?>"> </label></div> <div class="col"><label>Date To <input type="text" name="date_to" placeholder="YYYY-MM-DD" value="<?php echo h(gv('date_to','')); ?>"> </label></div> <div class="col"><label>Search (ID/Remark) <input type="text" name="q" value="<?php echo h(gv('q','')); ?>"> </label></div> <div class="col"><label>Per Page <input type="number" min="10" max="100" name="pp" value="<?php echo h($per_page); ?>"> </label></div> <div class="col" style="align-self:end;"> <button class="btn" type="submit">Apply</button> <a class="btn outline" href="?">Reset</a> </div> </div> <div class="small" style="margin-top:6px;">Total: <b><?php echo h($total); ?></b></div> </form> <!-- Table --> <div class="card" style="overflow:auto;"> <table class="tbl"> <thead> <tr> <th>ID</th> <th>Entry Date</th> <th>Machine No.</th> <th>Khata</th> <th>Quality</th> <th>Taka#</th> <th>PBN</th> <th>SBN</th> <th>Extra mtr</th> <th>Meter Total</th> <th>Weight</th> <th>Remark</th> <th>Actions</th> </tr> </thead> <tbody> <?php if(!$rows): ?> <tr><td colspan="13"><div class="note">No records found.</div></td></tr> <?php else: foreach($rows as $r): ?> <tr> <td><?php echo h($r['id']); ?></td> <td><?php echo h($r['entry_date']); ?></td> <td><?php echo h($MACHINES[(int)$r['machine_id']] ?? $r['machine_id']); ?></td> <td><?php echo h($KHATAS[(int)$r['khata_id']] ?? $r['khata_id']); ?></td> <td><?php echo h($QUALS[(int)$r['quality_id']] ?? $r['quality_id']); ?></td> <td><?php echo h($r['taka_no']); ?></td> <td><?php echo h($r['pbn'] ?? ''); ?></td> <td><?php echo h($r['sbn'] ?? ''); ?></td> <td><?php echo h($r['extra_mtr']); ?></td> <td><?php echo h($r['meter_total']); ?></td> <td><?php echo h($r['weight']); ?></td> <td><?php echo h($r['remark']); ?></td> <td> <div class="flex-left"> <button class="btn muted js-edit" data-row='<?php echo h(json_encode($r, JSON_UNESCAPED_UNICODE)); ?>'>Edit</button> <form method="post" class="del-form" onsubmit="return onDeleteSubmit(this, <?php echo (int)$r['id']; ?>);"> <input type="hidden" name="csrf" value="<?php echo h($CSRF); ?>"> <input type="hidden" name="action" value="delete"> <input type="hidden" name="id" value="<?php echo (int)$r['id']; ?>"> <input type="hidden" name="delete_reason" value=""> <button class="btn danger outline" type="submit" title="Delete Taka">Delete</button> </form> </div> </td> </tr> <?php endforeach; endif; ?> </tbody> </table> <!-- Pagination --> <?php $pages = max(1, (int)ceil($total / $per_page)); if ($pages > 1): $base = $_GET; unset($base['page']); $mk = function($p) use ($base){ $base['page']=$p; return '?'.http_build_query($base); }; ?> <div style="margin-top:10px; display:flex; gap:8px; flex-wrap:wrap;"> <?php for($p=1; $p<=$pages; $p++): ?> <a class="btn <?php echo $p===$page?'':'outline'; ?>" href="<?php echo h($mk($p)); ?>"> <?php echo $p; ?> </a> <?php endfor; ?> </div> <?php endif; ?> </div> <!-- Recently Deleted --> <div class="card"> <b>Recently Deleted Taka (archived)</b> <table class="tbl" style="margin-top:8px;"> <thead> <tr> <th>Archive ID</th> <th>Original ID</th> <th>Entry Date</th> <th>Machine</th> <th>Khata</th> <th>Quality</th> <th>Meter Total</th> <th>Deleted By</th> <th>Deleted At</th> <th>Reason</th> </tr> </thead> <tbody> <?php if (!$deleted_rows): ?> <tr><td colspan="10"><div class="note">No deleted records yet.</div></td></tr> <?php else: foreach($deleted_rows as $d): ?> <tr> <td><?php echo h($d['id']); ?></td> <td><?php echo h($d['original_id']); ?></td> <td><?php echo h($d['entry_date']); ?></td> <td><?php echo h($MACHINES[(int)$d['machine_id']] ?? $d['machine_id']); ?></td> <td><?php echo h($KHATAS[(int)$d['khata_id']] ?? $d['khata_id']); ?></td> <td><?php echo h($QUALS[(int)$d['quality_id']] ?? $d['quality_id']); ?></td> <td><?php echo h($d['meter_total']); ?></td> <td><?php echo h($d['deleted_by']); ?></td> <td><?php echo h($d['deleted_at']); ?></td> <td><?php echo h($d['delete_reason']); ?></td> </tr> <?php endforeach; endif; ?> </tbody> </table> </div> <!-- Edit Dialog with Visual Lines Editor --> <dialog id="dlg"> <form method="post" class="card" style="border:none; box-shadow:none; margin:0; padding:0;" id="edit_form"> <input type="hidden" name="action" value="update"> <input type="hidden" name="csrf" value="<?php echo h($CSRF); ?>"> <input type="hidden" name="id" id="f_id"> <div class="row"> <div class="col"><label>Entry Date <!-- CHANGED: date picker --> <input type="date" name="entry_date" id="f_entry_date"> </label></div> <div class="col"><label>Machine ID <input type="number" name="machine_id" id="f_machine_id"> <div class="small">Machine No: <span id="machine_no_view" class="pill"></span></div> </label></div> <div class="col"><label>Khata ID <input type="number" name="khata_id" id="f_khata_id"> <div class="small">Khata: <span id="khata_name_view" class="pill"></span></div> </label></div> <div class="col"><label>Quality (Header/Global) <!-- CHANGED: Quality Name dropdown (values = ids) --> <select name="quality_id" id="f_quality_id"></select> <div class="small">Quality: <span id="quality_name_view" class="pill"></span></div> </label></div> </div> <div class="row"> <div class="col"><label>Taka No <input type="number" name="taka_no" id="f_taka_no"> </label></div> <div class="col"><label>PBN <input type="number" name="pbn" id="f_pbn"> </label></div> <div class="col"><label>SBN <input type="number" name="sbn" id="f_sbn"> </label></div> <div class="col"><label>Extra Meter <input type="number" step="0.01" name="extra_mtr" id="f_extra_mtr"> </label></div> </div> <div class="row"> <div class="col"><label>Meter Total (Locked) <input class="readonly" type="number" step="0.01" name="meter_total" id="f_meter_total" readonly> <input type="hidden" name="orig_meter_total" id="f_meter_total_orig"> </label></div> <div class="col"><label>Weight <input type="number" step="0.01" name="weight" id="f_weight"> </label></div> <div class="col"><label>Remark <input type="text" name="remark" id="f_remark"> </label></div> </div> <!-- Lines Visual Editor (Date, Karigar, Meter) --> <div class="card" style="margin-top:12px;"> <div style="display:flex;justify-content:space-between;align-items:center;"> <b>Lines (Visual)</b> <div class="small">Fields: Date, Karigar (khata-wise), Meter</div> </div> <table class="tbl" id="lines_tbl"> <thead> <tr> <th style="width:140px;">Date</th> <th>Karigar</th> <th style="width:140px;">Meter</th> <th style="width:60px;"></th> </tr> </thead> <tbody id="lines_body"></tbody> </table> <!-- Lines Total / Expected Bar --> <div id="lines_total_bar" class="small" style="margin:8px 0; padding:8px 10px; border-radius:10px; background:#FFF9C4;"> Lines Total: <b id="lines_sum">0.00</b> / Expected: <b id="lines_expect">0.00</b> — <span id="lines_status">Mismatch</span> </div> <div class="actions"> <button class="btn outline" type="button" id="add_row_btn">+ Add Row</button> </div> </div> <!-- Hidden JSON actually submitted --> <textarea name="lines_json" id="f_lines_json" hidden></textarea> <!-- (Optional) Advanced JSON viewer/editor --> <details class="card" style="margin-top:10px;"> <summary>Advanced (view / paste JSON)</summary> <div class="row"> <div class="col" style="flex-basis:100%;"> <textarea rows="6" id="f_lines_json_text" placeholder='[{"karigar_id":9,"meter":10,"date":"2025-08-01"}]'></textarea> </div> </div> <div class="actions" style="justify-content:flex-start;"> <button class="btn muted" type="button" id="apply_json_btn">↻ Apply JSON to table</button> </div> </details> <div class="actions"> <button class="btn" id="btn_save" type="submit">Save</button> <button class="btn outline" type="button" onclick="document.getElementById('dlg').close()">Cancel</button> </div> <div class="small" style="padding:6px 2px; color:#5F6368;"> ID <span id="id_view" class="pill"></span> • Meter Total locked; Quality is global per entry. </div> </form> </dialog> <!-- Debug panel --> <?php if (gv('debug','0')==='1' && $errors): ?> <div class="card"> <div class="note"><b>Debug Messages</b></div> <pre style="white-space:pre-wrap;"><?php echo h(implode("\n", $errors)); ?></pre> </div> <?php endif; ?> </div> <?php if (is_file(__DIR__.'/partials/footer.php')) include __DIR__.'/partials/footer.php'; ?> <script> const MACHINES = <?php echo json_encode($MACHINES, JSON_UNESCAPED_UNICODE); ?>; const KHATAS = <?php echo json_encode($KHATAS, JSON_UNESCAPED_UNICODE); ?>; const QUALS = <?php echo json_encode($QUALS, JSON_UNESCAPED_UNICODE); ?>; const dlg = document.getElementById('dlg'); const form = document.getElementById('edit_form'); const linesBody = document.getElementById('lines_body'); const addBtn = document.getElementById('add_row_btn'); let karigarOptions = []; // khata-wise let defaultsForRow = {}; function escapeHtml(s){return (''+s).replace(/[&<>"']/g,m=>({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[m]));} function optionHTML(opts, selected){ let html = `<option value="">— Select —</option>`; (opts||[]).forEach(o=>{ const sel = String(o.id)===String(selected)?' selected':''; html += `<option value="${escapeHtml(o.id)}"${sel}>${escapeHtml(o.name||('#'+o.id))}</option>`; }); return html; } function fetchJSON(url){ return fetch(url,{credentials:'same-origin'}).then(r=>r.json()).catch(()=>({ok:false,options:[]})); } // Build quality dropdown options from QUALS map function qualityArray(){ const arr=[]; for (const id in QUALS){ arr.push({id:id, name:QUALS[id]}); } arr.sort((a,b)=> String(a.name).localeCompare(b.name)); return arr; } function buildQualityOptions(selected){ const qsel = document.getElementById('f_quality_id'); if (!qsel) return; qsel.innerHTML = optionHTML(qualityArray(), selected||''); } async function loadKarigars(khataId){ const d = await fetchJSON(`?ajax=karigars&khata_id=${encodeURIComponent(khataId||'')}`); karigarOptions = d.options || []; } function addLineRow(data={}){ const v = Object.assign({date: defaultsForRow.date || ''}, data); const tr = document.createElement('tr'); tr.innerHTML = ` <!-- CHANGED: date picker in lines --> <td><input type="date" class="ln-date" value="${escapeHtml(v.date||'')}"></td> <td><select class="ln-karigar"></select></td> <td><input type="number" class="ln-meter" step="0.01" value="${v.meter!=null?escapeHtml(v.meter):''}"></td> <td><button type="button" class="btn muted ln-del">✕</button></td> `; linesBody.appendChild(tr); const selK = tr.querySelector('.ln-karigar'); selK.innerHTML = optionHTML(karigarOptions, v.karigar_id); tr.querySelector('.ln-del').addEventListener('click', ()=>{ tr.remove(); syncLinesToJson(); }); tr.querySelectorAll('input,select').forEach(el=>el.addEventListener('input', syncLinesToJson)); } function buildFromJson(jsonTxt){ linesBody.innerHTML = ''; let arr = []; try { arr = JSON.parse(jsonTxt||'[]'); if (!Array.isArray(arr)) arr = []; } catch(e){ arr = []; } if (arr.length === 0) addLineRow({}); else arr.forEach(x=>{ addLineRow(x); }); syncLinesToJson(); } function round2(n){ return Math.round((n + Number.EPSILON)*100)/100; } function linesSumFromUI(){ let s = 0; document.querySelectorAll('#lines_body .ln-meter').forEach(inp=>{ const v = parseFloat(inp.value); if (!isNaN(v)) s += v; }); return round2(s); } function updateLinesSum(){ const expect = parseFloat(document.getElementById('f_meter_total_orig').value || document.getElementById('f_meter_total').value || '0') || 0; const sum = linesSumFromUI(); const bar = document.getElementById('lines_total_bar'); const elSum = document.getElementById('lines_sum'); const elExp = document.getElementById('lines_expect'); const elSt = document.getElementById('lines_status'); const btn = document.getElementById('btn_save'); elSum.textContent = sum.toFixed(2); elExp.textContent = expect.toFixed(2); if (Math.abs(sum - expect) <= 0.01) { elSt.textContent = 'OK'; bar.style.background = '#e9f7ef'; bar.style.border = '1px solid #c6efce'; btn.removeAttribute('disabled'); } else { elSt.textContent = 'Mismatch'; bar.style.background = '#fdecea'; bar.style.border = '1px solid #f5c6cb'; btn.setAttribute('disabled','disabled'); } } function syncLinesToJson(){ const rows = Array.from(linesBody.querySelectorAll('tr')).map(tr=>{ const o = {}; const d = tr.querySelector('.ln-date').value.trim(); const kid= tr.querySelector('.ln-karigar').value; const m = tr.querySelector('.ln-meter').value; if (d) o.date = d; if (kid) o.karigar_id = parseInt(kid,10); if (m !== '') o.meter = parseFloat(m); return o; }).filter(o => o.karigar_id && (o.meter!==undefined && !isNaN(o.meter))); document.getElementById('f_lines_json').value = JSON.stringify(rows); updateLinesSum(); } // header name pills function paintHeaderNames(){ const mid = document.getElementById('f_machine_id').value; const kid = document.getElementById('f_khata_id').value; const qid = document.getElementById('f_quality_id').value; document.getElementById('machine_no_view').textContent = MACHINES[mid] || mid || ''; document.getElementById('khata_name_view').textContent = KHATAS[kid] || kid || ''; document.getElementById('quality_name_view').textContent= QUALS[qid] || qid || ''; } ['f_machine_id','f_khata_id','f_quality_id'].forEach(id=>{ const el = document.getElementById(id); el && el.addEventListener('input', paintHeaderNames); }); // Refresh karigar dropdowns when khata changes document.getElementById('f_khata_id').addEventListener('change', async (e)=>{ await loadKarigars(e.target.value); document.querySelectorAll('#lines_body .ln-karigar').forEach(sel=>{ const curr = sel.value; sel.innerHTML = optionHTML(karigarOptions, curr); }); updateLinesSum(); paintHeaderNames(); }); // Optional JSON editor apply const applyBtn = document.getElementById('apply_json_btn'); if (applyBtn){ applyBtn.addEventListener('click', ()=>{ const t = document.getElementById('f_lines_json_text'); buildFromJson(t.value || '[]'); updateLinesSum(); }); } // Ensure hidden JSON is up-to-date on submit form.addEventListener('submit', ()=> syncLinesToJson()); // Bind Edit buttons document.querySelectorAll('.js-edit').forEach(btn=>{ btn.addEventListener('click', async ()=>{ const r = JSON.parse(btn.dataset.row); // Build quality dropdown BEFORE assigning value buildQualityOptions(r.quality_id ?? ''); for (const [k,v] of Object.entries({ id:'f_id', entry_date:'f_entry_date', machine_id:'f_machine_id', khata_id:'f_khata_id', quality_id:'f_quality_id', taka_no:'f_taka_no', pbn:'f_pbn', sbn:'f_sbn', extra_mtr:'f_extra_mtr', meter_total:'f_meter_total', weight:'f_weight', remark:'f_remark' })) { const el = document.getElementById(v); if (el) el.value = (r[k] ?? ''); } document.getElementById('f_meter_total').setAttribute('readonly','readonly'); document.getElementById('f_meter_total_orig').value = r.meter_total ?? 0; defaultsForRow = { date: (r.entry_date || '') }; await loadKarigars(r.khata_id || ''); buildFromJson(r.lines_json || '[]'); addBtn.onclick = ()=> addLineRow({}); paintHeaderNames(); updateLinesSum(); document.getElementById('id_view').textContent = r.id; dlg.showModal(); }); }); // Initialize quality dropdown once buildQualityOptions(''); // Delete submit handler with reason prompt function onDeleteSubmit(formEl, id){ const ok = confirm('Delete Taka #'+id+' ? This will move it to archive.'); if (!ok) return false; const why = prompt('Delete reason (optional):',''); if (why !== null) { const input = formEl.querySelector('input[name=delete_reason]'); if (input) input.value = why.trim(); } return true; } window.onDeleteSubmit = onDeleteSubmit; </script> </body> </html>