« Back to History
loom_production_entry_full.php
|
20260723_000646.php
Initial Domain Snapshot
Copy Code
<?php /* ============================================================== 1️⃣ PHP‑डिबग (फ़ाइल के सबसे ऊपर) ============================================================== */ error_reporting(E_ALL); ini_set('display_errors', '1'); // 1 = errors दिखाएँ ini_set('log_errors', '1'); ini_set('error_log', __DIR__.'/php_error.log'); // अपनी इछा से फ़ाइल‑पाथ बदलें /* ============================================================== 2️⃣ ERP‑ऑथ & DB‑इनिशियलाइज़ेशन (आपका मौजूदा कोड) ============================================================== */ require __DIR__ . '/modules/auth/auth.php'; /* ---- AJAX-safe auth (NO redirect, JSON only) ---- */ $isAjax = isset($_GET['act']) && $_GET['act'] !== ''; if ($isAjax) { ini_set('display_errors','0'); while (ob_get_level()) { ob_end_clean(); } $u = auth_user(); if (!$u) { header('Content-Type: application/json; charset=utf-8'); http_response_code(401); echo json_encode(['ok'=>false,'msg'=>'Session expired']); exit; } } else { require_login(); $u = auth_user(); } $COMPANY_ID = (int)$u['company_id']; $USER_ID = (int)$u['id']; /* यदि हेडर/टॉप‑बार छुपाना हो तो सेट करें */ $PAGE_HIDE_TOPBAR = true; /* ----------------------------------------------------------------- jexit – JSON रेस्पॉन्स (extra ob_clean ताकि वैरिशन से पहले कोई आउटपुट न रहे) ----------------------------------------------------------------- */ function jexit($arr,$code=200){ if (!headers_sent()){ header('Content-Type: application/json; charset=utf-8'); http_response_code($code); } if (ob_get_length()) ob_clean(); // कोई भी पिछले आउटपुट हटाएँ echo json_encode($arr, JSON_UNESCAPED_UNICODE); exit; } /* ----------------------------------------------------------------- DB‑कनेक्शन (जैसा था) ----------------------------------------------------------------- */ $pdo = $GLOBALS['pdo'] ?? null; if (!$pdo) { require __DIR__ . '/core/db.php'; } $pdo = $GLOBALS['pdo']; if (!$pdo) jexit(['ok'=>false,'msg'=>'DB connect fail'],500); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); /* ----------------------------------------------------------------- कॉन्स्टेंट्स व हेल्पर‑फ़ंक्शन्स (आपकी मौजूदा लॉजिक) ----------------------------------------------------------------- */ $ENTRY_TABLE = 'production_entry'; function month_period_to_range($month,$period){ $parts = explode('-',$month); if(count($parts)!=2) return null; $y=intval($parts[0]); $m=intval($parts[1]); if($m<1||$m>12) return null; if($period==='H1'){ $from = sprintf('%04d-%02d-01',$y,$m); $to = sprintf('%04d-%02d-15',$y,$m); }else{ $from = sprintf('%04d-%02d-16',$y,$m); $dim = (int)date('t', strtotime("$y-$m-01")); $to = sprintf('%04d-%02d-%02d',$y,$m,$dim); } return [$from,$to]; } function fetch_khata_by_code($pdo,$company_id,$code){ $st=$pdo->prepare("SELECT id,code,machine_from,machine_to FROM khatas WHERE company_id=? AND code=? AND is_active=1 LIMIT 1"); $st->execute([$company_id,trim($code)]); return $st->fetch(PDO::FETCH_ASSOC); } function fetch_assigned_karigars($pdo,$company_id,$khata_id,$machine_no){ $sql = "SELECT id, karigar_name, CAST(machine_from AS UNSIGNED) AS mf, CAST(machine_to AS UNSIGNED) AS mt FROM loom_karigar_master WHERE company_id=? AND khata_id=? AND is_active=1 AND ? BETWEEN CAST(IFNULL(machine_from,?) AS UNSIGNED) AND CAST(IFNULL(machine_to,?) AS UNSIGNED ORDER BY (machine_from IS NULL), mf, karigar_name"; $st=$pdo->prepare($sql); $st->execute([$company_id,$khata_id,$machine_no,$machine_no,$machine_no]); return $st->fetchAll(PDO::FETCH_ASSOC); } function fetch_all_karigars_in_khata($pdo,$company_id,$khata_id){ $st=$pdo->prepare("SELECT id,karigar_name FROM loom_karigar_master WHERE company_id=? AND khata_id=? AND is_active=1 ORDER BY karigar_name"); $st->execute([$company_id,$khata_id]); return $st->fetchAll(PDO::FETCH_ASSOC); } function quality_by_machine($pdo,$company_id,$machine_no){ $code = sprintf('loom %03d', $machine_no); $s=$pdo->prepare("SELECT quality FROM loom_quality_master WHERE company_id=? AND (machine_code=? OR machine_id=?) ORDER BY updated_at DESC, id DESC LIMIT 1"); $s->execute([$company_id,$code,$machine_no]); if($row=$s->fetch(PDO::FETCH_ASSOC)){ $qname = trim($row['quality'] ?? ''); if($qname!==''){ $st=$pdo->prepare("SELECT quality_id FROM quality_rates WHERE company_id=? AND TRIM(LOWER(quality_name))=TRIM(LOWER(?)) ORDER BY updated_at DESC,id DESC LIMIT 1"); $st->execute([$company_id,$qname]); $qid = (int)$st->fetchColumn(); return ['quality_name'=>$qname,'quality_id'=>$qid]; } } return null; } function quality_list($pdo,$company_id){ $st=$pdo->prepare("SELECT quality_id,quality_name FROM quality_rates WHERE company_id=? ORDER BY updated_at DESC, quality_name LIMIT 500"); $st->execute([$company_id]); return $st->fetchAll(PDO::FETCH_ASSOC); } /* ----------------------------------------------------------------- AJAX‑राउटर (सभी पिछले एन्ड‑पॉइंट वही रहे) ----------------------------------------------------------------- */ $act = $_GET['act'] ?? ''; if($act){ try{ switch($act){ case 'khata_list':{ $st=$pdo->prepare("SELECT id,code,machine_from,machine_to FROM khatas WHERE company_id=? AND is_active=1 ORDER BY code"); $st->execute([$COMPANY_ID]); $rows=$st->fetchAll(PDO::FETCH_ASSOC); jexit(['ok'=>true,'khatas'=>$rows]); } case 'khata_karigars':{ $kcode = trim($_GET['khata'] ?? ''); if(!$kcode) jexit(['ok'=>false,'msg'=>'Missing khata'],400); $kh = fetch_khata_by_code($pdo,$COMPANY_ID,$kcode); if(!$kh) jexit(['ok'=>false,'msg'=>'Khata not found'],404); $rows = fetch_all_karigars_in_khata($pdo,$COMPANY_ID,$kh['id']); jexit(['ok'=>true,'karigars'=>$rows]); } case 'quality_by_machine':{ $machine_no = (int)($_GET['machine_no'] ?? 0); if($machine_no<=0) jexit(['ok'=>false,'msg'=>'Missing machine_no'],400); $res = quality_by_machine($pdo,$COMPANY_ID,$machine_no); if($res) jexit(['ok'=>true,'quality'=>$res]); jexit(['ok'=>false,'msg'=>'Quality not found'],404); } case 'quality_list':{ $list = quality_list($pdo,$COMPANY_ID); jexit(['ok'=>true,'qualities'=>$list]); } case 'fetch_summary':{ $kcode = trim($_GET['khata'] ?? ''); $machine = (int)($_GET['machine'] ?? 0); $month = trim($_GET['month'] ?? ''); $period = trim($_GET['period'] ?? 'H1'); if(!$kcode || !$machine || !$month) jexit(['ok'=>false,'msg'=>'Missing params'],400); $kh = fetch_khata_by_code($pdo,$COMPANY_ID,$kcode); if(!$kh) jexit(['ok'=>false,'msg'=>'Khata not found'],404); $rng = month_period_to_range($month,$period); if(!$rng) jexit(['ok'=>false,'msg'=>'Invalid month/period'],400); list($from,$to) = $rng; $q = $pdo->prepare( "SELECT id,taka_no,entry_date,lines_json,quality_id FROM {$ENTRY_TABLE} WHERE company_id=? AND khata_id=? AND machine_id=? AND entry_date BETWEEN ? AND ? ORDER BY entry_date ASC,id ASC" ); $q->execute([$COMPANY_ID,(int)$kh['id'],$machine,$from,$to]); $rows = $q->fetchAll(PDO::FETCH_ASSOC); $agg = []; $kar_ids = []; foreach($rows as $r){ $entry_id = (int)$r['id']; $taka_no = $r['taka_no']; $lines = json_decode($r['lines_json'] ?? '[]', true); if(!is_array($lines)) continue; foreach($lines as $ln){ $ln_date = isset($ln['date']) ? substr($ln['date'],0,10) : $r['entry_date']; $kid = (int)($ln['karigar_id'] ?? 0); $m = floatval($ln['meter'] ?? 0); if($kid<=0) continue; $kar_ids[$kid]=1; if(!isset($agg[$kid])) $agg[$kid]=[]; if(!isset($agg[$kid][$ln_date])) $agg[$kid][$ln_date]=['details'=>[],'takas'=>[],'qualities'=>[]]; $agg[$kid][$ln_date]['details'][]=[ 'entry_id'=>$entry_id, 'taka_no'=>$taka_no, 'meter'=>$m, 'quality_id'=>($r['quality_id']??null) ]; if(!in_array($taka_no,$agg[$kid][$ln_date]['takas'],true)) $agg[$kid][$ln_date]['takas'][] = $taka_no; if($r['quality_id']) $agg[$kid][$ln_date]['qualities'][$r['quality_id']] = ($agg[$kid][$ln_date]['qualities'][$r['quality_id']] ?? 0) + $m; } } $dates=[]; $dstart=new DateTime($from); $dend=new DateTime($to); for($dt=$dstart; $dt <= $dend; $dt->modify('+1 day')) $dates[] = $dt->format('Y-m-d'); $assigned = fetch_assigned_karigars($pdo,$COMPANY_ID,$kh['id'],$machine); $all_kar = fetch_all_karigars_in_khata($pdo,$COMPANY_ID,$kh['id']); $out=[]; $seen=[]; foreach($assigned as $k){ $kid = (int)$k['id']; $seen[$kid]=1; $dlist=[]; foreach($dates as $dt){ if(isset($agg[$kid][$dt])){ $taks = $agg[$kid][$dt]['takas']; sort($taks,SORT_NUMERIC); $dlist[] = ['date'=>$dt,'has'=>true,'taka_display'=>implode('+',$taks), 'details'=>$agg[$kid][$dt]['details'], 'qualities'=>$agg[$kid][$dt]['qualities']]; }else{ $dlist[] = ['date'=>$dt,'has'=>false,'taka_display'=>'','details'=>[],'qualities'=>[]]; } } $out[] = ['karigar_id'=>$kid,'karigar_name'=>$k['karigar_name'],'dates'=>$dlist]; } foreach($agg as $kid=>$byd){ if(isset($seen[$kid])) continue; $st=$pdo->prepare("SELECT karigar_name FROM loom_karigar_master WHERE company_id=? AND id=? LIMIT 1"); $st->execute([$COMPANY_ID,$kid]); $r=$st->fetch(PDO::FETCH_ASSOC); $kname = $r ? $r['karigar_name'] : "K#{$kid}"; $dlist=[]; foreach($dates as $dt){ if(isset($agg[$kid][$dt])){ $taks = $agg[$kid][$dt]['takas']; sort($taks,SORT_NUMERIC); $dlist[] = ['date'=>$dt,'has'=>true,'taka_display'=>implode('+',$taks), 'details'=>$agg[$kid][$dt]['details'], 'qualities'=>$agg[$kid][$dt]['qualities']]; }else{ $dlist[] = ['date'=>$dt,'has'=>false,'taka_display'=>'','details'=>[],'qualities'=>[]]; } } $out[] = ['karigar_id'=>$kid,'karigar_name'=>$kname,'dates'=>$dlist]; } $kar_map=[]; foreach($all_kar as $k) $kar_map[]=['id'=>$k['id'],'name'=>$k['karigar_name']]; jexit(['ok'=>true,'from'=>$from,'to'=>$to,'dates'=>$dates,'karigars'=>$out,'all_karigars'=>$kar_map]); } case 'fetch_for_edit':{ $kcode = trim($_GET['khata'] ?? ''); $machine = (int)($_GET['machine'] ?? 0); $date = trim($_GET['date'] ?? ''); $kid = (int)($_GET['karigar_id'] ?? 0); if(!$kcode||!$machine||!$date||!$kid) jexit(['ok'=>false,'msg'=>'Missing params'],400); $kh = fetch_khata_by_code($pdo,$COMPANY_ID,$kcode); if(!$kh) jexit(['ok'=>false,'msg'=>'Khata not found'],404); $st = $pdo->prepare( "SELECT id,taka_no,lines_json,quality_id FROM {$ENTRY_TABLE} WHERE company_id=? AND khata_id=? AND machine_id=? AND entry_date=? ORDER BY id ASC" ); $st->execute([$COMPANY_ID,(int)$kh['id'],$machine,$date]); $rows = $st->fetchAll(PDO::FETCH_ASSOC); $out=[]; foreach($rows as $r){ $eid = (int)$r['id']; $taka = $r['taka_no']; $lines = json_decode($r['lines_json'] ?? '[]', true); if(!is_array($lines)) $lines=[]; $filtered=[]; foreach($lines as $ln){ $ln_date = isset($ln['date']) ? substr($ln['date'],0,10) : $date; if((int)($ln['karigar_id']??0) === $kid && $ln_date === $date){ $filtered[] = [ 'karigar_id'=> (int)$ln['karigar_id'], 'meter'=>floatval($ln['meter'] ?? 0), 'date'=>$ln_date ]; } } if(!empty($filtered)) $out[] = ['entry_id'=>$eid,'taka_no'=>$taka,'quality_id'=>($r['quality_id']??null),'lines'=>$filtered]; } jexit(['ok'=>true,'data'=>$out]); } case 'create_line':{ $in = json_decode(file_get_contents('php://input'), true); if(!$in) jexit(['ok'=>false,'msg'=>'Bad JSON'],400); $kcode = trim($in['khata'] ?? ''); $machine = (int)($in['machine'] ?? 0); $date = trim($in['date'] ?? ''); $kid = (int)($in['karigar_id'] ?? 0); $meter = floatval($in['meter'] ?? 0); $quality_id = (int)($in['quality_id'] ?? 0); if(!$kcode||!$machine||!$date||!$kid||$meter<=0) jexit(['ok'=>false,'msg'=>'Missing/invalid params'],400); $kh = fetch_khata_by_code($pdo,$COMPANY_ID,$kcode); if(!$kh) jexit(['ok'=>false,'msg'=>'Khata not found'],404); try{ $pdo->beginTransaction(); // auto‑fill quality if not sent $suggest = quality_by_machine($pdo,$COMPANY_ID,$machine); if($quality_id <= 0 && is_array($suggest) && !empty($suggest['quality_id'])) $quality_id = (int)$suggest['quality_id']; $s = $pdo->prepare("SELECT COALESCE(MAX(taka_no),0) FROM {$ENTRY_TABLE} WHERE company_id=? AND khata_id=? FOR UPDATE"); $s->execute([$COMPANY_ID,(int)$kh['id']]); $last=(int)$s->fetchColumn(); $next=$last+1; $lines = json_encode([['karigar_id'=>$kid,'meter'=>round($meter,2),'date'=>substr($date,0,10)]], JSON_UNESCAPED_UNICODE); $ins = $pdo->prepare( "INSERT INTO {$ENTRY_TABLE} (company_id,entry_date,machine_id,khata_id,quality_id,taka_no,lines_json,meter_total,created_by,created_at) VALUES (?,?,?,?,?,?,?,?,?,NOW())" ); $ins->execute([$COMPANY_ID,$date,$machine,(int)$kh['id'],$quality_id,$next,$lines,round($meter,2),$USER_ID]); $nid = $pdo->lastInsertId(); $pdo->commit(); jexit(['ok'=>true,'entry_id'=>$nid,'taka_no'=>$next,'meter'=>round($meter,2),'quality_id'=>$quality_id]); }catch(Throwable $e){ $pdo->rollBack(); jexit(['ok'=>false,'msg'=>'create failed','detail'=>$e->getMessage()],500); } } case 'add_extra':{ $in = json_decode(file_get_contents('php://input'), true); if(!$in) jexit(['ok'=>false,'msg'=>'Bad JSON'],400); $kcode = trim($in['khata'] ?? ''); $machine = (int)($in['machine'] ?? 0); $date = trim($in['date'] ?? ''); if(!$kcode || !$machine || !$date) jexit(['ok'=>false,'msg'=>'Missing params'],400); $meters = []; if(isset($in['meters']) && is_array($in['meters'])){ foreach($in['meters'] as $m){ $m2 = floatval($m); if($m2>0) $meters[] = round($m2,2); } }elseif(isset($in['meter'])){ $m2 = floatval($in['meter']); if($m2>0) $meters[] = round($m2,2); } if(empty($meters)) jexit(['ok'=>false,'msg'=>'No valid meter provided'],400); $note = trim($in['note'] ?? ''); $quality_id = isset($in['quality_id']) ? (int)$in['quality_id'] : 0; $kh = fetch_khata_by_code($pdo,$COMPANY_ID,$kcode); if(!$kh) jexit(['ok'=>false,'msg'=>'Khata not found'],404); try{ $pdo->beginTransaction(); // auto‑fill quality if missing $suggest = quality_by_machine($pdo,$COMPANY_ID,$machine); if($quality_id <= 0 && is_array($suggest) && !empty($suggest['quality_id'])) $quality_id = (int)$suggest['quality_id']; $sel = $pdo->prepare( "SELECT id,lines_json,meter_total,taka_no FROM {$ENTRY_TABLE} WHERE company_id=? AND khata_id=? AND machine_id=? AND entry_date=? ORDER BY id DESC LIMIT 1 FOR UPDATE" ); $sel->execute([$COMPANY_ID,(int)$kh['id'],$machine,$date]); if($row = $sel->fetch(PDO::FETCH_ASSOC)){ // Append extra to existing entry of that day $entry_id = (int)$row['id']; $orig = json_decode($row['lines_json'] ?? '[]', true); if(!is_array($orig)) $orig=[]; foreach($meters as $mval){ $orig[] = [ 'karigar_id'=>0, 'meter'=>round($mval,2), 'date'=>substr($date,0,10), 'extra_note'=>$note ]; } $new_total = round((float)$row['meter_total'] + array_sum($meters),2); if($quality_id>0){ $upd = $pdo->prepare("UPDATE {$ENTRY_TABLE} SET lines_json=?, meter_total=?, quality_id=? WHERE id=? AND company_id=?"); $upd->execute([json_encode($orig, JSON_UNESCAPED_UNICODE), $new_total, $quality_id, $entry_id, $COMPANY_ID]); }else{ $upd = $pdo->prepare("UPDATE {$ENTRY_TABLE} SET lines_json=?, meter_total=? WHERE id=? AND company_id=?"); $upd->execute([json_encode($orig, JSON_UNESCAPED_UNICODE), $new_total, $entry_id, $COMPANY_ID]); } $pdo->commit(); jexit(['ok'=>true,'action'=>'appended_extra','entry_id'=>$entry_id,'taka_no'=>$row['taka_no'], 'meter_added'=>array_sum($meters),'meter_total'=>$new_total]); } // No entry for that date → create a new one (only extra) $s2 = $pdo->prepare("SELECT COALESCE(MAX(taka_no),0) FROM {$ENTRY_TABLE} WHERE company_id=? AND khata_id=? FOR UPDATE"); $s2->execute([$COMPANY_ID,(int)$kh['id']]); $last = (int)$s2->fetchColumn(); $next = $last + 1; $lines = []; $sum = 0.0; foreach($meters as $mval){ $lines[] = ['karigar_id'=>0,'meter'=>round($mval,2),'date'=>substr($date,0,10),'extra_note'=>$note]; $sum += $mval; } $sum = round($sum,2); $ins = $pdo->prepare( "INSERT INTO {$ENTRY_TABLE} (company_id,entry_date,machine_id,khata_id,quality_id,taka_no,lines_json,meter_total,created_by,created_at) VALUES (?,?,?,?,?,?,?,?,?,NOW())" ); $ins->execute([$COMPANY_ID,$date,$machine,(int)$kh['id'],$quality_id,$next, json_encode($lines, JSON_UNESCAPED_UNICODE),$sum,$USER_ID]); $nid = $pdo->lastInsertId(); $pdo->commit(); jexit(['ok'=>true,'action'=>'created_extra','entry_id'=>$nid,'taka_no'=>$next,'meter_total'=>$sum]); }catch(Throwable $e){ $pdo->rollBack(); jexit(['ok'=>false,'msg'=>'add_extra failed','detail'=>$e->getMessage()],500); } } case 'update_entries':{ $in = json_decode(file_get_contents('php://input'), true); if(!$in || !isset($in['edits']) || !is_array($in['edits'])) jexit(['ok'=>false,'msg'=>'Bad JSON'],400); $edits = $in['edits']; $pdo->beginTransaction(); try{ $updated=[]; foreach($edits as $e){ $entry_id = (int)($e['entry_id'] ?? 0); if(!$entry_id) continue; $st = $pdo->prepare("SELECT lines_json FROM {$ENTRY_TABLE} WHERE company_id=? AND id=? FOR UPDATE"); $st->execute([$COMPANY_ID,$entry_id]); $row=$st->fetch(PDO::FETCH_ASSOC); if(!$row) continue; $orig = json_decode($row['lines_json'] ?? '[]', true); if(!is_array($orig)) $orig=[]; $to_replace=[]; $incoming = $e['lines'] ?? []; $norm = []; foreach($incoming as $ln){ $kd = (int)($ln['karigar_id'] ?? 0); $dd = substr(trim($ln['date'] ?? ''),0,10); $mraw = $ln['meter'] ?? ''; if($kd<=0 || $dd==='') continue; if(is_string($mraw) && strpos($mraw,'+')!==false){ $parts = array_filter(array_map('trim', explode('+',$mraw))); foreach($parts as $p){ $fv = floatval($p); if($fv>0) $norm[] = ['karigar_id'=>$kd,'meter'=>round($fv,2),'date'=>$dd]; } }else{ $mv = floatval($mraw); if($mv>0) $norm[] = ['karigar_id'=>$kd,'meter'=>round($mv,2),'date'=>$dd]; } $to_replace[] = $kd.'::'.$dd; } $new_lines=[]; foreach($orig as $ol){ $ok_k = (int)($ol['karigar_id'] ?? -1); $ok_d = substr(trim($ol['date'] ?? ''),0,10); if(in_array($ok_k.'::'.$ok_d, $to_replace, true)) continue; $new_lines[] = $ol; } foreach($norm as $nl){ $new_lines[] = [ 'karigar_id'=> (int)$nl['karigar_id'], 'meter'=> round(floatval($nl['meter']),2), 'date'=> substr($nl['date'],0,10) ]; } $sum = 0.0; foreach($new_lines as $nl) $sum += floatval($nl['meter'] ?? 0); $sum = round($sum,2); $upd = $pdo->prepare("UPDATE {$ENTRY_TABLE} SET lines_json=?, meter_total=? WHERE id=? AND company_id=?"); $upd->execute([json_encode($new_lines, JSON_UNESCAPED_UNICODE), $sum, $entry_id, $COMPANY_ID]); if(isset($e['quality_id'])){ $qv = (int)$e['quality_id']; $qup = $pdo->prepare("UPDATE {$ENTRY_TABLE} SET quality_id=? WHERE id=? AND company_id=?"); $qup->execute([$qv,$entry_id,$COMPANY_ID]); } $updated[] = ['entry_id'=>$entry_id,'meter_total'=>$sum]; } $pdo->commit(); jexit(['ok'=>true,'updated'=>$updated]); }catch(Throwable $e){ $pdo->rollBack(); jexit(['ok'=>false,'msg'=>'Update failed','detail'=>$e->getMessage()],500); } } case 'delete_entry':{ $in = json_decode(file_get_contents('php://input'), true); if(!$in || empty($in['entry_id'])) jexit(['ok'=>false,'msg'=>'Missing entry_id'],400); $eid = (int)$in['entry_id']; try{ $st = $pdo->prepare("SELECT id FROM {$ENTRY_TABLE} WHERE company_id=? AND id=? LIMIT 1"); $st->execute([$COMPANY_ID,$eid]); if(!$st->fetchColumn()){ jexit(['ok'=>false,'msg'=>'Entry not found'],404); } $del = $pdo->prepare("DELETE FROM {$ENTRY_TABLE} WHERE id=? AND company_id=?"); $del->execute([$eid,$COMPANY_ID]); jexit(['ok'=>true,'deleted_id'=>$eid,'rows'=>$del->rowCount()]); }catch(Throwable $e){ jexit(['ok'=>false,'msg'=>'Delete failed','detail'=>$e->getMessage()],500); } } case 'save_period':{ $in = json_decode(file_get_contents('php://input'), true); if(!$in) jexit(['ok'=>false,'msg'=>'Bad JSON'],400); $kcode = trim($in['khata'] ?? ''); $machine = (int)($in['machine'] ?? 0); $from = trim($in['from'] ?? ''); $to = trim($in['to'] ?? ''); $quality_id = isset($in['quality_id']) ? (int)$in['quality_id'] : 0; $lines = $in['aggregated_lines'] ?? null; $mode = $in['mode'] ?? 'insert'; if(!$kcode || !$machine || !$from || !$to || !is_array($lines) || empty($lines)) jexit(['ok'=>false,'msg'=>'Missing params'],400); $kh = fetch_khata_by_code($pdo,$COMPANY_ID,$kcode); if(!$kh) jexit(['ok'=>false,'msg'=>'Khata not found'],404); try{ $pdo->beginTransaction(); // quality auto‑fill $suggest = quality_by_machine($pdo,$COMPANY_ID,$machine); if($quality_id <= 0 && is_array($suggest) && !empty($suggest['quality_id'])) $quality_id = (int)$suggest['quality_id']; $s = $pdo->prepare("SELECT COALESCE(MAX(taka_no),0) FROM {$ENTRY_TABLE} WHERE company_id=? AND khata_id=? FOR UPDATE"); $s->execute([$COMPANY_ID,(int)$kh['id']]); $last=(int)$s->fetchColumn(); $next=$last+1; $json_lines = json_encode($lines, JSON_UNESCAPED_UNICODE); $meter_sum = 0.0; foreach($lines as $ln){ $meter_sum += floatval($ln['meter'] ?? 0); } $ins = $pdo->prepare( "INSERT INTO {$ENTRY_TABLE} (company_id,entry_date,machine_id,khata_id,quality_id,taka_no,lines_json,meter_total,created_by,created_at) VALUES (?,?,?,?,?,?,?,?,?,NOW())" ); $ins->execute([$COMPANY_ID,$from,$machine,(int)$kh['id'],$quality_id,$next,$json_lines,round($meter_sum,2),$USER_ID]); $new_id = $pdo->lastInsertId(); if($mode === 'move'){ $del = $pdo->prepare("DELETE FROM {$ENTRY_TABLE} WHERE company_id=? AND khata_id=? AND machine_id=? AND entry_date BETWEEN ? AND ?"); $del->execute([$COMPANY_ID,(int)$kh['id'],$machine,$from,$to]); } $pdo->commit(); jexit(['ok'=>true,'action'=>'saved_period','entry_id'=>$new_id,'taka_no'=>$next,'meter_total'=>round($meter_sum,2)]); }catch(Throwable $e){ $pdo->rollBack(); jexit(['ok'=>false,'msg'=>'save_period failed','detail'=>$e->getMessage()],500); } } default: jexit(['ok'=>false,'msg'=>'Unknown act'],404); } }catch(Throwable $e){ if(isset($_GET['debug']) && $_GET['debug']=='1') jexit(['ok'=>false,'msg'=>'Server error','detail'=>$e->getMessage()],500); jexit(['ok'=>false,'msg'=>'Server error'],500); } } /* ------------------------------------------------------------------------- Header‑include – fallback (जैसे पहले) ------------------------------------------------------------------------- */ $__header_paths = [ __DIR__ . '/partials/header.php', __DIR__ . '/erp/partials/header.php', __DIR__ . '/../erp/partials/header.php', __DIR__ . '/../../erp/partials/header.php', $_SERVER['DOCUMENT_ROOT'] . '/erp/partials/header.php', ]; $__header_included = false; foreach ($__header_paths as $__hp) { if ($__hp && file_exists($__hp)) { require_once $__hp; $__header_included = true; break; } } if (! $__header_included) { error_log('loom_production_entry_full.php: header.php not found. Tried: '.implode(' | ', $__header_paths)); echo "<!-- WARNING: header.php not found. -->\n"; echo "<div style=\"padding:8px;background:#fff4e5;border:1px solid #ffddb5;margin:8px;border-radius:6px;color:#b91c1c;font-size:13px\"> Header include not found. Check ERP partials path. </div>\n"; } ?> <!doctype html> <html lang="en"><head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width,initial-scale=1"/> <title>Loom Production Entry – Machine</title> <!-- ============================================================== 3️⃣ जावास्क्रिप्ट‑डिबग‑ब्लॉक (पेज के सबसे ऊपर) ============================================================== --> <script> /* ============================================================ SAFE HELPERS ============================================================ */ const $ = (id) => document.getElementById(id); /* ============================================================ fetchJSON – REQUIRED helper (missing tha) ============================================================ */ async function fetchJSON(url, opts = {}) { const resp = await fetch(url, { credentials: 'same-origin', ...opts }); const text = await resp.text(); let data; try { data = JSON.parse(text); } catch (e) { console.error('Non-JSON response:', text); throw new Error('Invalid JSON'); } if (!data.ok) { throw new Error(data.msg || 'Server error'); } return data; } /* ============================================================ DOM READY ============================================================ */ document.addEventListener('DOMContentLoaded', () => { /* -------------------------------------------------------- 1) LOAD INITIAL DATA (KHATA + QUALITY) -------------------------------------------------------- */ async function loadInit(){ const khSelect = $('khata'); if(!khSelect) return; khSelect.innerHTML = '<option>Loading...</option>'; /* ---- Khata list ---- */ try{ const j = await fetchJSON('?act=khata_list'); khSelect.innerHTML = '<option value="">Select Khata</option>'; (j.khatas || []).forEach(k=>{ const o = document.createElement('option'); o.value = k.code; o.dataset.from = k.machine_from; o.dataset.to = k.machine_to; o.textContent = k.code + ` — ${k.machine_from}-${k.machine_to}`; khSelect.appendChild(o); }); }catch(e){ console.error('khata_list failed', e); khSelect.innerHTML = '<option value="">(failed)</option>'; } /* ============================================================ KHATA → MACHINE AUTO FILL (MST1 = 1–40 etc.) ============================================================ */ $('khata')?.addEventListener('change', (e) => { const opt = e.target.selectedOptions[0]; const mSel = $('machine'); if (!mSel) return; // reset machine dropdown mSel.innerHTML = '<option value="">Select Machine</option>'; if (!opt) return; const from = parseInt(opt.dataset.from || 0, 10); const to = parseInt(opt.dataset.to || 0, 10); if (!from || !to || to < from) { console.warn('Invalid machine range for khata', opt.value, from, to); return; } for (let m = from; m <= to; m++) { const o = document.createElement('option'); o.value = m; o.textContent = 'Machine ' + m; mSel.appendChild(o); } // auto-select first machine (optional, safe) mSel.value = from; mSel.dispatchEvent(new Event('change', { bubbles: true })); }); /* ---- Quality list ---- */ try{ const q = await fetchJSON('?act=quality_list'); const qs = $('quality_select'); if(qs){ qs.innerHTML = '<option value="0">(select)</option>'; (q.qualities || []).forEach(qual=>{ const o = document.createElement('option'); o.value = qual.quality_id; o.textContent = qual.quality_name; qs.appendChild(o); }); } window.__qualityList = q.qualities || []; }catch(e){ console.warn('quality_list failed', e); } } loadInit(); /* -------------------------------------------------------- 2) KHATA CHANGE → LOAD KARIGARS -------------------------------------------------------- */ $('khata')?.addEventListener('change', async (e)=>{ const opt = e.target.selectedOptions[0]; const ek = $('extraKarigar'); if(!ek) return; ek.innerHTML = '<option value="">Select</option>'; if(!opt || !opt.value) return; try{ const j = await fetchJSON(`?act=khata_karigars&khata=${encodeURIComponent(opt.value)}`); (j.karigars || []).forEach(k=>{ const o = document.createElement('option'); o.value = k.id; o.textContent = k.karigar_name; ek.appendChild(o); }); }catch(e){ console.error('khata_karigars failed', e); } }); /* -------------------------------------------------------- 3) MACHINE CHANGE → AUTO QUALITY SUGGEST -------------------------------------------------------- */ $('machine')?.addEventListener('change', async (e)=>{ const m = parseInt(e.target.value || 0, 10); $('machineDisplay') && ($('machineDisplay').textContent = m ? m : '—'); if(!m) return; try{ const q = await fetchJSON(`?act=quality_by_machine&machine_no=${m}`); if(q?.quality?.quality_id && $('quality_select')){ $('quality_select').value = q.quality.quality_id; } }catch(e){ console.warn('quality_by_machine not found'); } if($('month')?.value) $('btnLoad')?.click(); }); /* -------------------------------------------------------- 4) LOAD BUTTON → FETCH SUMMARY -------------------------------------------------------- */ $('btnLoad')?.addEventListener('click', async ()=>{ const kh = $('khata')?.value?.trim(); const mach = parseInt($('machine')?.value || 0, 10); const month = $('month')?.value; const period = $('period')?.value || 'H1'; const wrap = $('summaryWrap'); if(!kh || !mach || !month){ alert('Fill khata, machine and month'); return; } if(wrap) wrap.innerHTML = 'Loading...'; try{ const j = await fetchJSON( `?act=fetch_summary&khata=${encodeURIComponent(kh)}&machine=${mach}&month=${encodeURIComponent(month)}&period=${period}` ); renderKarigars(j); }catch(e){ console.error('fetch_summary failed', e); wrap && (wrap.innerHTML = 'Error loading data'); } }); }); /* ============================================================ RENDER FUNCTIONS (UNCHANGED LOGIC) ============================================================ */ function renderKarigars(data){ window.__lastSummaryData = data; const wrap = document.getElementById('summaryWrap'); if(!wrap) return; wrap.innerHTML = ''; if(!data.karigars || !data.karigars.length){ wrap.innerHTML = '<div>No data</div>'; document.getElementById('grandTotalValue').textContent = '0'; return; } const grid = document.createElement('div'); grid.className = 'grid'; data.karigars.forEach(k=>{ grid.appendChild(renderCardForKarigar(k)); }); wrap.appendChild(grid); updateGrandTotal(); } function renderCardForKarigar(k){ const card = document.createElement('div'); card.className = 'card-kg prod-col'; card.dataset.kg = k.karigar_id; const header = document.createElement('div'); header.className = 'prod-header'; header.innerHTML = `<strong>${k.karigar_name}</strong> <span class="kg-total" data-total="0">0 mtr</span>`; card.appendChild(header); const rows = document.createElement('div'); rows.className = 'prod-rows'; k.dates.forEach(d=>{ const r = document.createElement('div'); r.className = 'prod-row'; r.innerHTML = `<span>${d.date}</span> <span class="meter">${d.has ? d.taka_display : ''}</span>`; rows.appendChild(r); }); card.appendChild(rows); updateCardTotal(card); return card; } function updateCardTotal(card){ let sum = 0; card.querySelectorAll('.meter').forEach(m=>{ m.textContent.split('+').forEach(x=>{ const n = parseFloat(x); if(!isNaN(n)) sum += n; }); }); sum = Math.round(sum * 100) / 100; card.querySelectorAll('.kg-total').forEach(t=>{ t.textContent = sum + ' mtr'; t.dataset.total = sum; }); updateGrandTotal(); } function updateGrandTotal(){ let g = 0; document.querySelectorAll('.kg-total').forEach(t=>{ const n = parseFloat(t.dataset.total || 0); if(!isNaN(n)) g += n; }); document.getElementById('grandTotalValue').textContent = Math.round(g * 100) / 100; } </script> <style> /* ----------------------- आपका मौजूदा CSS ----------------------- */ body{font-family:Inter,system-ui,Arial;margin:12px;background:#f8fafc;color:#0f172a} .page-wrap{max-width:1200px;margin:auto} .card{background:#fff;padding:14px;border:1px solid #e6e9ee;border-radius:10px;box-shadow:0 2px 6px rgba(15,23,42,0.02)} .filter-card .fg{margin-bottom:8px} .filter-card .fg label{font-size:13px;display:block;margin-bottom:3px} .filter-card .fg input,.filter-card .fg select{width:100%} .filter-card .action{margin-top:12px;display:flex;gap:8px} .extra-card{margin-left:12px} .extra-card .extra-title{font-weight:600;margin-bottom:6px} .extra-row{margin-bottom:8px;display:flex;gap:6px;align-items:center} .extra-row label{min-width:40px} .machine-card{margin-left:12px} .machine-box{display:flex;flex-direction:column;gap:8px} .machine-nav{display:flex;align-items:center;gap:4px;margin-bottom:8px} .machine-nav button{border:none;background:transparent;font-size:18px;cursor:pointer} .karigar-row{display:flex;gap:6px;align-items:center} .production-grid{margin-top:18px;display:flex;gap:12px;flex-wrap:wrap} .prod-col{background:#fff;border:1px solid #eef2ff;border-radius:10px;padding:10px;min-width:260px;max-width:360px} .prod-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px} .prod-header .name{font-weight:800} .prod-header .badge{background:#f1f5f9;color:#0f172a;padding:4px 8px;border-radius:8px} .prod-header button{font-size:12px;padding:4px 6px} .prod-rows .prod-row{display:flex;justify-content:space-between;align-items:center;padding:4px 0;border-top:1px dashed #f1f5f9} .prod-rows .prod-row:first-child{border-top:none} .prod-rows .prod-row .date{font-size:13px;color:#374151} .prod-rows .prod-row .meter{font-weight:600} .prod-rows .input-row{display:flex;gap:6px;align-items:center;padding:4px 0} .input-row input{flex:1} .prod-footer{margin-top:8px;border-top:1px dashed #f1f5f9;padding-top:8px;display:flex;justify-content:space-between;align-items:center} .prod-footer .total{font-weight:600} .grand-total{margin-top:16px;font-size:15px} .btn{background:#1f6feb;color:#fff;border:none;border-radius:6px;padding:6px 10px;cursor:pointer} .btn-ghost{background:#fff;color:#111;border:1px solid #e2e8f0} .btn-small{font-size:12px;padding:4px 8px} </style> </head><body> <div class="page-wrap"> <!-- ================== TOP CONTROLS ROW ================== --> <div class="top-row" style="display:flex;flex-wrap:wrap;gap:12px"> <!-- ===== LEFT: FILTER / LOAD CARD ===== --> <div class="card filter-card" style="flex:1 1 300px"> <div class="filter-grid"> <div class="fg"><label>Khata</label><select id="khata" class="input full"><option>Loading…</option></select></div> <div class="fg"><label>Machine</label><select id="machine" class="input small"> <option value="">Select Machine</option> </select> </div> <div class="fg"><label>Quality</label><select id="quality_select" class="input small"><option value="0">(select)</option></select></div> <div class="fg"><label>Month</label><input id="month" type="month" class="input small"></div> <div class="fg"><label>Period</label><select id="period" class="input small"><option value="H1">H1</option><option value="H2">H2</option></select></div> <div class="fg action"> <button id="btnFlip" class="btn btn-ghost btn-small">Flip order</button> <button id="btnLoad" class="btn btn-ghost btn-small">Load</button> </div> </div> </div> <!-- ===== MIDDLE: ADD EXTRA ===== --> <div class="card extra-card" style="flex:0 0 260px"> <div class="extra-box"> <div class="extra-title">Add Extra</div> <div class="extra-row"><label>Date</label><input id="extra_date" type="date" class="input"></div> <div class="extra-row"><label>MTR</label><input id="extra_meter" type="text" class="input" placeholder="e.g. 12 or 12+25"><button id="btnAddExtra" class="btn btn-small btn-ghost">Add</button></div> </div> </div> <!-- ===== RIGHT: MACHINE / KARIGAR ===== --> <div class="card machine-card" style="flex:0 0 260px"> <div class="machine-box"> <div class="machine-title">Machine</div> <div class="machine-nav"> <button id="btnPrevMachine" class="nav-btn" title="Previous machine">‹</button> <span class="machine-no" id="machineDisplay">—</span> <button id="btnNextMachine" class="nav-btn" title="Next machine">›</button> </div> <div class="karigar-row"> <select id="extraKarigar"><option value="">Select</option></select> <button id="btnAddKarigar" class="btn btn-small btn-ghost">Add</button> </div> </div> </div> </div> <!-- ================== PRODUCTION GRID ================== --> <div class="production-grid" id="summaryWrap"> <!-- cards will be injected here by JavaScript --> </div> <!-- ================== GRAND TOTAL ================== --> <div class="grand-total"><strong>Grand total:</strong> <span id="grandTotalValue">0</span> mtr</div> </div> <!-- end .page-wrap --> <script> /* ------------------------------------------------------------------------- नीचे आपका मौजूदा जावास्क्रिप्ट है – अब `fetchJSON` डिबगर के साथ काम करेगा ------------------------------------------------------------------------- */ const $ = id => document.getElementById(id); /* ------------------------------------------------------------- Helper – meter parsing (12+25 etc.) ------------------------------------------------------------- */ function parseMetersRaw(str){ if(typeof str!=='string') str = String(str||''); const parts = str.split('+').map(s=>s.trim()).filter(s=>s!==''); const nums = parts.map(p=>parseFloat(p)); if(nums.some(n=>isNaN(n)||n<=0)) return null; return nums; } /* ------------------------------------------------------------- 1️⃣ Load initial dropdowns (khata list + quality list) ------------------------------------------------------------- */ async function loadInit(){ const khSelect = $('khata'); khSelect.innerHTML = '<option>Loading…</option>'; try{ const j = await fetchJSON('?act=khata_list'); console.log('🔹 khata_list received →', j); khSelect.innerHTML = '<option value="">Select Khata</option>'; for(const k of j.khatas){ const o=document.createElement('option'); o.value = k.code; o.dataset.from = k.machine_from; o.dataset.to = k.machine_to; o.textContent = k.code + (k.machine_from && k.machine_to ? ` — ${k.machine_from}-${k.machine_to}` : ''); khSelect.appendChild(o); } }catch(e){ console.error('❌ khata_list failed →', e); khSelect.innerHTML = '<option value="">(failed)</option>'; } try{ const q = await fetchJSON('?act=quality_list'); console.log('🔹 quality_list received →', q); const qs = $('quality_select'); qs.innerHTML = '<option value="0">(select)</option>'; for(const qual of q.qualities){ const o=document.createElement('option'); o.value = qual.quality_id; o.textContent = qual.quality_name; qs.appendChild(o); } window.__qualityList = q.qualities||[]; }catch(e){ console.warn('⚠️ quality_list failed →', e); } } document.addEventListener('DOMContentLoaded', loadInit); /* ------------------------------------------------------------- 2️⃣ Khata change → fill “Add Karigar” dropdown ------------------------------------------------------------- */ $('khata').addEventListener('change', async (e)=>{ const opt = e.target.selectedOptions[0]; $('extraKarigar').innerHTML = '<option value="">Select</option>'; if(!opt) return; try{ const j = await fetchJSON(`?act=khata_karigars&khata=${encodeURIComponent(opt.value)}`); console.log('🔹 khata_karigars received →', j); const ek = $('extraKarigar'); ek.innerHTML = '<option value="">Select</option>'; for(const k of j.karigars){ const o=document.createElement('option'); o.value = k.id; o.textContent = k.karigar_name; ek.appendChild(o); } }catch(e){ console.error('❌ khata_karigars failed →', e); } }); /* ------------------------------------------------------------- 3️⃣ Machine change → auto‑suggest quality & auto‑load ------------------------------------------------------------- */ $('machine').addEventListener('change', async (e)=>{ const m = parseInt(e.target.value||0); $('machineDisplay').textContent = m ? String(m) : '—'; if(!m) return; // auto‑suggest quality try{ const q = await fetchJSON(`?act=quality_by_machine&machine_no=${m}`); console.log('🔹 quality_by_machine →', q); if (q && q.quality && q.quality.quality_id){ const qs = $('quality_select'); const opt = qs.querySelector(`option[value="${q.quality.quality_id}"]`); if(opt) qs.value = opt.value; } }catch(e){ console.warn('⚠️ quality_by_machine not found →', e); } // auto‑load if month already selected if($('month').value) $('btnLoad').click(); }); /* ------------------------------------------------------------- 4️⃣ Prev / Next machine navigation ------------------------------------------------------------- */ $('btnPrevMachine').addEventListener('click', ()=>{ const mInp = $('machine'); const cur = parseInt(mInp.value||0); if(!cur) return; mInp.value = cur-1; mInp.dispatchEvent(new Event('change',{bubbles:true})); }); $('btnNextMachine').addEventListener('click', ()=>{ const mInp = $('machine'); const cur = parseInt(mInp.value||0); if(!cur) return; mInp.value = cur+1; mInp.dispatchEvent(new Event('change',{bubbles:true})); }); /* ------------------------------------------------------------- 5️⃣ “Add Extra” (साइड‑कार्ड) ------------------------------------------------------------- */ $('btnAddExtra').addEventListener('click', async ()=>{ const kh = $('khata').value.trim(); const mach = parseInt($('machine').value||0); const date = $('extra_date').value; const raw = $('extra_meter').value.trim(); const qid = parseInt($('quality_select').value||0); if(!kh||!mach) return alert('Select khata & machine first'); if(!date) return alert('Pick a date'); const meters = parseMetersRaw(raw); if(!meters) return alert('Invalid meter format – use numbers or “12+25”'); const payload = {khata:kh,machine:mach,date:date,meters:meters,quality_id:qid}; console.log('🟦 Add Extra payload →', payload); try{ const resp = await fetchJSON('?act=add_extra', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload) }); console.log('✅ Add Extra response →', resp); alert('Extra saved – '+(resp.action||'OK')); // clear UI and reload grid $('extra_date').value=''; $('extra_meter').value=''; $('btnLoad').click(); }catch(err){ alert('Add Extra failed: '+err.message); } }); /* ------------------------------------------------------------- 6️⃣ “Add Karigar” – नई कॉलम बनायें ------------------------------------------------------------- */ $('btnAddKarigar').addEventListener('click', ()=>{ const sel = $('extraKarigar'); const kid = sel.value; if(!kid) return alert('Select a karigar first'); const name = sel.selectedOptions[0]?.textContent || ('K#'+kid); // यदि वही करिगर पहले से मौजूद है → पहले खाली इनपुट पर फोकस if(document.querySelector(`.card-kg[data-kg="${kid}"]`)){ const card = document.querySelector(`.card-kg[data-kg="${kid}"]`); const inp = card.querySelector('input.inlineMeter:not([disabled])'); if(inp){ inp.focus(); inp.select && inp.select(); } return; } if(!window.__lastSummaryData || !window.__lastSummaryData.dates) return alert('Load a period first (click Load)'); const k = { karigar_id:kid, karigar_name:name, dates:window.__lastSummaryData.dates.map(dt=>({date:dt,has:false,taka_display:'',details:[],qualities:[]})) }; const wrap = $('summaryWrap'); const grid = wrap.querySelector('.grid') || (function(){ const g=document.createElement('div'); g.className='grid'; wrap.appendChild(g); return g; })(); const card = renderCardForKarigar(k); grid.appendChild(card); const firstInp = card.querySelector('input.inlineMeter'); if(firstInp){ firstInp.focus(); firstInp.select && firstInp.select(); } }); /* ------------------------------------------------------------- 7️⃣ Render एक करिगर‑कॉलम (HTML skeleton के अनुसार) ------------------------------------------------------------- */ function renderCardForKarigar(k){ const card = document.createElement('div'); card.className='card-kg prod-col'; card.dataset.kg = k.karigar_id; /* ----- हेडर (नाम + टैटल) ----- */ const header = document.createElement('div'); header.className='prod-header'; const left = document.createElement('div'); left.style.display='flex'; left.style.alignItems='center'; left.style.gap='8px'; const name = document.createElement('div'); name.className='name'; name.textContent=k.karigar_name; const total = document.createElement('div'); total.className='kg-total'; total.textContent='0 mtr'; total.dataset.total='0'; left.appendChild(name); left.appendChild(total); const addBtn = document.createElement('button'); addBtn.className='btn btn-ghost btn-small'; addBtn.textContent='Add'; addBtn.onclick = ()=>{ const rows = Array.from(card.querySelectorAll('.kg-row')); for(const r of rows){ const inp = r.querySelector('input.inlineMeter'); if(inp && inp.value.trim()===''){ inp.focus(); inp.select && inp.select(); return; } } alert('No empty row – create a date first (by loading a period).'); }; header.appendChild(left); header.appendChild(addBtn); card.appendChild(header); /* ----- रोज़ (प्रति तिथि) ----- */ const rowsWrap = document.createElement('div'); rowsWrap.className='prod-rows'; k.dates.forEach(d=>{ const row = document.createElement('div'); row.className='kg-row prod-row'; row.dataset.date = d.date; const dateSpan = document.createElement('span'); dateSpan.className='date'; dateSpan.textContent = d.date; const right = document.createElement('div'); right.className='smallActions'; if(d.has){ const meters = d.details.map(x=>Number(x.meter||0)); const meterStr = meters.length ? meters.map(m=>Math.round(m*100)/100).join('+') : (d.taka_display||''); const disp = document.createElement('span'); disp.className='meter'; disp.textContent = meterStr; const edit = document.createElement('button'); edit.className='btn btn-small'; edit.textContent='Edit'; edit.dataset.date = d.date; edit.dataset.kid = k.karigar_id; edit.onclick = ()=>startEditInline(k.karigar_id,d.date,edit); right.appendChild(disp); right.appendChild(edit); }else{ const inp = document.createElement('input'); inp.type='text'; inp.className='inlineMeter'; inp.placeholder='mtr (e.g. 12 or 12+25)'; inp.dataset.kid = k.karigar_id; inp.dataset.date = d.date; inp.addEventListener('keydown', ev=>{ if(ev.key!=='Enter') return; ev.preventDefault(); const raw = (ev.target.value||'').trim(); if(raw===''){ focusNextDateInput(row); return; } handleQuickAddEnter(ev.target); }); right.appendChild(inp); } row.appendChild(dateSpan); row.appendChild(right); rowsWrap.appendChild(row); }); card.appendChild(rowsWrap); /* ----- फुटर (duplicate total) ----- */ const footer = document.createElement('div'); footer.className='prod-footer kg-footer'; const ftTotal = document.createElement('div'); ftTotal.className='kg-total'; ftTotal.textContent='0 mtr'; footer.appendChild(document.createElement('div')); // left placeholder footer.appendChild(ftTotal); card.appendChild(footer); updateCardTotal(card); return card; } /* ------------------------------------------------------------- 8️⃣ Render पूरी ग्रिड (Load बटन पर) ------------------------------------------------------------- */ function renderKarigars(data){ window.__lastSummaryData = data; const wrap = $('summaryWrap'); wrap.innerHTML=''; if(!data.karigars || !data.karigars.length){ wrap.innerHTML = '<div class="placeholder">No data for selected range.</div>'; $('grandTotalValue').textContent='0'; return; } const grid = document.createElement('div'); grid.className='grid'; const list = (window.__flipColumns) ? data.karigars.slice().reverse() : data.karigars; list.forEach(k=>grid.appendChild(renderCardForKarigar(k))); wrap.appendChild(grid); // “Add Karigar” dropdown – सभी करिगर को लोड करें const ek = $('extraKarigar'); ek.innerHTML = '<option value="">Select</option>'; (data.all_karigars||[]).forEach(k=>{ const o=document.createElement('option'); o.value=k.id; o.textContent=k.name; ek.appendChild(o); }); updateGrandTotal(); } /* ------------------------------------------------------------- 9️⃣ Totals handling ------------------------------------------------------------- */ function updateCardTotal(card){ const vals = Array.from(card.querySelectorAll('.prod-row .meter')).map(d=>d.textContent.trim()).filter(x=>x!==''); let sum = 0; vals.forEach(s=>{ s.split('+').forEach(p=>{ const n=parseFloat(p); if(!isNaN(n)) sum+=n; }); }); sum = Math.round(sum*100)/100; const spans = card.querySelectorAll('.kg-total'); spans.forEach(span=>{ span.textContent = sum+' mtr'; span.dataset.total = String(sum); }); updateGrandTotal(); } function updateGrandTotal(){ const cards = Array.from(document.querySelectorAll('.card-kg')); let grand = 0; cards.forEach(c=>{ const t=parseFloat(c.querySelector('.kg-total')?.dataset.total||0); grand+=isNaN(t)?0:t; }); grand = Math.round(grand*100)/100; $('grandTotalValue').textContent = grand; } /* ------------------------------------------------------------- 10️⃣ Quick‑add from खाली सेल ------------------------------------------------------------- */ async function handleQuickAddEnter(inp){ const raw = (inp.value||'').trim(); if(!raw) return alert('Enter meter'); const meters = parseMetersRaw(raw); if(!meters) return alert('Invalid meter format'); const kid = parseInt(inp.dataset.kid||0,10); const date = inp.dataset.date; const kh = $('khata').value.trim(); const mach = parseInt($('machine').value||0); const qid = parseInt($('quality_select').value||0); if(!kh||!mach) return alert('Select khata & machine'); console.log('⚡ Quick‑Add payload →', { khata:kh, machine:mach, date:date, karigar_id:kid, meter:meters, quality_id:qid }); inp.disabled = true; try{ const created=[]; for(const meter of meters){ const payload = {khata:kh,machine:mach,date:date,karigar_id:kid,meter:meter,quality_id:qid}; const resp = await fetchJSON('?act=create_line', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload) }); created.push(resp); } // Replace input with display + edit button const row = inp.closest('.kg-row'); const right = row.querySelector('.smallActions'); right.innerHTML = ''; const disp = document.createElement('span'); disp.className='meter'; disp.textContent = created.map(c=>Number(c.meter||0)).map(m=>Math.round(m*100)/100).join('+'); const edit = document.createElement('button'); edit.className='btn btn-small'; edit.textContent='Edit'; edit.dataset.date = date; edit.dataset.kid = kid; edit.onclick = ()=>startEditInline(kid,date,edit); right.appendChild(disp); right.appendChild(edit); const card = row.closest('.card-kg'); updateCardTotal(card); focusNextDateInput(row); }catch(err){ alert('Add failed: '+err.message); inp.disabled = false; } } /* ------------------------------------------------------------- 11️⃣ Move focus to अगले खाली इनपुट (same column) ------------------------------------------------------------- */ function focusNextDateInput(currentRow){ const card = currentRow.closest('.card-kg'); if(!card) return; const rows = Array.from(card.querySelectorAll('.kg-row')); const idx = rows.indexOf(currentRow); for(let i=idx+1;i<rows.length;i++){ const inp = rows[i].querySelector('input.inlineMeter'); if(inp && inp.value.trim()===''){ inp.focus(); inp.select && inp.select(); return; } } // backward fallback for(let i=idx-1;i>=0;i--){ const inp = rows[i].querySelector('input.inlineMeter'); if(inp && inp.value.trim()===''){ inp.focus(); inp.select && inp.select(); return; } } } /* ------------------------------------------------------------- 12️⃣ Inline Edit (Edit button) – unchanged, but uses fetchJSON ------------------------------------------------------------- */ async function startEditInline(kid,date,triggerBtn){ const kh = $('khata').value.trim(); const machine = parseInt($('machine').value||0); if(!kh||!machine) return alert('Select khata & machine'); try{ const j = await fetchJSON(`?act=fetch_for_edit&khata=${encodeURIComponent(kh)}&machine=${machine}&date=${encodeURIComponent(date)}&karigar_id=${kid}`); console.log('🛠️ Edit data →', j); const row = triggerBtn.closest('.kg-row'); const right = row.querySelector('.smallActions'); right.innerHTML = ''; if(!j.data || !j.data.length){ right.innerHTML = '<div class="placeholder">No editable entries</div>'; return; } const inputsMeta = []; j.data.forEach(entry=>{ const grp = document.createElement('div'); grp.style.marginBottom='8px'; const lbl = document.createElement('div'); lbl.style.fontWeight='700'; lbl.textContent = `Taka #${entry.taka_no} (Entry ${entry.entry_id})`; grp.appendChild(lbl); // quality selector const qSel = document.createElement('select'); qSel.className='input small'; qSel.appendChild(new Option('(keep current)', '')); (window.__qualityList||[]).forEach(q=>{ const o=document.createElement('option'); o.value = q.quality_id; o.textContent = q.quality_name; if(entry.quality_id && Number(q.quality_id)===Number(entry.quality_id)) o.selected=true; qSel.appendChild(o); }); grp.appendChild(qSel); entry.lines.forEach(ln=>{ const r = document.createElement('div'); r.style.display='flex'; r.style.gap='6px'; r.style.alignItems='center'; const inpt = document.createElement('input'); inpt.type='text'; inpt.className='inlineMeter'; inpt.value = (Math.round((ln.meter||0)*100)/100).toString(); inpt.placeholder='12 or 12+25'; inpt.addEventListener('keydown', ev=>{ if(ev.key==='Enter'){ ev.preventDefault(); saveBtn && saveBtn.click(); } if(ev.key==='Delete'){ ev.preventDefault(); deleteBtn && deleteBtn.click(); } }); r.appendChild(inpt); grp.appendChild(r); inputsMeta.push({ entry_id: entry.entry_id, karigar_id: ln.karigar_id, date: ln.date, el: inpt, qsel: qSel, entry_quality_id: entry.quality_id }); }); right.appendChild(grp); }); const saveBtn = document.createElement('button'); saveBtn.className='btn'; saveBtn.textContent='Save'; const cancelBtn = document.createElement('button'); cancelBtn.className='btn btn-ghost'; cancelBtn.textContent='Cancel'; const deleteBtn = document.createElement('button'); deleteBtn.className='btn btn-ghost'; deleteBtn.style.marginLeft='8px'; deleteBtn.textContent='Delete Entry'; deleteBtn.onclick = async ()=>{ if(!confirm('Delete this entry permanently? This cannot be undone.')) return; const firstMeta = inputsMeta[0]; if(!firstMeta || !firstMeta.entry_id) return alert('No entry id'); try{ deleteBtn.disabled = true; deleteBtn.textContent = 'Deleting…'; const resp = await fetch('?act=delete_entry', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({entry_id: Number(firstMeta.entry_id)}) }); const j = await resp.json(); if(!j.ok) throw new Error(j.msg||'Server error'); alert('Deleted entry #' + j.deleted_id); $('btnLoad').click(); }catch(err){ alert('Delete failed: '+(err.message||err)); deleteBtn.disabled = false; deleteBtn.textContent = 'Delete Entry'; } }; saveBtn.onclick = async ()=>{ const map = {}; const qualForEntry = {}; for(const meta of inputsMeta){ const raw = (meta.el.value||'').toString().trim(); if(raw==='' || meta.entry_id<=0) continue; const parts = raw.split('+').map(s=>s.trim()).filter(s=>s!==''); for(const p of parts){ const val = parseFloat(p); if(isNaN(val)||val<=0) continue; if(!map[meta.entry_id]) map[meta.entry_id] = []; map[meta.entry_id].push({karigar_id:meta.karigar_id,meter:val,date:(meta.date||date).substr(0,10)}); } const qv = meta.qsel && meta.qsel.value!=='' ? parseInt(meta.qsel.value||0,10) : undefined; if(typeof qv!=='undefined') qualForEntry[meta.entry_id] = qv; } const edits = Object.keys(map).map(eid=>{ const obj = {entry_id:parseInt(eid,10),lines:map[eid]}; if(typeof qualForEntry[eid]!=='undefined') obj.quality_id = qualForEntry[eid]; return obj; }); if(!edits.length) return alert('Nothing to save'); saveBtn.disabled = true; saveBtn.textContent = 'Saving…'; try{ await fetchJSON('?act=update_entries', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({edits}) }); await new Promise(r=>setTimeout(r,150)); $('btnLoad').click(); }catch(err){ alert('Save failed: '+err.message); } finally{ saveBtn.disabled = false; saveBtn.textContent = 'Save'; } }; cancelBtn.onclick = ()=> $('btnLoad').click(); const actions = document.createElement('div'); actions.style.marginTop='6px'; actions.appendChild(saveBtn); actions.appendChild(cancelBtn); actions.appendChild(deleteBtn); right.appendChild(actions); window.__currentDeleteEntryId = inputsMeta[0] ? inputsMeta[0].entry_id : null; }catch(err){ alert('Edit load failed: '+err.message); } } function sleep(ms){ return new Promise(r=>setTimeout(r,ms)); } /* ------------------------------------------------------------- 13️⃣ Load बटन – पूरे ग्रिड को बनाता है ------------------------------------------------------------- */ $('btnLoad').addEventListener('click', async ()=>{ const kh = $('khata').value.trim(); const mach = parseInt($('machine').value||0); const month = $('month').value; const period = $('period').value; if(!kh||!mach||!month) return alert('Fill khata, machine and month'); const wrap = $('summaryWrap'); wrap.innerHTML = '<div class="placeholder">Loading…</div>'; const btn = $('btnLoad'); btn.disabled=true; btn.textContent='Loading…'; try{ const j = await fetchJSON(`?act=fetch_summary&khata=${encodeURIComponent(kh)}&machine=${mach}&month=${encodeURIComponent(month)}&period=${encodeURIComponent(period)}`); console.log('📊 fetch_summary →', j); renderKarigars(j); // ensure quality list is cached (used by Edit popup) if(!window.__qualityList) { const q = await fetchJSON('?act=quality_list'); window.__qualityList = q.qualities||[]; } }catch(err){ wrap.innerHTML = `<div class="placeholder" style="color:#b91c1c">Error: ${err.message}</div>`; }finally{ btn.disabled=false; btn.textContent='Load'; updateMachineNavDisplay(); } }); /* ------------------------------------------------------------- 14️⃣ Flip order (columns reverse) ------------------------------------------------------------- */ $('btnFlip').addEventListener('click', ()=>{ window.__flipColumns = !window.__flipColumns; const grid = document.querySelector('.grid'); if(grid) grid.classList.toggle('flipColumns', !!window.__flipColumns); if(window.__lastSummaryData) renderKarigars(window.__lastSummaryData); }); /* ------------------------------------------------------------- 15️⃣ Keyboard shortcuts (Ctrl/Alt/Shift + Enter, Delete) ------------------------------------------------------------- */ document.addEventListener('keydown', function(e){ if(e.key==="Enter" && e.ctrlKey && !e.shiftKey && !e.altKey){ e.preventDefault(); const m = $('machine'); if(m){ m.focus(); m.select && m.select(); } return; } if(e.key==="Enter" && e.altKey && !e.ctrlKey && !e.shiftKey){ e.preventDefault(); const b = $('btnLoad'); if(b) b.click(); return; } if(e.key==="Enter" && e.shiftKey && !e.ctrlKey && !e.altKey){ e.preventDefault(); const ek = $('extraKarigar'); if(ek){ ek.focus(); } return; } if(e.key==="Enter" && e.ctrlKey && e.shiftKey){ e.preventDefault(); const ab = $('btnAddKarigar'); if(ab) ab.click(); return; } if(e.key==="Delete" && !e.ctrlKey && !e.altKey){ const active = document.activeElement; if(active && (active.classList && (active.classList.contains('inlineMeter') || active.closest('.card-kg'))){ if(window.__currentDeleteEntryId){ if(confirm('Delete current entry #' + window.__currentDeleteEntryId + '?')){ fetch('?act=delete_entry', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({entry_id: Number(window.__currentDeleteEntryId)}) }) .then(r=>r.json()).then(j=>{ if(!j.ok) return alert('Delete failed: '+(j.msg||'error')); alert('Deleted entry #' + j.deleted_id); $('btnLoad').click(); }).catch(err=>alert('Delete failed: '+err)); } } } } }); /* ------------------------------------------------------------- 16️⃣ Update machine display (top‑right) ------------------------------------------------------------- */ function updateMachineNavDisplay(){ const m = parseInt($('machine').value||0); $('machineDisplay').textContent = m ? String(m) : '—'; } window.addEventListener('load', ()=>{ updateMachineNavDisplay(); }); /* ------------------------------------------------------------- 17️⃣ Fallback – अगर header $PAGE_HIDE_TOPBAR को नहीं मानता तो आम‑टॉप‑बार सेलेक्टर्स को hide कर दें ------------------------------------------------------------- */ (function(){ try{ if (typeof window !== 'undefined') { setTimeout(()=>{ const selectors = ['#topbar','.topbar','#erp-topbar','.main-nav','.navbar','.header-menu']; selectors.forEach(s=>{ const el = document.querySelector(s); if(el) el.style.display='none'; }); },80); } }catch(e){} })(); </script> </body></html> <?php /* ============================================================== Footer include – fallback (जैसे पहले) ============================================================== */ $__footer_paths = [ __DIR__ . '/partials/footer.php', __DIR__ . '/erp/partials/footer.php', __DIR__ . '/../erp/partials/footer.php', __DIR__ . '/../../erp/partials/footer.php', $_SERVER['DOCUMENT_ROOT'] . '/erp/partials/footer.php', ]; $__footer_included = false; foreach ($__footer_paths as $__fp) { if ($__fp && file_exists($__fp)) { require_once $__fp; $__footer_included = true; break; } } if (! $__footer_included) { error_log('loom_production_entry_full.php: footer.php not found. Tried: '.implode(' | ', $__footer_paths)); echo "<!-- WARNING: footer.php not found. -->\n"; } ?>