« Back to History
loom_machine_down_report.php
|
20260721_154033.php
Initial Bulk Import
Copy Code
<?php require_once __DIR__ . '/modules/auth/page_acl.php'; $ctx = page_require_access('machine_down_report'); $pdo = $ctx['pdo'] ?? null; $company_id = (int)($ctx['company_id'] ?? 0); $u = $ctx['user'] ?? null; if (!$pdo) { require_once __DIR__ . '/core/db.php'; $pdo = $GLOBALS['pdo']; } /* CSRF */ if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } /* ── Fetch khatas for this company ─────────────────────────────────────────── */ $st = $pdo->prepare("SELECT id, name, code, machine_from, machine_to FROM khatas WHERE company_id = ? AND is_active = 1 ORDER BY machine_from"); $st->execute([$company_id]); $khatas = $st->fetchAll(PDO::FETCH_ASSOC); /* ── Fetch down reasons ─────────────────────────────────────────────────────── */ $st = $pdo->prepare("SELECT id, reason_name FROM down_reasons WHERE company_id = ? AND is_active = 1 ORDER BY reason_name"); $st->execute([$company_id]); $reason_list = $st->fetchAll(PDO::FETCH_ASSOC); /* ── POST handler ───────────────────────────────────────────────────────────── */ $msg = ''; $msg_type = 'success'; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) { die('Invalid CSRF token'); } $report_date = $_POST['report_date'] ?? ''; $khata_id = (int)($_POST['khata_id'] ?? 0); $shift = in_array($_POST['shift'] ?? '', ['day','night']) ? $_POST['shift'] : 'day'; $reason_id = (int)($_POST['reason_id'] ?? 0); $remarks = substr(trim($_POST['remarks'] ?? ''), 0, 500); $machine_numbers = json_decode($_POST['machine_numbers'] ?? '[]', true); if (!is_array($machine_numbers)) { $machine_numbers = []; } // Keep only integers $machine_numbers = array_values(array_filter(array_map('intval', $machine_numbers))); // Validate khata range $khata_row = null; foreach ($khatas as $k) { if ((int)$k['id'] === $khata_id) { $khata_row = $k; break; } } if (!$report_date || !$khata_row || !$shift || $reason_id <= 0 || empty($machine_numbers)) { $msg = 'Sabhi fields required hain aur kam se kam ek machine select karein.'; $msg_type = 'danger'; } else { $mfrom = (int)$khata_row['machine_from']; $mto = (int)$khata_row['machine_to']; $invalid = array_filter($machine_numbers, fn($n) => $n < $mfrom || $n > $mto); if ($invalid) { $msg = 'Kuch machine numbers khata range ke bahar hain: ' . implode(', ', $invalid); $msg_type = 'danger'; } else { $ins = $pdo->prepare(" INSERT INTO machine_down_report (company_id, report_date, shift, machine_no, reason_id, remarks, created_by) VALUES (:cid, :dt, :sh, :mc, :rid, :rmk, :uid) ON DUPLICATE KEY UPDATE shift = VALUES(shift), reason_id = VALUES(reason_id), remarks = VALUES(remarks) "); foreach ($machine_numbers as $mno) { $ins->execute([ ':cid' => $company_id, ':dt' => $report_date, ':sh' => $shift, ':mc' => $mno, ':rid' => $reason_id, ':rmk' => $remarks, ':uid' => $u['id'] ?? null, ]); } $count = count($machine_numbers); $msg = $count . ' machine' . ($count > 1 ? 's' : '') . ' ka down entry save ho gaya!'; } } } /* ── Summary report query ──────────────────────────────────────────────────── */ $sum_date = $_GET['sum_date'] ?? date('Y-m-d'); $sum_shift = in_array($_GET['sum_shift'] ?? '', ['day','night','']) ? ($_GET['sum_shift'] ?? '') : ''; if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $sum_date)) { $sum_date = date('Y-m-d'); } $sum_sql = " SELECT dr.reason_name, COUNT(mdr.id) AS machine_count, GROUP_CONCAT(mdr.machine_no ORDER BY mdr.machine_no SEPARATOR ', ') AS machine_list, GROUP_CONCAT( CASE WHEN TRIM(mdr.remarks) != '' THEN CONCAT('M', mdr.machine_no, ': ', TRIM(mdr.remarks)) END ORDER BY mdr.machine_no SEPARATOR ' | ' ) AS remarks_detail FROM machine_down_report mdr JOIN down_reasons dr ON dr.id = mdr.reason_id WHERE mdr.company_id = :cid AND mdr.report_date = :dt "; $sum_params = [':cid' => $company_id, ':dt' => $sum_date]; if ($sum_shift !== '') { $sum_sql .= " AND mdr.shift = :sh "; $sum_params[':sh'] = $sum_shift; } $sum_sql .= " GROUP BY dr.id, dr.reason_name ORDER BY machine_count DESC"; $sum_st = $pdo->prepare($sum_sql); $sum_st->execute($sum_params); $summary_rows = $sum_st->fetchAll(PDO::FETCH_ASSOC); // Total = distinct machines (ek machine multiple reasons me ho sakti hai) $sum_total_sql = " SELECT COUNT(DISTINCT mdr.machine_no) AS unique_count FROM machine_down_report mdr WHERE mdr.company_id = :cid AND mdr.report_date = :dt "; if ($sum_shift !== '') { $sum_total_sql .= " AND mdr.shift = :sh"; } $sum_tot_st = $pdo->prepare($sum_total_sql); $sum_tot_st->execute($sum_params); $sum_total = (int)$sum_tot_st->fetchColumn(); /* ── Build khatas JSON for JS ───────────────────────────────────────────────── */ $khatas_json = json_encode(array_values($khatas)); require_once __DIR__ . '/partials/header.php'; ?> <div class="container-fluid mt-3" style="max-width:860px;"> <div class="card mb-3 border-0 shadow-sm"> <div class="card-body py-3 d-flex justify-content-between align-items-center"> <h5 class="mb-0 fw-bold">Machine Down Entry</h5> <button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#summaryModal"> 📊 Summary Report </button> </div> </div> <?php if ($msg): ?> <div class="alert alert-<?= $msg_type ?> py-2"><?= htmlspecialchars($msg) ?></div> <?php endif; ?> <div class="card border-0 shadow-sm"> <div class="card-body"> <form method="POST" id="downForm"> <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>"> <input type="hidden" name="machine_numbers" id="machine_numbers" value="[]"> <div class="row g-3 mb-4"> <!-- Date --> <div class="col-6 col-md-3"> <label class="form-label fw-semibold small">Date</label> <input type="date" name="report_date" class="form-control" value="<?= htmlspecialchars($_POST['report_date'] ?? date('Y-m-d')) ?>" max="<?= date('Y-m-d') ?>" required> </div> <!-- Shift --> <div class="col-6 col-md-3"> <label class="form-label fw-semibold small">Shift</label> <select name="shift" class="form-select" required> <option value="day" <?= ($_POST['shift'] ?? 'day') === 'day' ? 'selected' : '' ?>>☀ Day</option> <option value="night" <?= ($_POST['shift'] ?? 'day') === 'night' ? 'selected' : '' ?>>☾ Night</option> </select> </div> <!-- Khata --> <div class="col-6 col-md-3"> <label class="form-label fw-semibold small">Khata</label> <select name="khata_id" id="khata_sel" class="form-select" required onchange="buildPicker()"> <option value="">-- Select --</option> <?php foreach ($khatas as $k): ?> <option value="<?= $k['id'] ?>" data-from="<?= $k['machine_from'] ?>" data-to="<?= $k['machine_to'] ?>" <?= ((int)($_POST['khata_id'] ?? 0) === (int)$k['id']) ? 'selected' : '' ?>> <?= htmlspecialchars($k['name']) ?> (<?= $k['machine_from'] ?>–<?= $k['machine_to'] ?>) </option> <?php endforeach; ?> </select> </div> <!-- Reason --> <div class="col-6 col-md-3"> <label class="form-label fw-semibold small">Reason</label> <select name="reason_id" class="form-select" required> <option value="">-- Select --</option> <?php foreach ($reason_list as $r): ?> <option value="<?= $r['id'] ?>" <?= ((int)($_POST['reason_id'] ?? 0) === (int)$r['id']) ? 'selected' : '' ?>> <?= htmlspecialchars($r['reason_name']) ?> </option> <?php endforeach; ?> </select> </div> <!-- Remarks --> <div class="col-6 col-md-3"> <label class="form-label fw-semibold small">Remarks</label> <input type="text" name="remarks" class="form-control" value="<?= $msg_type === 'success' ? '' : htmlspecialchars($_POST['remarks'] ?? '') ?>" maxlength="500"> </div> </div> <!-- Machine Picker (JS rendered) --> <div id="pickerWrap" class="d-none"> <!-- Mode toggle --> <div class="d-flex gap-2 mb-3 align-items-center flex-wrap"> <span class="fw-semibold small">Select Mode:</span> <div class="btn-group btn-group-sm" role="group"> <input type="radio" class="btn-check" name="pick_mode" id="mode_seq" value="seq" checked onchange="buildPicker()"> <label class="btn btn-outline-primary" for="mode_seq">Sequence (4-combo)</label> <input type="radio" class="btn-check" name="pick_mode" id="mode_ind" value="ind" onchange="buildPicker()"> <label class="btn btn-outline-primary" for="mode_ind">Individual Machine</label> </div> <button type="button" class="btn btn-sm btn-outline-secondary ms-auto" onclick="clearSelection()">Clear</button> </div> <!-- Sequence buttons --> <div id="seq_area"> <div class="small text-muted mb-2">Click karein — ek ya anek select kar sakte hain:</div> <div id="seq_buttons" class="d-flex flex-wrap gap-2 mb-3"></div> </div> <!-- Individual buttons --> <div id="ind_area" class="d-none"> <div class="small text-muted mb-2">Machine number select karein:</div> <div id="ind_buttons" class="d-flex flex-wrap gap-1 mb-3"></div> </div> <!-- Selection summary --> <div id="sel_summary" class="alert alert-info py-2 small d-none"></div> </div> <div id="noKhata" class="alert alert-secondary py-2 small"> Pehle khata select karein, phir machines dikhengi. </div> <div class="mt-3"> <button type="submit" class="btn btn-danger px-4" onclick="return prepareSubmit()"> ✓ Save Down Entry </button> </div> </form> </div> </div> </div> <script> const SEQ_SIZE = 4; let selected = new Set(); // selected machine numbers function getKhata() { const sel = document.getElementById('khata_sel'); const opt = sel.options[sel.selectedIndex]; if (!opt || !opt.value) return null; return { id: opt.value, from: parseInt(opt.dataset.from), to: parseInt(opt.dataset.to) }; } function getMode() { return document.querySelector('input[name=pick_mode]:checked')?.value ?? 'seq'; } function buildPicker() { const kh = getKhata(); document.getElementById('noKhata').classList.toggle('d-none', !!kh); document.getElementById('pickerWrap').classList.toggle('d-none', !kh); if (!kh) return; selected.clear(); const mode = getMode(); document.getElementById('seq_area').classList.toggle('d-none', mode !== 'seq'); document.getElementById('ind_area').classList.toggle('d-none', mode !== 'ind'); if (mode === 'seq') buildSeqButtons(kh); else buildIndButtons(kh); updateSummary(); } function buildSeqButtons(kh) { const wrap = document.getElementById('seq_buttons'); wrap.innerHTML = ''; for (let s = kh.from; s <= kh.to; s += SEQ_SIZE) { const e = Math.min(s + SEQ_SIZE - 1, kh.to); const label = s + '–' + e; const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'btn btn-outline-secondary btn-sm seq-btn'; btn.dataset.from = s; btn.dataset.to = e; btn.textContent = label; btn.onclick = () => toggleSeq(btn, s, e); wrap.appendChild(btn); } } function buildIndButtons(kh) { const wrap = document.getElementById('ind_buttons'); wrap.innerHTML = ''; for (let n = kh.from; n <= kh.to; n++) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'btn btn-outline-secondary btn-sm ind-btn'; btn.dataset.num = n; btn.textContent = n; btn.style.minWidth = '46px'; btn.onclick = () => toggleInd(btn, n); wrap.appendChild(btn); } } function toggleSeq(btn, from, to) { // Check if ALL in range selected let allSelected = true; for (let n = from; n <= to; n++) { if (!selected.has(n)) { allSelected = false; break; } } if (allSelected) { for (let n = from; n <= to; n++) selected.delete(n); btn.classList.remove('btn-danger'); btn.classList.add('btn-outline-secondary'); } else { for (let n = from; n <= to; n++) selected.add(n); btn.classList.remove('btn-outline-secondary'); btn.classList.add('btn-danger'); } updateSummary(); } function toggleInd(btn, num) { if (selected.has(num)) { selected.delete(num); btn.classList.remove('btn-danger'); btn.classList.add('btn-outline-secondary'); } else { selected.add(num); btn.classList.remove('btn-outline-secondary'); btn.classList.add('btn-danger'); } updateSummary(); } function clearSelection() { selected.clear(); document.querySelectorAll('.seq-btn, .ind-btn').forEach(b => { b.classList.remove('btn-danger'); b.classList.add('btn-outline-secondary'); }); updateSummary(); } function updateSummary() { const box = document.getElementById('sel_summary'); if (selected.size === 0) { box.classList.add('d-none'); return; } const sorted = [...selected].sort((a,b) => a-b); box.classList.remove('d-none'); box.innerHTML = '<strong>' + selected.size + ' machine(s) selected:</strong> ' + sorted.join(', '); } function prepareSubmit() { if (selected.size === 0) { alert('Kam se kam ek machine select karein.'); return false; } document.getElementById('machine_numbers').value = JSON.stringify([...selected]); return true; } // Init on page load if khata already selected (after POST error) document.addEventListener('DOMContentLoaded', () => { if (document.getElementById('khata_sel').value) buildPicker(); }); </script> <!-- ═══════════════ Summary Report Modal ═══════════════ --> <div class="modal fade" id="summaryModal" tabindex="-1" aria-labelledby="summaryModalLabel" aria-hidden="true"> <div class="modal-dialog modal-lg modal-dialog-centered"> <div class="modal-content"> <div class="modal-header bg-primary text-white"> <h5 class="modal-title" id="summaryModalLabel">📊 Machine Down Summary</h5> <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button> </div> <div class="modal-body"> <!-- Filter form inside modal --> <form method="GET" class="row g-2 align-items-end mb-4"> <div class="col-auto"> <label class="form-label small fw-semibold mb-1">Date</label> <input type="date" name="sum_date" class="form-control form-control-sm" value="<?= htmlspecialchars($sum_date) ?>" max="<?= date('Y-m-d') ?>"> </div> <div class="col-auto"> <label class="form-label small fw-semibold mb-1">Shift</label> <select name="sum_shift" class="form-select form-select-sm"> <option value="" <?= $sum_shift === '' ? 'selected' : '' ?>>Both Shifts</option> <option value="day" <?= $sum_shift === 'day' ? 'selected' : '' ?>>☀ Day</option> <option value="night" <?= $sum_shift === 'night' ? 'selected' : '' ?>>☾ Night</option> </select> </div> <div class="col-auto"> <button type="submit" class="btn btn-sm btn-primary" onclick="document.getElementById('openSummary').value='1'"> Load </button> <input type="hidden" name="open_summary" id="openSummary" value="0"> </div> </form> <?php if (empty($summary_rows)): ?> <div class="text-center py-4 text-muted"> <?= htmlspecialchars($sum_date) ?> ke liye koi down entry nahi mili. </div> <?php else: ?> <!-- Reason-wise summary cards --> <div class="row g-3 mb-4"> <?php foreach ($summary_rows as $row): ?> <div class="col-6 col-md-4"> <div class="card border-danger text-center h-100"> <div class="card-body py-3"> <div class="display-6 fw-bold text-danger"><?= (int)$row['machine_count'] ?></div> <div class="fw-semibold mt-1"><?= htmlspecialchars($row['reason_name']) ?></div> <div class="text-muted small mt-1">machine<?= (int)$row['machine_count'] > 1 ? 's' : '' ?> band</div> </div> </div> </div> <?php endforeach; ?> <!-- Total card --> <div class="col-6 col-md-4"> <div class="card border-dark bg-dark text-white text-center h-100"> <div class="card-body py-3"> <div class="display-6 fw-bold"><?= $sum_total ?></div> <div class="fw-semibold mt-1">Total</div> <div class="small mt-1 opacity-75">machines down</div> </div> </div> </div> </div> <!-- Detail table --> <table class="table table-sm table-bordered mb-0"> <thead class="table-dark"> <tr> <th>Reason</th> <th class="text-center" style="width:80px">Count</th> <th>Machines</th> </tr> </thead> <tbody> <?php foreach ($summary_rows as $row): ?> <tr> <td class="fw-semibold"><?= htmlspecialchars($row['reason_name']) ?></td> <td class="text-center"> <span class="badge bg-danger"><?= (int)$row['machine_count'] ?></span> </td> <td class="small"> <span class="text-muted"><?= htmlspecialchars($row['machine_list']) ?></span> <?php if (!empty($row['remarks_detail']) && stripos($row['reason_name'], 'other') !== false): ?> <div class="mt-1"> <?php foreach (explode(' | ', $row['remarks_detail']) as $rpart): ?> <span class="badge bg-warning text-dark me-1 mb-1 fw-normal"> <?= htmlspecialchars($rpart) ?> </span> <?php endforeach; ?> </div> <?php endif; ?> </td> </tr> <?php endforeach; ?> <tr class="table-dark fw-bold"> <td>Total</td> <td class="text-center"><?= $sum_total ?></td> <td></td> </tr> </tbody> </table> <?php endif; ?> </div><!-- /modal-body --> </div> </div> </div> <script> // Auto-open modal if sum_date/sum_shift was set via GET <?php if (isset($_GET['sum_date']) || isset($_GET['sum_shift'])): ?> document.addEventListener('DOMContentLoaded', () => { new bootstrap.Modal(document.getElementById('summaryModal')).show(); }); <?php endif; ?> </script> <?php require_once __DIR__ . '/partials/footer.php'; ?>