« Back to History
loom_salary_send_advance.php
|
20260723_000646.php
Initial Domain Snapshot
Copy Code
<?php /* ============================================================================ File: /erp/loom_salary_send_advance.php Title: Loom Salary — Send to Advance / Send to Deduction (final fixed) - POST handler runs BEFORE header include. - Rebuilds karigar list & NET from production_entry so POST has amounts. - Inserts matched to karigar_advance and karigar_deduction schemas. - CSRF protected, transaction wrapped, idempotent checks. ============================================================================ */ header('X-Frame-Options: SAMEORIGIN'); error_reporting(E_ALL); ini_set('display_errors', 1); /* ---------------------- 0. Auth + DB bootstrap ---------------------- */ require __DIR__ . '/modules/auth/auth.php'; require_login(); $ctx = $ctx ?? []; $u = $ctx['user'] ?? (function_exists('auth_user') ? auth_user() : null); $company_id = (int)($ctx['company_id'] ?? ($u['company_id'] ?? 0)); $user_id = (int)($u['id'] ?? 0); $pdo = $ctx['pdo'] ?? ($GLOBALS['pdo'] ?? null); if (!$pdo) { require __DIR__ . '/core/db.php'; $pdo = $GLOBALS['pdo'] ?? $pdo; } /* ---------------------- Feature flag (defined to avoid undefined warnings) ---------------------- */ $FF_USE_QUEUE_ADJ = true; /* ---------------------- 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; } function safe_json($s){ $a=@json_decode((string)$s,true); return is_array($a)?$a:[]; } function norm_half($h){ $h=strtoupper((string)$h); return in_array($h,['1','2','F'],true)?$h:'F'; } function yrmo_half_range(int $y,int $m,string $half): array{ $start = sprintf('%04d-%02d-01',$y,$m); $end = date('Y-m-t', strtotime($start)); if ($half==='2') $start = sprintf('%04d-%02d-16',$y,$m); if ($half==='1') $end = sprintf('%04d-%02d-15',$y,$m); return [$start,$end]; } function label_for_period(int $y,int $m,string $half,string $from,string $to): string{ if ($half==='F') return 'FULL '.date('M', mktime(0,0,0,$m,1,$y)).' '.$y; if ($half==='1') return '1-15 '.date('M', mktime(0,0,0,$m,1,$y)).' '.$y; if ($half==='2') return '16-END '.date('M', mktime(0,0,0,$m,1,$y)).' '.$y; return $from.' to '.$to; } /* ---------------------- GET filters (report generation) ---------------------- */ $year = (int)gv('year', date('Y')); $month = (int)gv('month', date('n')); $half = norm_half(gv('half','F')); [$date_from,$date_to] = yrmo_half_range($year,$month,$half); $khata_id = (int)gv('khata_id', 0); $machine_id= (int)gv('machine_id', 0); $period_label = label_for_period($year,$month,$half,$date_from,$date_to); /* ---------------------- PRE-BUILD karigar list and NET (so POST can access amounts) This duplicates the report logic but is intentionally lightweight: reads production_entry rows in the chosen report period and accumulates per karigar. ---------------------- */ $KAR_NAME = []; $KAR_ENTRY = []; $KAR_KHATA_ID = []; try{ $stmt = $pdo->prepare("SELECT id, karigar_name, entry_name, khata_id FROM loom_karigar_master WHERE company_id=?"); $stmt->execute([$company_id]); foreach($stmt as $r){ $id=(int)$r['id']; $KAR_NAME[$id] = $r['karigar_name'] ?: "Karigar #$id"; $KAR_ENTRY[$id] = $r['entry_name'] ?? ''; $KAR_KHATA_ID[$id] = (int)($r['khata_id'] ?? 0); } }catch(Throwable $_){} /* Build MTR & NET arrays (from production_entry -> lines_json). This mirrors your report code. */ $MTR = []; $MTR_KAR_TOT = []; $karigar_set = []; $quality_set = []; $_skipped_bad_date = 0; $_skipped_out_range = 0; $from_ts = strtotime($date_from . ' 00:00:00'); $to_ts = strtotime($date_to . ' 23:59:59'); try{ $sql = "SELECT id, quality_id, entry_date, lines_json FROM production_entry WHERE company_id = :cid AND entry_date BETWEEN :d1 AND :d2"; $params = [':cid'=>$company_id, ':d1'=>$date_from, ':d2'=>$date_to]; if ($khata_id>0){ $sql .= " AND khata_id=:khata"; $params[':khata']=$khata_id; } if ($machine_id>0){ $sql .= " AND machine_id=:mc"; $params[':mc']=$machine_id; } $sql .= " ORDER BY entry_date, id"; $st = $pdo->prepare($sql); $st->execute($params); foreach($st as $row){ $qid = (int)($row['quality_id'] ?? 0); $row_entry = substr((string)$row['entry_date'],0,10); if(!preg_match('/^\d{4}-\d{2}-\d{2}$/',$row_entry)){ $row_entry = date('Y-m-d', strtotime($row['entry_date'])); } if(!$row_entry){ $_skipped_bad_date++; continue; } $ts = strtotime($row_entry.' 00:00:00'); if($ts < $from_ts || $ts > $to_ts){ $_skipped_out_range++; continue; } $lines = safe_json($row['lines_json'] ?? ''); foreach($lines as $ln){ $kid = (int)($ln['karigar_id'] ?? 0); $m = (float)($ln['meter'] ?? 0); if ($kid<=0 || $m<=0 || $qid<=0) continue; $karigar_set[$kid] = true; $quality_set[$qid] = true; $MTR[$kid][$qid] = ($MTR[$kid][$qid] ?? 0) + $m; $MTR_KAR_TOT[$kid] = ($MTR_KAR_TOT[$kid] ?? 0) + $m; } } }catch(Throwable $e){ error_log("pre-build production read failed: ".$e->getMessage()); } /* Fetch quality rates (we need them to compute money if necessary) */ $QUAL_RATE = []; $QUALS = []; try{ $qr = $pdo->prepare("SELECT quality_id AS id, quality_name AS name, rate FROM quality_rates WHERE company_id=?"); $qr->execute([$company_id]); foreach($qr as $r){ $id=(int)$r['id']; $QUALS[$id]=$r['name']; $QUAL_RATE[$id] = (float)$r['rate']; } }catch(Throwable $_){} /* Compute gross/net arrays now so POST handler can use them */ $EXTRA=[]; $DEDUCT=[]; $ADV=[]; /* optional queue adjustments */ if ($FF_USE_QUEUE_ADJ) { try{ $sq=$pdo->prepare("SELECT karigar_id, type, SUM(amount) amt FROM salary_adjust_queue WHERE company_id=? AND person_type='karigar' AND apply_start=? AND apply_end=? GROUP BY karigar_id,type"); $sq->execute([$company_id,$date_from,$date_to]); foreach($sq as $r){ $kid=(int)$r['karigar_id']; if($kid<=0) continue; $t=(string)$r['type']; $amt=(float)$r['amt']; if($t==='extra') $EXTRA[$kid]=($EXTRA[$kid]??0)+$amt; if($t==='deduction') $DEDUCT[$kid]=($DEDUCT[$kid]??0)+$amt; } }catch(Throwable $_){} } /* Sum other tables for each karigar (extra/deduction/advance) */ function sum_money_tbl($pdo,$company_id,$tbl,$kid,$from,$to,$date_cols=['date','entry_date','created_at'],$amt_cols=['amount','amt','value']){ foreach($date_cols as $dc){ foreach($amt_cols as $ac){ try{ $st=$pdo->prepare("SELECT SUM($ac) s FROM `$tbl` WHERE company_id=? AND karigar_id=? AND $dc BETWEEN ? AND ?"); $st->execute([$company_id,$kid,$from,$to]); $v=$st->fetchColumn(); if($v!==false && $v!==null) return (float)$v; }catch(Throwable $e){} }} return 0.0; } foreach(array_keys($karigar_set) as $kid){ $EXTRA[$kid] = ($EXTRA[$kid] ?? 0) + sum_money_tbl($pdo,$company_id,'karigar_extra',$kid,$date_from,$date_to); $DEDUCT[$kid] = ($DEDUCT[$kid] ?? 0) + sum_money_tbl($pdo,$company_id,'karigar_deduction',$kid,$date_from,$date_to); $ADV[$kid] = sum_money_tbl($pdo,$company_id,'advance_data',$kid,$date_from,$date_to); } /* GROSS / NET */ $GROSS = []; $NET = []; $kar_list = array_keys($karigar_set); foreach($kar_list as $kid){ $g = 0.0; foreach($quality_set as $qid_key => $_){ /* loop quality ids existing in quality_set is awkward; instead use $QUAL_RATE keys */ /* we'll iterate over QUAL_RATE keys to be safe */ } foreach(array_keys($QUAL_RATE) as $qid){ $g += ($MTR[$kid][$qid] ?? 0.0) * ($QUAL_RATE[$qid] ?? 0.0); } $GROSS[$kid] = $g; $n = $g + ($EXTRA[$kid] ?? 0) - ($DEDUCT[$kid] ?? 0) - ($ADV[$kid] ?? 0); $NET[$kid] = $n; } /* Compute totals for summary */ $bank_total = 0.0; $cash_total = 0.0; $grand_net = 0.0; try{ $st = $pdo->prepare("SELECT karigar_id FROM loom_bank_master WHERE company_id=? AND (is_default=1 OR is_default IS NULL) AND (COALESCE(account_no,'')<>'' OR COALESCE(upi_id,'')<>'')"); $st->execute([$company_id]); $HAS_BANK = []; foreach($st as $r) $HAS_BANK[(int)$r['karigar_id']]=true; }catch(Throwable $_){ $HAS_BANK=[]; } foreach($kar_list as $kid){ $grand_net += ($NET[$kid] ?? 0.0); if(!empty($HAS_BANK[$kid])) $bank_total += ($NET[$kid] ?? 0.0); else $cash_total += ($NET[$kid] ?? 0.0); } /* ---------------------------- POST handler — uses rebuilt $kar_list and $NET above ---------------------------- */ if (session_status() !== PHP_SESSION_ACTIVE) @session_start(); if (empty($_SESSION['csrf_token'])) { try{ $_SESSION['csrf_token'] = bin2hex(random_bytes(16)); } catch(Throwable $_){ $_SESSION['csrf_token'] = md5(uniqid('',true)); } } if ($_SERVER['REQUEST_METHOD'] === 'POST' && in_array(pv('action'), ['send_to_advance','send_to_deduction'], true)) { $action = pv('action'); if (!pv('csrf') || pv('csrf') !== ($_SESSION['csrf_token'] ?? '')) { $flash = 'CSRF token mismatch. Action cancelled.'; } else { $isAdvance = ($action === 'send_to_advance'); $apply_year = (int)pv('apply_year', $year); $apply_month = (int)pv('apply_month', $month); $apply_half = norm_half(pv('apply_half', $half)); [$apply_from,$apply_to] = yrmo_half_range($apply_year,$apply_month,$apply_half); $apply_label = label_for_period($apply_year,$apply_month,$apply_half,$apply_from,$apply_to); $remark_tag = "[AUTO-SALARY:{$apply_year}-{$apply_month}-{$apply_half}]"; try{ if (!$pdo->inTransaction()) $pdo->beginTransaction(); $added = 0; if ($isAdvance) { $chk = $pdo->prepare("SELECT 1 FROM `karigar_advance` WHERE company_id=:cid AND karigar_id=:kid AND `date` BETWEEN :from AND :to AND remark LIKE :tag LIMIT 1"); $ins = $pdo->prepare("INSERT INTO `karigar_advance` (`company_id`,`karigar_id`,`amount`,`date`,`remark`,`deduct_parts`,`remaining_amount`,`status`,`created_by`) VALUES (:cid,:kid,:amt,:d,:r,1,:amt,'OPEN',:uid)"); } else { $chk = $pdo->prepare("SELECT 1 FROM `karigar_deduction` WHERE company_id=:cid AND karigar_id=:kid AND entry_date BETWEEN :from AND :to AND notes LIKE :tag LIMIT 1"); $ins = $pdo->prepare("INSERT INTO `karigar_deduction` (`company_id`,`karigar_id`,`karigar_name`,`entry_date`,`amount`,`notes`,`created_by`) VALUES (:cid,:kid,:kname,:d,:amt,:n,:uid)"); } foreach($kar_list as $kid){ $amt = (float)($NET[$kid] ?? 0.0); if ($amt == 0.0) continue; $chk->execute([':cid'=>$company_id, ':kid'=>$kid, ':from'=>$apply_from, ':to'=>$apply_to, ':tag'=>"%{$remark_tag}%"]); if ($chk->fetchColumn()) continue; if ($isAdvance) { $remark = "Salary ({$apply_label}) {$remark_tag}"; $ins->execute([':cid'=>$company_id, ':kid'=>$kid, ':amt'=>$amt, ':d'=>$apply_to, ':r'=>$remark, ':uid'=>$user_id]); } else { $notes = "Salary ({$apply_label}) {$remark_tag}"; $ins->execute([':cid'=>$company_id, ':kid'=>$kid, ':kname'=>$KAR_NAME[$kid] ?? '', ':d'=>$apply_to, ':amt'=>$amt, ':n'=>$notes, ':uid'=>$user_id]); } $added++; } $pdo->commit(); $flash = ($isAdvance ? "Sent to Advance: " : "Sent to Deduction: ") . $added . " record(s) for $apply_label."; } catch(Throwable $e) { if ($pdo->inTransaction()) try{ $pdo->rollBack(); } catch(Throwable $_){} error_log("[salary-send] action=$action company=$company_id user=$user_id err=" . $e->getMessage()); $flash = 'Action failed: see server logs'; } // redirect to show flash and avoid resubmit $qs = http_build_query(['year'=>$year,'month'=>$month,'half'=>$half,'khata_id'=>$khata_id,'machine_id'=>$machine_id,'flash'=>$flash]); header("Location: ?$qs"); exit; } } /* ========================= Now include header and render page ========================= */ require_once __DIR__ . '/partials/header.php'; /* check PDF availability (optional) */ $PDF_AVAILABLE = false; $autoload = __DIR__ . '/vendor/autoload.php'; if (is_file($autoload)) { require_once $autoload; $PDF_AVAILABLE = class_exists(\Dompdf\Dompdf::class); } ?> <!doctype html> <html> <head> <meta charset="utf-8"> <title>Loom Salary — Send Advance/Deduction</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> :root{ --green:#34A853; --mint:#F5FFF7; --stone:#5F6368; --sage:#D0E8D0; --warn:#ef4444; } *{box-sizing:border-box} body{margin:0;background:var(--mint);color:#222;font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial} .wrap{max-width:1200px;margin:20px auto;padding:0 12px} .h1{color:#2c7d3f;margin:6px 0 12px} .hbar{display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap;background:#e9f7ee;border:1px solid #d6f0dd;padding:12px;border-radius:12px} .hbar label{font-size:12px;display:block;margin-bottom:4px} .hbar select{padding:8px 10px;border:1px solid #cfe8d0;border-radius:10px;background:#fff;min-width:120px} .btn{background:var(--green);color:#fff;border:none;border-radius:12px;padding:9px 14px;font-weight:600;cursor:pointer;display:inline-block} .btn.gray{background:#5F6368} .btn.warn{background:var(--warn)} .card{background:#fff;border:1px solid #ecf2ec;border-radius:14px;overflow:hidden;margin-top:16px;padding:12px} .table{width:100%;border-collapse:separate;border-spacing:0} .table th,.table td{padding:10px 12px;border-bottom:1px solid #eef5ef} .table thead th{background:#cfe9d7;color:#1b4d2a;text-align:right} .table thead th:first-child{text-align:left} .table tbody td{text-align:right} .table tbody td:first-child{text-align:left} .table tfoot td{background:#fff9e8;font-weight:700} .totalcol{background:#f3faf4;font-weight:700} .small{font-size:12px;color:#555} .flash{background:#e7ffe9;border:1px solid #c7f0cb;color:#1e5a2c;padding:8px 12px;border-radius:10px;margin:10px 0;display:inline-block} </style> </head> <body> <div class="wrap"> <h2 class="h1">Loom Salary</h2> <div class="small" style="margin-bottom:8px;"><b>Company:</b> <?= h($company_id ? $company_id : "Company #$company_id") ?></div> <?php if (!empty($_GET['flash'])): ?> <div class="flash"><?= h($_GET['flash']) ?></div> <?php endif; ?> <!-- Report Filters --> <form class="hbar" method="get" action=""> <div><label>Month</label><select name="month"><?php for($m=1;$m<=12;$m++): ?><option value="<?= $m ?>" <?= $m===$month?'selected':'' ?>><?= date('M', mktime(0,0,0,$m,1,$year)) ?></option><?php endfor; ?></select></div> <div><label>Period</label><select name="half"><option value="1" <?= $half==='1'?'selected':''?>>1–15</option><option value="2" <?= $half==='2'?'selected':''?>>16–End</option><option value="F" <?= $half==='F'?'selected':''?>>Full</option></select></div> <div><label>Year</label><select name="year"><?php for($y=date('Y')-2;$y<=date('Y')+1;$y++): ?><option value="<?= $y ?>" <?= $y===$year?'selected':'' ?>><?= $y ?></option><?php endfor; ?></select></div> <button class="btn" type="submit">Show Report</button> </form> <div class="small" style="margin:10px 0;"> <b>Summary:</b> Bank / <?= number_format($bank_total,2) ?>, Cash / <?= number_format($cash_total,2) ?>, <b>Total</b> <?= number_format($grand_net,2) ?> </div> <!-- Apply Actions --> <div class="card" style="display:flex;flex-direction:column;gap:12px"> <div style="display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end"> <form method="post" style="display:flex;gap:8px;align-items:flex-end"> <input type="hidden" name="action" value="send_to_advance"> <input type="hidden" name="csrf" value="<?= h($_SESSION['csrf_token'] ?? '') ?>"> <div><label class="small">Apply Month</label> <select name="apply_month"><?php for($m=1;$m<=12;$m++): ?><option value="<?= $m ?>" <?= $m===$month?'selected':'' ?>><?= date('M',mktime(0,0,0,$m,1,$year)) ?></option><?php endfor; ?></select> </div> <div><label class="small">Apply Period</label> <select name="apply_half"><option value="1">1–15</option><option value="2">16–End</option><option value="F" <?= $half==='F'?'selected':''?>>Full</option></select> </div> <div><label class="small">Apply Year</label> <select name="apply_year"><?php for($y=date('Y')-2;$y<=date('Y')+1;$y++): ?><option value="<?= $y ?>" <?= $y===$year?'selected':'' ?>><?= $y ?></option><?php endfor; ?></select> </div> <button class="btn" type="submit" onclick="return confirm('Send generated salaries to karigar_advance? Continue?')">Send to Advance</button> </form> <form method="post" style="display:flex;gap:8px;align-items:flex-end"> <input type="hidden" name="action" value="send_to_deduction"> <input type="hidden" name="csrf" value="<?= h($_SESSION['csrf_token'] ?? '') ?>"> <div><label class="small">Apply Month</label> <select name="apply_month"><?php for($m=1;$m<=12;$m++): ?><option value="<?= $m ?>" <?= $m===$month?'selected':'' ?>><?= date('M',mktime(0,0,0,$m,1,$year)) ?></option><?php endfor; ?></select> </div> <div><label class="small">Apply Period</label> <select name="apply_half"><option value="1">1–15</option><option value="2">16–End</option><option value="F" <?= $half==='F'?'selected':''?>>Full</option></select> </div> <div><label class="small">Apply Year</label> <select name="apply_year"><?php for($y=date('Y')-2;$y<=date('Y')+1;$y++): ?><option value="<?= $y ?>" <?= $y===$year?'selected':'' ?>><?= $y ?></option><?php endfor; ?></select> </div> <button class="btn warn" type="submit" onclick="return confirm('Send generated salaries to karigar_deduction? Continue?')">Send to Deduction</button> </form> </div> </div> <!-- Salary Table --> <div class="card"> <table class="table"> <thead> <tr> <th>Karigar</th> <?php foreach(array_keys($QUAL_RATE) as $qid): ?> <th><?= h($QUALS[$qid] ?? ("Q$qid")) ?> <div class="small">@ <?= number_format($QUAL_RATE[$qid]??0,2) ?></div></th> <?php endforeach; ?> <th>Total Mtr</th><th>Gross</th><th>Extra</th><th>Deduction</th><th>Advance</th><th>Payable</th> </tr> </thead> <tbody> <?php if (!empty($kar_list)): foreach($kar_list as $kid): ?> <tr> <td><?= h($KAR_NAME[$kid] ?? ("Karigar #$kid")) ?><?php if(!empty($KAR_ENTRY[$kid])): ?><br><span class="small"><?= h($KAR_ENTRY[$kid]) ?></span><?php endif; ?></td> <?php foreach(array_keys($QUAL_RATE) as $qid): $m = $MTR[$kid][$qid] ?? 0; ?> <td><?= $m>0 ? number_format($m,2) : '' ?></td> <?php endforeach; ?> <td class="totalcol"><?= number_format($MTR_KAR_TOT[$kid] ?? 0,2) ?></td> <td><?= number_format($GROSS[$kid] ?? 0,2) ?></td> <td><?= number_format($EXTRA[$kid] ?? 0,2) ?></td> <td><?= number_format($DEDUCT[$kid] ?? 0,2) ?></td> <td><?= number_format($ADV[$kid] ?? 0,2) ?></td> <td class="totalcol"><?= number_format($NET[$kid] ?? 0,2) ?></td> </tr> <?php endforeach; else: ?> <tr><td colspan="<?= 6 + max(1, count($QUAL_RATE)) ?>" class="small">No production rows in this period.</td></tr> <?php endif; ?> </tbody> <tfoot> <tr> <td>TOTAL</td> <?php $GRAND_GROSS = 0.0; foreach(array_keys($QUAL_RATE) as $qid){ $s = 0.0; foreach($kar_list as $kid){ $s += $MTR[$kid][$qid] ?? 0; } echo '<td>'.number_format($s,2).'</td>'; $GRAND_GROSS += $s * ($QUAL_RATE[$qid] ?? 0.0); } $TOT_MTR = array_sum(array_map(fn($k)=>($MTR_KAR_TOT[$k]??0), $kar_list)); $TOT_EXTRA = array_sum(array_intersect_key($EXTRA, array_flip($kar_list))); $TOT_DED = array_sum(array_intersect_key($DEDUCT, array_flip($kar_list))); $TOT_ADV = array_sum(array_intersect_key($ADV, array_flip($kar_list))); ?> <td class="totalcol"><?= number_format($TOT_MTR,2) ?></td> <td><?= number_format($GRAND_GROSS,2) ?></td> <td><?= number_format($TOT_EXTRA,2) ?></td> <td><?= number_format($TOT_DED,2) ?></td> <td><?= number_format($TOT_ADV,2) ?></td> <td class="totalcol"><?= number_format($grand_net,2) ?></td> </tr> <tr> <td colspan="<?= 6 + count($QUAL_RATE) ?>" class="small" style="text-align:left;padding:12px"> Amount (= Σ Meters × Rate). Rates from <code>quality_rates</code>. Lines from <code>production_entry</code>. <br>Skipped: Bad Date = <?= (int)$_skipped_bad_date ?>, Out of Range = <?= (int)$_skipped_out_range ?>. </td> </tr> </tfoot> </table> </div> </div> </body> </html> <?php /* include footer */ require_once __DIR__ . '/partials/footer.php';