« Back to History
machine_avg_reportv2.php
|
20260721_154033.php
Initial Bulk Import
Copy Code
<?php /* machine_avg_report.php Final + Summary + Print options - Summary at top: per-machine sum_days, sum_meter, avg/day - Print Summary (prints only summary) and Print Full (prints the whole page) - Export CSV for both full and summary - Added: one-click update to machine_avg table (create + upsert summary rows) */ /* ---- Auth + DB bootstrap ---- */ require_once __DIR__ . '/modules/auth/page_acl.php'; $ctx = page_require_access('machine_avg_report'); $u = $ctx['user'] ?? null; $COMPANY_ID = (int)($ctx['company_id'] ?? 0); $USER_ID = (int)($u['id'] ?? 0); $pdo = $ctx['pdo'] ?? null; if (!($pdo instanceof PDO)) require __DIR__ . '/core/db.php'; if (!function_exists('h')) { function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } } /* ---- Inputs ---- */ $default_to = date('Y-m-d'); $default_from = date('Y-m-d', strtotime('-29 days')); $from = $_GET['from'] ?? $default_from; $to = $_GET['to'] ?? $default_to; $machine_filter = $_GET['machine_id'] ?? 'all'; $export = $_GET['export'] ?? null; $export_mode = $_GET['mode'] ?? 'full'; // 'full' or 'summary' $from_dt = DateTime::createFromFormat('Y-m-d', $from); $to_dt = DateTime::createFromFormat('Y-m-d', $to); if (!$from_dt || !$to_dt) { $from = $default_from; $to = $default_to; } /* ---- Load qualities (avg_per_day_pro) ---- */ $qualities = []; try { $qsql = "SELECT id, COALESCE(quality_name,'') AS qname, COALESCE(quality_code,'') AS qcode, COALESCE(avg_per_day_pro,0) AS per_day FROM qualities WHERE company_id = :cid"; $qstmt = $pdo->prepare($qsql); $qstmt->execute([':cid'=>$COMPANY_ID]); foreach ($qstmt->fetchAll(PDO::FETCH_ASSOC) as $r) { $qualities[(int)$r['id']] = [ 'name' => $r['qname'], 'code' => $r['qcode'], 'per_day' => (float)$r['per_day'] ]; } } catch (Exception $e) { $qualities = []; } /* ---- Load karigar names from loom_karigar_master ---- */ $karigar_names = []; try { $ksql = "SELECT id, COALESCE(karigar_name, '') AS name FROM loom_karigar_master WHERE company_id = :cid"; $kstmt = $pdo->prepare($ksql); $kstmt->execute([':cid'=>$COMPANY_ID]); foreach ($kstmt->fetchAll(PDO::FETCH_ASSOC) as $r) { $karigar_names[(int)$r['id']] = $r['name']; } } catch (Exception $e) { $karigar_names = []; } /* ---- Fetch production_entry rows for date range ---- */ $params = [':cid'=>$COMPANY_ID, ':from'=>$from, ':to'=>$to]; $sql = "SELECT id, machine_id, entry_date, meter_total, lines_json, quality_id FROM production_entry WHERE company_id = :cid AND entry_date BETWEEN :from AND :to"; if ($machine_filter !== 'all') { $sql .= " AND machine_id = :mid"; $params[':mid'] = (int)$machine_filter; } $sql .= " ORDER BY machine_id, entry_date, id"; try { $stmt = $pdo->prepare($sql); $stmt->execute($params); $prod_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (Exception $e) { $prod_rows = []; $error_msg = "DB error: " . $e->getMessage(); } /* ---- Fetch pissing entries for machines present ---- */ $machines_in_data = []; foreach ($prod_rows as $r) $machines_in_data[(int)$r['machine_id']] = true; $machines_list = array_keys($machines_in_data); $pissing_map = []; if (!empty($machines_list)) { $in = implode(',', array_map('intval', $machines_list)); $psql = "SELECT machine, entry_date FROM pissing_entry WHERE company_id = :cid AND entry_date BETWEEN :from AND :to AND machine IN ($in)"; try { $pstmt = $pdo->prepare($psql); $pstmt->execute([':cid'=>$COMPANY_ID, ':from'=>$from, ':to'=>$to]); foreach ($pstmt->fetchAll(PDO::FETCH_ASSOC) as $r) { $mid = (int)$r['machine']; $d = $r['entry_date']; $pissing_map[$mid][$d] = true; } } catch (Exception $e) { /* ignore */ } } /* ---- Aggregate per machine+date: total meter, per-karigar meters, quality votes, last_non_null ---- */ $agg = []; foreach ($prod_rows as $r) { $mid = (int)$r['machine_id']; $date = $r['entry_date']; $meter = (float)$r['meter_total']; if (!isset($agg[$mid])) $agg[$mid] = []; if (!isset($agg[$mid][$date])) $agg[$mid][$date] = [ 'meter' => 0.0, 'karigars' => [], // set of karigar ids 'per_karigar_meter' => [], // [karigar_id => meter_sum] 'quality_votes' => [], 'last_quality_id' => null ]; $agg[$mid][$date]['meter'] += $meter; // parse lines_json to extract karigar meters (if present) if (!empty($r['lines_json'])) { $json = json_decode($r['lines_json'], true); if (is_array($json)) { foreach ($json as $item) { if (is_array($item) && isset($item['karigar_id'])) { $kid = (int)$item['karigar_id']; $km = isset($item['meter']) ? (float)$item['meter'] : 0.0; if ($kid) { $agg[$mid][$date]['karigars'][$kid] = true; if (!isset($agg[$mid][$date]['per_karigar_meter'][$kid])) $agg[$mid][$date]['per_karigar_meter'][$kid] = 0.0; $agg[$mid][$date]['per_karigar_meter'][$kid] += $km; } } } } } // quality votes and safe last non-null assignment $qid = $r['quality_id'] !== null ? (int)$r['quality_id'] : null; if ($qid && $qid > 0) { if (!isset($agg[$mid][$date]['quality_votes'][$qid])) $agg[$mid][$date]['quality_votes'][$qid] = 0; $agg[$mid][$date]['quality_votes'][$qid] += 1; $agg[$mid][$date]['last_quality_id'] = $qid; // safe: only set when >0 } } /* ---- Build report applying per-karigar rules + pissing rules ---- */ $report = []; foreach ($agg as $mid => $dates) { $report[$mid] = ['rows'=>[], 'sum_meter'=>0.0, 'sum_days'=>0.0]; foreach ($dates as $date => $info) { $daily_total = $info['meter']; $per_karigar = $info['per_karigar_meter'] ?? []; $karigar_ids = array_keys($info['karigars'] ?? []); $kcount = count($karigar_ids); // pick quality_id (mode, tie -> last_non_null, fallback) $picked_qid = null; $raw_votes = $info['quality_votes'] ?? []; $clean_votes = []; foreach ($raw_votes as $k => $v) { $kint = (int)$k; if ($kint > 0) $clean_votes[$kint] = (int)$v; } if (!empty($clean_votes)) { $max = 0; $modes = []; foreach ($clean_votes as $qid => $cnt) { if ($cnt > $max) { $max = $cnt; $modes = [$qid]; } elseif ($cnt === $max) $modes[] = $qid; } if (count($modes) === 1) $picked_qid = $modes[0]; else { $last = $info['last_quality_id'] ?? null; if ($last && in_array((int)$last, $modes, true)) $picked_qid = (int)$last; else { sort($modes, SORT_NUMERIC); $picked_qid = $modes[0]; } } } else { $picked_qid = $info['last_quality_id'] ?? null; } // per-day expected (from qualities), fallback to average if missing $per_day_expected = 0.0; if ($picked_qid && isset($qualities[$picked_qid])) $per_day_expected = (float)$qualities[$picked_qid]['per_day']; else { $vals = array_column($qualities, 'per_day'); if (!empty($vals)) $per_day_expected = array_sum($vals) / count($vals); } $per_shift_expected = $per_day_expected > 0 ? ($per_day_expected / 2.0) : 0.0; // pissing checks $pissing_here = isset($pissing_map[$mid][$date]); $prev_date = date('Y-m-d', strtotime($date . ' -1 day')); $pissing_prev = isset($pissing_map[$mid][$prev_date]); $include = true; $reason = ''; $day_equiv = 0.0; if ($pissing_here) { $include = false; $reason = 'pissing_on_date'; } elseif ($pissing_prev) { $include = false; $reason = 'pissing_prev_date'; } else { // apply per-karigar rules (40% rule etc.) if ($kcount <= 1) { // single karigar - prefer per_karigar value if present if (!empty($per_karigar)) { $single_meter = array_values($per_karigar)[0] ?? $daily_total; } else { // no per_karigar detail - fallback: assume single did full daily_total $single_meter = $daily_total; } // RULES: // if single_meter < 0.40 * per_shift_expected => IGNORE if ($per_shift_expected > 0 && $single_meter < 0.40 * $per_shift_expected) { $include = false; $reason = 'single_karigar_below_40pct_shift_ignore'; } else { if ($per_shift_expected > 0 && $single_meter <= $per_shift_expected) { $day_equiv = 0.5; $reason = 'single_karigar_half'; } else { if ($per_day_expected > 0 && $single_meter >= 0.60 * $per_day_expected) { $day_equiv = 1.0; $reason = 'single_karigar_full_60pct'; } else { $day_equiv = 0.5; $reason = 'single_karigar_half_fallback'; } } } } else { // multi karigar - count good shifts (>= 40% of per_shift_expected) $good_shifts = 0; if (!empty($per_karigar)) { foreach ($per_karigar as $kid => $km) { if ($per_shift_expected > 0 && $km >= 0.40 * $per_shift_expected) $good_shifts++; } } else { // fallback heuristic when per_karigar missing: if ($per_day_expected > 0 && $daily_total >= 0.70 * $per_day_expected) $good_shifts = 2; else { if ($per_shift_expected > 0 && $daily_total >= $per_shift_expected) $good_shifts = min(2, (int)floor($daily_total / $per_shift_expected)); } } if ($good_shifts == 0) { $include = false; $reason = 'both_shifts_below_40pct_ignore'; } else { $day_equiv = min(2, $good_shifts) * 0.5; $reason = ($day_equiv == 1.0) ? 'multi_karigar_full' : 'multi_karigar_one_good_half'; } } } // prepare per_karigar display map using names when possible $per_karigar_display = []; foreach ($per_karigar as $kid => $km) { $kname = $karigar_names[$kid] ?? ("#{$kid}"); $per_karigar_display["{$kname} ({$kid})"] = $km; } // prepare karigar names list $karigar_names_list = []; foreach ($karigar_ids as $kid) { $kname = $karigar_names[$kid] ?? ("#{$kid}"); $karigar_names_list[] = "{$kname} ({$kid})"; } // append row $report[$mid]['rows'][] = [ 'date'=>$date, 'meter'=>$daily_total, 'karigar_ids'=>$karigar_ids, 'karigars_display'=>$karigar_names_list, 'per_karigar'=>$per_karigar_display, 'picked_quality_id'=>$picked_qid, 'picked_quality_name'=> $picked_qid && isset($qualities[$picked_qid]) ? $qualities[$picked_qid]['name'] : '', 'picked_quality_code'=> $picked_qid && isset($qualities[$picked_qid]) ? $qualities[$picked_qid]['code'] : '', 'per_day_expected'=>$per_day_expected, 'per_shift_expected'=>$per_shift_expected, 'day_equiv'=> $include ? $day_equiv : 0.0, 'reason'=>$reason ]; if ($include && $day_equiv > 0) { $report[$mid]['sum_meter'] += $daily_total; $report[$mid]['sum_days'] += $day_equiv; } } } /* ---- Prepare summary array ---- */ $summary = []; foreach ($report as $mid => $info) { $sum_meter = $info['sum_meter']; $sum_days = $info['sum_days']; $avg = $sum_days > 0 ? round($sum_meter / $sum_days, 3) : 0; $summary[$mid] = [ 'machine_id' => $mid, 'machine_name' => null, // will fetch machines list below 'sum_meter' => $sum_meter, 'sum_days' => $sum_days, 'avg' => $avg ]; } /* ---- Handler: create+populate machine_avg table on demand ---- This handler runs when form posts action=update_machine_avg. It creates the table if missing and upserts rows from $summary. Keep this module-scoped — does not change global settings. */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'update_machine_avg') { // optional CSRF: if you use CSRF tokens in session, validate if (isset($_SESSION['csrf_token']) && isset($_POST['csrf_token']) && $_POST['csrf_token'] !== $_SESSION['csrf_token']) { $error_msg = "CSRF token mismatch. Update aborted."; } else { try { // create table if not exists $createSql = "CREATE TABLE IF NOT EXISTS machine_avg ( id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id INT NOT NULL, machine_id INT NOT NULL, period_from DATE NOT NULL, period_to DATE NOT NULL, sum_meter DOUBLE DEFAULT 0, sum_days DOUBLE DEFAULT 0, avg_per_day DOUBLE DEFAULT 0, updated_by INT DEFAULT NULL, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_company_machine (company_id, machine_id), UNIQUE KEY uniq_period (company_id, machine_id, period_from, period_to) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"; $pdo->exec($createSql); // prepare upsert statement (MySQL) $upsertSql = "INSERT INTO machine_avg (company_id, machine_id, period_from, period_to, sum_meter, sum_days, avg_per_day, updated_by) VALUES (:cid, :mid, :pfrom, :pto, :sum_meter, :sum_days, :avg, :updated_by) ON DUPLICATE KEY UPDATE sum_meter = VALUES(sum_meter), sum_days = VALUES(sum_days), avg_per_day = VALUES(avg_per_day), updated_by = VALUES(updated_by), updated_at = CURRENT_TIMESTAMP"; $upsertStmt = $pdo->prepare($upsertSql); // begin transaction $pdo->beginTransaction(); // Insert rows only for machines present in $summary $inserted = 0; foreach ($summary as $mid => $s) { // respect optional filter from the POST (if user wanted single machine) if (!empty($_POST['machine_id']) && $_POST['machine_id'] !== 'all' && (int)$_POST['machine_id'] !== (int)$mid) continue; $avg = isset($s['avg']) ? (float)$s['avg'] : ( (isset($s['sum_days']) && $s['sum_days']>0) ? ($s['sum_meter']/$s['sum_days']) : 0.0 ); $upsertStmt->execute([ ':cid' => $COMPANY_ID, ':mid' => (int)$mid, ':pfrom' => $from, ':pto' => $to, ':sum_meter' => (float)($s['sum_meter'] ?? 0.0), ':sum_days' => (float)($s['sum_days'] ?? 0.0), ':avg' => $avg, ':updated_by' => $USER_ID ?: null ]); $inserted++; } $pdo->commit(); $success_msg = "machine_avg updated for {$inserted} machine(s) for period {$from} to {$to}."; } catch (Exception $e) { if ($pdo->inTransaction()) $pdo->rollBack(); $error_msg = "Failed to update machine_avg: " . $e->getMessage(); } } } /* ---- Machines list for dropdown and names for summary ---- */ $machines = []; try { $mstmt = $pdo->prepare("SELECT id, COALESCE(name, CONCAT('Machine ', id)) AS name FROM machines WHERE company_id = :cid ORDER BY id"); $mstmt->execute([':cid'=>$COMPANY_ID]); foreach ($mstmt->fetchAll(PDO::FETCH_ASSOC) as $r) { $machines[(int)$r['id']] = $r['name']; if (isset($summary[(int)$r['id']])) $summary[(int)$r['id']]['machine_name'] = $r['name']; } } catch (Exception $e) { foreach (array_keys($report) as $mid) { $machines[$mid] = "Machine {$mid}"; if (isset($summary[$mid]) && !$summary[$mid]['machine_name']) $summary[$mid]['machine_name'] = "Machine {$mid}"; } } /* ---- CSV export: full or summary ---- */ if ($export === 'csv') { if ($export_mode === 'summary') { $fn = "machine_summary_{$from}_to_{$to}.csv"; header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="'.basename($fn).'"'); $out = fopen('php://output','w'); fputcsv($out, ['machine_id','machine_name','included_meter_sum','included_days','avg_per_day']); foreach ($summary as $s) { fputcsv($out, [$s['machine_id'], $s['machine_name'] ?? "Machine {$s['machine_id']}", $s['sum_meter'], $s['sum_days'], $s['avg']]); } fclose($out); exit; } else { // full export (existing) $fn = "machine_avg_{$from}_to_{$to}.csv"; header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="'.basename($fn).'"'); $out = fopen('php://output','w'); fputcsv($out, ['machine_id','machine_name','date','picked_quality_id','picked_quality_code','picked_quality_name','per_day_expected','per_shift_expected','meter','day_equiv','reason','per_karigar_json']); foreach ($report as $mid => $info) { $mname = $machines[$mid] ?? "Machine {$mid}"; foreach ($info['rows'] as $r) { fputcsv($out, [ $mid, $mname, $r['date'], $r['picked_quality_id'], $r['picked_quality_code'], $r['picked_quality_name'], $r['per_day_expected'], $r['per_shift_expected'], $r['meter'], $r['day_equiv'], $r['reason'], json_encode($r['per_karigar']) ]); } fputcsv($out, [$mid, $mname, 'SUMMARY', '', '', '', '', '', $info['sum_meter'], $info['sum_days'], '', '']); } fclose($out); exit; } } /* ---- Render UI ---- */ require_once __DIR__ . '/partials/header.php'; ?> <style> /* small helpers: hide print-only / no-print classes */ .no-print { display: inline-block; } @media print { .no-print { display: none !important; } } .summary-table { width:100%; border-collapse: collapse; margin-bottom:1rem; } .summary-table th, .summary-table td { border: 1px solid #ddd; padding: 8px; text-align:left; } .summary-card { padding: 10px; background: #f7f7f7; border-radius:4px; margin-bottom:1rem; } </style> <div class="card"> <div class="card-header"> <h3>Machine — Per-day AVG (summary + full)</h3> <p>Company: <?php echo h($COMPANY_ID); ?> — Range: <?php echo h($from); ?> to <?php echo h($to); ?></p> </div> <div class="card-body"> <!-- Controls --> <form method="get" class="form-inline no-print" style="margin-bottom:1rem;"> <label style="margin-right:.5rem">From</label> <input type="date" name="from" value="<?php echo h($from); ?>" class="form-control" required> <label style="margin:0 .5rem">To</label> <input type="date" name="to" value="<?php echo h($to); ?>" class="form-control" required> <label style="margin:0 .5rem">Machine</label> <select name="machine_id" class="form-control"> <option value="all" <?php echo $machine_filter==='all' ? 'selected' : ''; ?>>All machines</option> <?php foreach ($machines as $mid => $mname): ?> <option value="<?php echo h($mid); ?>" <?php echo ((string)$mid === (string)$machine_filter) ? 'selected' : ''; ?>><?php echo h($mname); ?></option> <?php endforeach; ?> </select> <button class="btn" type="submit" style="margin-left:.5rem">Show</button> <!-- Export/Print buttons --> <a class="btn no-print" href="<?php echo h($_SERVER['PHP_SELF'] . '?' . http_build_query(array_merge($_GET, ['export'=>'csv','mode'=>'full']))); ?>" style="margin-left:.5rem">Export CSV (full)</a> <a class="btn no-print" href="<?php echo h($_SERVER['PHP_SELF'] . '?' . http_build_query(array_merge($_GET, ['export'=>'csv','mode'=>'summary']))); ?>" style="margin-left:.5rem">Export CSV (summary)</a> <button type="button" class="btn no-print" onclick="printFull()" style="margin-left:.5rem">Print Full</button> <button type="button" class="btn no-print" onclick="printSummary()" style="margin-left:.5rem">Print Summary</button> </form> <!-- POST form for update_machine_avg - keep outside the GET form to avoid nested forms --> <div style="display:inline-block; margin-bottom:1rem;"> <form id="update-machine-avg-form" method="post" style="display:inline-block; margin-left:.25rem;"> <?php if (isset($_SESSION['csrf_token'])): ?> <input type="hidden" name="csrf_token" value="<?php echo h($_SESSION['csrf_token']); ?>"> <?php endif; ?> <input type="hidden" name="action" value="update_machine_avg"> <input type="hidden" name="from" value="<?php echo h($from); ?>"> <input type="hidden" name="to" value="<?php echo h($to); ?>"> <input type="hidden" name="machine_id" value="<?php echo h($machine_filter); ?>"> <button type="button" class="btn no-print" onclick="confirmUpdateAvg()" title="Create / update machine_avg table and insert summary rows">Update machine_avg table</button> </form> </div> <?php if (!empty($success_msg)): ?><div class="notice notice-success" style="margin-top:.5rem;"><?php echo h($success_msg); ?></div><?php endif; ?> <?php if (!empty($error_msg)): ?><div class="notice notice-danger" style="margin-top:.5rem;"><?php echo h($error_msg); ?></div><?php endif; ?> <!-- SUMMARY SECTION --> <div id="summary-section" class="summary-card"> <h4>Summary (included days & avg per machine)</h4> <table class="summary-table"> <thead> <tr> <th>Machine ID</th> <th>Machine</th> <th>Included meter (sum)</th> <th>Included days</th> <th>Avg / day</th> </tr> </thead> <tbody> <?php if (empty($summary)): ?> <tr><td colspan="5">No data</td></tr> <?php else: ?> <?php foreach ($summary as $mid => $s): ?> <tr> <td><?php echo h($s['machine_id']); ?></td> <td><?php echo h($s['machine_name'] ?? ($machines[$mid] ?? "Machine {$mid}")); ?></td> <td><?php echo h($s['sum_meter']); ?></td> <td><?php echo h($s['sum_days']); ?></td> <td><?php echo h($s['avg']); ?></td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> </div> <!-- FULL DETAIL --> <?php if (empty($report)): ?> <div class="notice">No production data found in selected range.</div> <?php else: ?> <?php foreach ($report as $mid => $info): $mname = $machines[$mid] ?? "Machine {$mid}"; $sum_meter = $info['sum_meter']; $sum_days = $info['sum_days']; $avg = $sum_days > 0 ? round($sum_meter / $sum_days, 3) : 0; ?> <div class="card" style="margin-bottom:1rem;"> <div class="card-header"> <strong><?php echo h($mname); ?></strong> <span style="float:right;"> Counted days: <?php echo h($sum_days); ?> | Avg/day: <?php echo h($avg); ?> </span> </div> <div class="card-body"> <table class="table table-striped table-sm"> <thead> <tr> <th>Date</th> <th>Meter</th> <th>Karigars</th> <th>Per-karigar (meter)</th> <th>Quality</th> <th>Per-day exp</th> <th>Per-shift exp</th> <th>Day-eq</th> <th>Reason</th> </tr> </thead> <tbody> <?php foreach ($info['rows'] as $r): ?> <tr<?php if($r['day_equiv']==0) echo ' class="text-muted"'; ?>> <td><?php echo h($r['date']); ?></td> <td><?php echo h($r['meter']); ?></td> <td><?php echo h(!empty($r['karigars_display']) ? implode(', ', $r['karigars_display']) : '-'); ?></td> <td><?php echo h(!empty($r['per_karigar']) ? json_encode($r['per_karigar']) : '-'); ?></td> <td><?php echo h(($r['picked_quality_code'] ? $r['picked_quality_code'] . ' - ' : '') . $r['picked_quality_name']); ?></td> <td><?php echo h($r['per_day_expected'] ?: '-'); ?></td> <td><?php echo h($r['per_shift_expected'] ?: '-'); ?></td> <td><?php echo h($r['day_equiv'] ?: '-'); ?></td> <td><?php echo h($r['reason'] ?: '-'); ?></td> </tr> <?php endforeach; ?> <tr class="table-footer"> <td><strong>Included sum / avg</strong></td> <td><?php echo h($sum_meter); ?></td> <td></td> <td></td> <td></td> <td><?php echo h($sum_days); ?></td> <td><strong><?php echo h($avg); ?></strong></td> </tr> </tbody> </table> </div> </div> <?php endforeach; ?> <?php endif; ?> </div> </div> <script> /* Robust print swap with page-break + overflow fixes so multi-page prints work. Replace older print functions with these. */ function _getPrintableHtml_withPrintCss(contentHtml){ // Important print CSS that removes scrolling and allows tables to break across pages var printCss = ` /* Reset page layout for printing */ html, body { height: auto !important; overflow: visible !important; margin: 0; padding: 0; color: #222; } /* ensure cards/containers are fully expanded */ .card, .container, .summary-card { overflow: visible !important; height: auto !important; max-height: none !important; } /* Hide elements not needed in print */ .no-print, .filters, .header, .actions { display: none !important; } /* Table printing rules: allow breaking across pages */ table { width: 100%; border-collapse: collapse; page-break-inside: auto; } thead { display: table-header-group; } /* repeat header on each printed page */ tfoot { display: table-footer-group; } tr { page-break-inside: avoid; page-break-after: auto; } th, td { border: 1px solid #ddd; padding: 6px; text-align: left; vertical-align: top; } /* smaller font for dense tables on paper */ body { font-family: Arial, Helvetica, sans-serif; font-size: 12px; } /* Optional: limit very large cells */ td { word-break: break-word; } /* ensure large tables are not inside fixed-height scroll boxes */ .summary-table, .table { max-height: none !important; overflow: visible !important; } /* if you have custom page margins needed, add @page rules here */ `; // copy existing <link rel="stylesheet"> and <style> to preserve look (optional) var headNodes = document.head.querySelectorAll('style, link[rel="stylesheet"]'); var headHtml = ''; headNodes.forEach(function(n){ headHtml += n.outerHTML; }); return '<!doctype html><html><head><meta charset="utf-8"><title>Print</title>' + headHtml + '<style>' + printCss + '</style>' + '</head><body>' + contentHtml + '</body></html>'; } function _swapBodyAndPrint_htmlSnapshot(printHtml, restoreDelayMs){ restoreDelayMs = restoreDelayMs || 1000; var origHtml = document.documentElement.outerHTML; try { // replace full document with print snapshot (keeps head+styles we injected) document.open(); document.write(printHtml); document.close(); // give browser time to layout setTimeout(function(){ try { window.print(); } catch(e){ console.error('print error', e); } // restore original page after a small delay setTimeout(function(){ document.open(); document.write(origHtml); document.close(); // NOTE: if your page relies on JS initialization, consider forcing reload: // location.reload(); }, restoreDelayMs); }, 400); } catch (err) { console.error('print swap failed', err); alert('Print attempt failed. Check console for details. Reloading page.'); location.reload(); } } /* Print only the summary section */ function printSummary() { var summaryEl = document.getElementById('summary-section'); if (!summaryEl) { alert('Summary section not found'); return; } var content = '<div style="padding:12px;"><h1>Machine Summary</h1>' + summaryEl.outerHTML + '</div>'; var html = _getPrintableHtml_withPrintCss(content); _swapBodyAndPrint_htmlSnapshot(html, 900); } /* Print full report (summary + all detail tables) */ function printFull() { var summaryEl = document.getElementById('summary-section'); var content = '<div style="padding:12px;"><h1>Machine Report</h1>'; if (summaryEl) content += summaryEl.outerHTML; // include all tables / cards with table data // choose nodes that have <table> within and are visible var nodes = document.querySelectorAll('body .card, body .container'); nodes.forEach(function(n){ if (n.querySelector && n.querySelector('table')) { content += '<div style="margin-top:10px;">' + n.outerHTML + '</div>'; } }); content += '</div>'; var html = _getPrintableHtml_withPrintCss(content); _swapBodyAndPrint_htmlSnapshot(html, 1200); } /* Confirm and submit POST form to update machine_avg table */ function confirmUpdateAvg(){ if(!confirm('This will create (if missing) and update machine_avg rows for the current period. Continue?')) return; document.getElementById('update-machine-avg-form').submit(); } </script> <?php require_once __DIR__ . '/partials/footer.php'; ?>