« Back to History
quality_list.php
|
20260722_120325.php
Initial Domain Snapshot
Copy Code
<?php /* ========================================================= Quality List (company-scoped) + Search + Pagination - Shows rows from `qualities` - Quick-add by name (module-only change) with auto-code generation - Handles NOT NULL numeric columns by inserting 0 - View + Edit buttons (new tabs) - Uses global main.css (no page CSS) ========================================================= */ error_reporting(E_ALL); ini_set('display_errors', 1); /* ---------- Auth + DB ---------- */ require __DIR__ . '/modules/auth/page_acl.php'; $ctx = page_require_access('quality_list'); // Owner bypass; others need grant $u = $ctx['user']; $company_id = (int)$ctx['company_id']; $pdo = isset($ctx['pdo']) && $ctx['pdo'] instanceof PDO ? $ctx['pdo'] : null; if (!$pdo) { require __DIR__ . '/core/db.php'; $pdo = $GLOBALS['pdo']; } /* Header/Footer auth-skip flags (if partials respect them) */ if (!defined('MM_SKIP_HEADER_AUTH')) define('MM_SKIP_HEADER_AUTH', true); if (!defined('MM_SKIP_FOOTER_AUTH')) define('MM_SKIP_FOOTER_AUTH', true); /* ---------- Safe include + debug ---------- */ function mm_include_safe($file){ $res = ['file'=>$file,'exists'=>file_exists($file),'included'=>false,'error'=>null,'headers'=>[]]; if (!$res['exists']) return $res; try { ob_start(); include $file; // include (not require) to avoid white-screen on fatal echo ob_get_clean(); $res['included'] = true; if (function_exists('headers_list')) $res['headers'] = headers_list(); } catch (Throwable $e) { @ob_end_clean(); $res['error'] = $e->getMessage(); echo '<div class="alert err" style="margin:12px 0;">' .'Include error in <b>'.htmlspecialchars($file).'</b>: '.htmlspecialchars($e->getMessage()) .'</div>'; } return $res; } $__HDR_PATH = __DIR__ . '/partials/header.php'; $__FTR_PATH = __DIR__ . '/partials/footer.php'; $__DEBUG = isset($_GET['debug']) && $_GET['debug'] == '1'; /* ---------- helpers ---------- */ function table_cols(PDO $pdo, $table){ $cols = []; try { foreach ($pdo->query("SHOW COLUMNS FROM `$table`")->fetchAll(PDO::FETCH_ASSOC) as $r) $cols[] = $r['Field']; } catch(Throwable $e) {} return $cols; } $cols = table_cols($pdo, 'qualities'); $has = fn($c) => in_array($c, $cols, true); function col_is_not_null(PDO $pdo, $table, $col) { try { $st = $pdo->prepare("SHOW COLUMNS FROM `$table` LIKE :col"); $st->execute([':col' => $col]); $r = $st->fetch(PDO::FETCH_ASSOC); if ($r && isset($r['Null'])) return (strtoupper($r['Null']) !== 'YES'); } catch(Throwable $e) {} return false; } function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function col($row, $names, $fallback=''){ foreach ((array)$names as $n) if (array_key_exists($n,$row) && $row[$n]!==null && $row[$n]!=='') return $row[$n]; return $fallback; } function get_next_autoinc(PDO $pdo, $table) { try { $db = $pdo->query("SELECT DATABASE()")->fetchColumn(); $st = $pdo->prepare("SELECT AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = :db AND TABLE_NAME = :tn"); $st->execute([':db' => $db, ':tn' => $table]); $ai = (int)$st->fetchColumn(); if ($ai > 0) return $ai; } catch(Throwable $e) {} return null; } function generate_quality_code(PDO $pdo, $prefix = 'Q') { $ai = get_next_autoinc($pdo, 'qualities'); if ($ai !== null) return $prefix . str_pad((int)$ai, 4, '0', STR_PAD_LEFT); // Q0007 return $prefix . time(); } /* ---------- Quick add (POST) ---------- */ $msg = ''; $err = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['_action']) && $_POST['_action'] === 'quick_add') { try { $name_in = trim((string)($_POST['name'] ?? '')); $name_col = $has('quality_name') ? 'quality_name' : 'name'; $name_val = ($name_in === '') ? null : $name_in; if ($name_val !== null) { $chkSql = "SELECT id FROM qualities WHERE company_id = :cid AND {$name_col} = :nm LIMIT 1"; $chk = $pdo->prepare($chkSql); $chk->execute([':cid' => $company_id, ':nm' => $name_val]); if ($chk->fetchColumn()) throw new RuntimeException('A quality with that name already exists for this company.'); } $cols_ins = ['company_id']; $ph = [':cid']; $binds = [':cid'=>$company_id]; // name $cols_ins[] = $name_col; if ($name_val === null) { $ph[] = 'NULL'; } else { $ph[]=':name'; $binds[':name'] = $name_val; } // code (if NOT NULL, generate; else NULL) $code_col_present = $has('code'); $code_not_null = $code_col_present ? col_is_not_null($pdo, 'qualities', 'code') : false; if ($code_col_present) { $cols_ins[] = 'code'; if ($code_not_null) { $ph[]=':code'; $binds[':code'] = generate_quality_code($pdo, 'Q'); } else { $ph[]='NULL'; } } // audit/default cols if ($has('created_by')) { $cols_ins[]='created_by'; $ph[]=':cb'; $binds[':cb']=(int)$ctx['user']['id']; } if ($has('updated_by')) { $cols_ins[]='updated_by'; $ph[]=':ub'; $binds[':ub']=(int)$ctx['user']['id']; } if ($has('created_at')) { $cols_ins[]='created_at'; $ph[]='NOW()'; } if ($has('updated_at')) { $cols_ins[]='updated_at'; $ph[]='NOW()'; } if ($has('active')) { $cols_ins[]='active'; $ph[]=':active'; $binds[':active']=1; } // ensure alt code columns are NULL if present (avoid unique conflicts) foreach (['quality_code','code'] as $maybe) { if ($has($maybe) && !in_array($maybe, $cols_ins, true)) { $cols_ins[]=$maybe; $ph[]='NULL'; } } // NOT NULL numeric columns -> 0, else NULL $numeric_candidates = [ 'rate_per_meter','pissing_cost','avg_per_day_pro','peak', 'default_rate_m','production_rate','avg_per_day_production','avg_per_day_prod' ]; foreach ($numeric_candidates as $scol) { if ($has($scol) && !in_array($scol, $cols_ins, true)) { $cols_ins[] = $scol; if (col_is_not_null($pdo, 'qualities', $scol)) { $ph[]=':'.$scol; $binds[':'.$scol]=0; } else { $ph[]='NULL'; } } } $sql = 'INSERT INTO qualities (' . implode(',', $cols_ins) . ') VALUES (' . implode(',', $ph) . ')'; $st = $pdo->prepare($sql); foreach ($binds as $k=>$v) { if ($v === null) $st->bindValue($k, null, PDO::PARAM_NULL); elseif (is_int($v)) $st->bindValue($k, $v, PDO::PARAM_INT); else $st->bindValue($k, (string)$v, PDO::PARAM_STR); } $st->execute(); $msg = 'Added successfully (company-scoped).'; } catch(Throwable $e) { if ($e instanceof PDOException && isset($e->errorInfo[1])) { $errno = (int)$e->errorInfo[1]; if ($errno === 1062) { $err = 'Duplicate entry detected. Please provide a different name or check existing qualities.'; } elseif ($errno === 1048) { $err = 'A required column could not be NULL. Please check table schema or contact admin.'; } else { $err = $e->getMessage(); } } else { $err = $e->getMessage(); } } } /* ---------- Inputs ---------- */ $q = trim((string)($_GET['q'] ?? '')); $pg = max(1, (int)($_GET['page'] ?? 1)); $pp = min(100, max(10, (int)($_GET['per'] ?? 20))); // rows per page /* ---------- WHERE ---------- */ $where = ["company_id = :cid"]; $params = [':cid' => $company_id]; if ($q !== '') { $searchParts = []; foreach (['quality_name','name','quality_code','quality_group','category','loom_type','design_no'] as $c) { if ($has($c)) $searchParts[] = "$c LIKE :q"; } if ($searchParts) { $where[] = '(' . implode(' OR ', $searchParts) . ')'; $params[':q'] = "%$q%"; } } $whereSql = $where ? ('WHERE ' . implode(' AND ', $where)) : ''; /* ---------- Count ---------- */ $total = 0; try { $stmt = $pdo->prepare("SELECT COUNT(*) FROM qualities $whereSql"); $stmt->execute($params); $total = (int)$stmt->fetchColumn(); } catch(Throwable $e){} /* ---------- Fetch page ---------- */ $offset = ($pg - 1) * $pp; $orderBy = $has('created_at') ? 'created_at DESC' : 'id DESC'; $sql = "SELECT * FROM qualities $whereSql ORDER BY $orderBy LIMIT :lim OFFSET :off"; $stmt = $pdo->prepare($sql); foreach ($params as $k=>$v) $stmt->bindValue($k, $v); $stmt->bindValue(':lim', $pp, PDO::PARAM_INT); $stmt->bindValue(':off', $offset, PDO::PARAM_INT); $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: []; /* ---------- HEADER include ---------- */ $__hdr = mm_include_safe($__HDR_PATH); if ($__DEBUG) { echo '<pre style="background:#0b0f10;color:#8df58d;padding:10px;border-radius:8px;margin:12px 0;white-space:pre-wrap">'; echo "DEBUG: /erp/quality_list.php\n"; echo "User: ".h($u['name'] ?? 'N/A')." | Company: ".$company_id."\n"; echo "HEADER exists: ".($__hdr['exists']?'yes':'no')." included: ".($__hdr['included']?'yes':'no')."\n"; if ($__hdr['error']) echo "HEADER error: ".$__hdr['error']."\n"; $loc = array_values(array_filter(($__hdr['headers']??[]), fn($h)=>stripos($h,'Location:')===0)); if ($loc) echo ">>> Location header sent by header.php: {$loc[0]}\n"; echo "</pre>"; } ?> <!-- ========================= PAGE BODY (main.css classes) ========================= --> <div class="wrap container-fluid py-4"> <div class="card border-0 shadow-sm mb-4"> <div class="card-body d-flex align-items-center justify-content-between py-3"> <div class="d-flex align-items-center gap-3"> <div class="rounded-3 shadow-sm d-flex align-items-center justify-content-center text-white fw-bold" style="width:45px; height:45px; background: linear-gradient(135deg,#34A853,#A7D8DE);"> MM </div> <div> <h4 class="fw-bold m-0 text-dark">Quality Master List</h4> <small class="text-muted">Company ID: <span class="badge bg-light text-dark border"><?= (int)$company_id ?></span></small> </div> </div> <a href="quality_add.php" target="_blank" class="btn btn-primary px-4 fw-bold shadow-sm"> <i class="bi bi-plus-lg me-2"></i> Add New Quality </a> </div> </div> <?php if ($msg): ?> <div class="alert alert-success border-0 border-start border-4 border-success shadow-sm mb-4"><?= h($msg) ?></div> <?php endif; ?> <?php if ($err): ?> <div class="alert alert-danger border-0 border-start border-4 border-danger shadow-sm mb-4"><?= h($err) ?></div> <?php endif; ?> <div class="row g-4 mb-4"> <div class="col-lg-7"> <div class="card border-0 shadow-sm h-100"> <div class="card-body"> <form method="get" class="row g-2"> <div class="col-md-7"> <div class="input-group"> <span class="input-group-text bg-light border-end-0"><i class="bi bi-search text-muted"></i></span> <input class="form-control border-start-0 ps-0 shadow-none" type="text" name="q" value="<?= h($q) ?>" placeholder="Search name, code, group..."> </div> </div> <div class="col-md-3"> <select name="per" class="form-select shadow-none"> <?php foreach([10,20,50,100] as $opt): ?> <option value="<?=$opt?>" <?= $pp===$opt?'selected':'' ?>><?=$opt?> / page</option> <?php endforeach; ?> </select> </div> <div class="col-md-2"> <button class="btn btn-dark w-100 shadow-sm" type="submit">Filter</button> </div> </form> </div> </div> </div> <div class="col-lg-5"> <div class="card border-0 shadow-sm h-100 bg-light border-start border-primary border-4"> <div class="card-body"> <form method="post" class="row g-2 align-items-center"> <input type="hidden" name="_action" value="quick_add"> <div class="col-8"> <input class="form-control shadow-none border-0" type="text" name="name" required placeholder="<?= $has('quality_name') ? 'Quick Name (quality_name)' : 'Quick Name (name)' ?>"> </div> <div class="col-4 text-end"> <button class="btn btn-primary w-100 fw-bold" type="submit">Quick Add</button> </div> </form> </div> </div> </div> </div> <div class="card border-0 shadow-sm overflow-hidden"> <div class="table-responsive"> <table class="table table-hover align-middle mb-0 small"> <thead class="bg-light text-muted text-uppercase fw-bold" style="font-size: 0.75rem;"> <tr> <th class="ps-4">ID</th> <th>Code / Name</th> <th>Group / Loom</th> <th>Rate (₹/m)</th> <th>Pasaria (WJ|WOJ|J)</th> <th>Pissing (P|S)</th> <th>Status</th> <th>Created At</th> <th class="text-end pe-4">Actions</th> </tr> </thead> <tbody> <?php if (!$rows): ?> <tr><td colspan="9" class="text-center py-5 text-muted">No qualities found.</td></tr> <?php else: foreach($rows as $r): $id = (int)$r['id']; $code = col($r, ['quality_code','code']); $name = col($r, ['quality_name','name'], '—'); $grp = col($r, ['quality_group','category']); $loom = col($r, ['loom_type','machine']); $rate = col($r, ['default_rate_m','production_rate','rate_per_meter']); // Complex column data $pas_wj = isset($r['pasaria_with_jog']) ? (float)$r['pasaria_with_jog'] : '-'; $pas_woj = isset($r['pasaria_without_jog']) ? (float)$r['pasaria_without_jog'] : '-'; $pas_j = isset($r['pasaria_jog']) ? (float)$r['pasaria_jog'] : '-'; $act = isset($r['active']) ? (int)$r['active'] : 1; $created = col($r, ['created_at']); ?> <tr> <td class="ps-4 text-muted">#<?= $id ?></td> <td> <div class="fw-bold text-dark"><?= h($name) ?></div> <div class="x-small text-muted font-monospace"><?= h($code) ?></div> </td> <td> <div class="text-primary fw-bold"><?= h($grp) ?></div> <div class="x-small text-muted italic"><?= h($loom) ?></div> </td> <td><span class="badge bg-success-soft text-success px-2 py-1 fs-6 fw-bold">₹<?= number_format((float)$rate, 2) ?></span></td> <td> <div class="d-flex gap-1 x-small font-monospace"> <span class="bg-light border px-1 rounded" title="With Jog"><?= $pas_wj ?></span> <span class="bg-light border px-1 rounded" title="Without Jog"><?= $pas_woj ?></span> <span class="bg-light border px-1 rounded" title="Jog Only"><?= $pas_j ?></span> </div> </td> <td> <div class="x-small"> <span class="text-muted">P:</span> <?= (float)($r['primary_pissing_rate']??0) ?> | <span class="text-muted">S:</span> <?= (float)($r['secondary_pissing_rate']??0) ?> </div> </td> <td> <span class="badge rounded-pill <?= $act ? 'bg-success' : 'bg-danger' ?>" style="font-size: 0.65rem;"> <?= $act ? 'ACTIVE' : 'INACTIVE' ?> </span> </td> <td class="text-muted" style="font-size: 0.75rem;"><?= h($created) ?></td> <td class="text-end pe-4"> <div class="btn-group btn-group-sm rounded-pill shadow-sm overflow-hidden"> <a class="btn btn-outline-secondary border-0" href="quality_view.php?id=<?= $id ?>" target="_blank"><i class="bi bi-eye"></i></a> <a class="btn btn-outline-primary border-0" href="quality_edit.php?id=<?= $id ?>" target="_blank"><i class="bi bi-pencil"></i></a> </div> </td> </tr> <?php endforeach; endif; ?> </tbody> </table> </div> <?php if ($pages > 1): $qs = $_GET; unset($qs['page']); $base = '?' . http_build_query($qs); ?> <div class="card-footer bg-white py-3"> <nav class="d-flex justify-content-between align-items-center"> <div class="text-muted x-small">Page <b><?= $pg ?></b> of <b><?= $pages ?></b></div> <ul class="pagination pagination-sm mb-0"> <li class="page-item <?= ($pg <= 1) ? 'disabled' : '' ?>"> <a class="page-link shadow-none" href="<?= $base . '&page=' . ($pg-1) ?>">Previous</a> </li> <?php $start = max(1, $pg-2); $end = min($pages, $pg+2); for($i=$start; $i<=$end; $i++): ?> <li class="page-item <?= ($i === $pg) ? 'active' : '' ?>"> <a class="page-link shadow-none" href="<?= $base . '&page=' . $i ?>"><?= $i ?></a> </li> <?php endfor; ?> <li class="page-item <?= ($pg >= $pages) ? 'disabled' : '' ?>"> <a class="page-link shadow-none" href="<?= $base . '&page=' . ($pg+1) ?>">Next</a> </li> </ul> </nav> </div> <?php endif; ?> </div> </div> <style> .bg-success-soft { background-color: rgba(25, 135, 84, 0.1); } .x-small { font-size: 0.72rem; } .pagination .page-link { border: none; color: #6c757d; font-weight: 500; padding: 6px 12px; } .pagination .page-item.active .page-link { background-color: #111; border-radius: 6px; color: #fff; } tr:hover { background-color: #f8faff !important; } .btn-group .btn:hover { background-color: #f1f3f5; } </style>