« Back to History
saree_stock_entry.php
|
20260721_154033.php
Initial Bulk Import
Copy Code
<?php // saree_stock_entry.php (final, patched complete file) // - OUT mode blocked if insufficient stock (company + optional quality/design/color match) // - Add new lookups persisted to DB if table exists, otherwise saved locally (localStorage) // - OUT mode shows party dropdown; IN mode shows remark // - Print option added (prints last entry for current mode) // - Do NOT reset pcs on OUT (only reset for IN) // - No global config changes; DB/bootstrap fallback logic preserved /* -------------------- BOOTSTRAP / AUTH -------------------- */ $acl_candidates = [ __DIR__ . '/modules/auth/page_acl.php', __DIR__ . '/../modules/auth/page_acl.php', __DIR__ . '/../../modules/auth/page_acl.php', __DIR__ . '/erp/modules/auth/page_acl.php', ]; $acl_loaded = false; foreach ($acl_candidates as $p) { if (is_file($p)) { require_once $p; $acl_loaded = true; break; } } if (!$acl_loaded) { header('Content-Type: text/plain; charset=utf-8', true, 500); echo "Missing modules/auth/page_acl.php. Please ensure project bootstrap exists."; exit; } $ctx = page_require_access('saree_stock_entry'); $user = $ctx['user'] ?? null; $company_id = (int)($ctx['company_id'] ?? 0); $pdo = $ctx['pdo'] ?? null; /* DB fallback candidates (do not modify global config) */ if (!$pdo) { $db_candidates = [ __DIR__ . '/core/db.php', __DIR__ . '/../core/db.php', __DIR__ . '/../../core/db.php', ]; foreach ($db_candidates as $p) { if (is_file($p)) { require_once $p; break; } } } /* SESSION + CSRF */ if (session_status() === PHP_SESSION_NONE) session_start(); if (empty($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = bin2hex(random_bytes(16)); $csrf_token = $_SESSION['csrf_token']; /* helpers */ if (!function_exists('h')) { function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } } function require_first_existing(array $paths) { foreach ($paths as $p) { if (is_file($p)) { require_once $p; return true; } } return false; } /* -------------------- API Endpoints -------------------- */ $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; $action = $_REQUEST['action'] ?? null; if ($method === 'POST' && $action) { header('Content-Type: application/json; charset=utf-8'); $incoming_csrf = $_POST['csrf_token'] ?? ''; if (!hash_equals($_SESSION['csrf_token'], (string)$incoming_csrf)) { echo json_encode(['ok'=>false,'err'=>'CSRF']); exit; } if (!$pdo) { echo json_encode(['ok'=>false,'err'=>'DB missing']); exit; } if ($action === 'add_lookup') { $type = $_POST['type'] ?? ''; $name = trim($_POST['name'] ?? ''); if (!in_array($type, ['quality','design','color','party']) || $name === '') { echo json_encode(['ok'=>false,'err'=>'Invalid input']); exit; } $tableMap = [ 'quality' => 'saree_qualities', 'design' => 'saree_designs', 'color' => 'saree_colors', 'party' => 'saree_parties' ]; $table = $tableMap[$type]; $tblExists = false; try { $r = $pdo->query("SHOW TABLES LIKE " . $pdo->quote($table))->fetchAll(); if ($r) $tblExists = true; } catch (Exception $e) { $tblExists = false; } if (!$tblExists) { // Let client persist locally echo json_encode(['ok'=>true,'id'=>null,'name'=>$name,'note'=>'lookup_table_missing']); exit; } try { $ins = $pdo->prepare("INSERT INTO {$table} (company_id, name, created_by) VALUES (:cid, :name, :uid)"); $ins->execute([':cid'=>$company_id, ':name'=>$name, ':uid'=>$user['id'] ?? 0]); $id = (int)$pdo->lastInsertId(); echo json_encode(['ok'=>true,'id'=>$id,'name'=>$name]); exit; } catch (PDOException $ex) { // Duplicate -> return existing id if (isset($ex->errorInfo[1]) && $ex->errorInfo[1] == 1062) { $s = $pdo->prepare("SELECT id FROM {$table} WHERE company_id = :cid AND name = :name LIMIT 1"); $s->execute([':cid'=>$company_id, ':name'=>$name]); $row = $s->fetch(PDO::FETCH_ASSOC); if ($row) { echo json_encode(['ok'=>true,'id'=> (int)$row['id'],'name'=>$name]); exit; } } echo json_encode(['ok'=>false,'err'=>$ex->getMessage()]); exit; } } if ($action === 'save_entry') { $mode = ($_POST['mode'] ?? 'in') === 'out' ? 'out' : 'in'; $entry_date = $_POST['entry_date'] ?? null; $quality_text = trim($_POST['quality_text'] ?? ''); $design_text = trim($_POST['design_text'] ?? ''); $color_text = trim($_POST['color_text'] ?? ''); $saree_qty = (int)($_POST['saree_qty'] ?? 0); $remark = trim($_POST['remark'] ?? ''); $party_text = trim($_POST['party_text'] ?? ''); // Multi-color arrays (JSON decoded) $multi_colors_json = $_POST['multi_colors'] ?? '[]'; $multi_qtys_json = $_POST['multi_qtys'] ?? '[]'; $multi_colors = json_decode($multi_colors_json, true) ?: []; $multi_qtys = json_decode($multi_qtys_json, true) ?: []; if (!$entry_date) { echo json_encode(['ok'=>false,'err'=>'Missing date']); exit; } // Validation: either single qty or multi colors must have qty > 0 $has_single_qty = ($saree_qty > 0); $has_multi_qty = false; foreach ($multi_qtys as $q) { if ((int)$q > 0) { $has_multi_qty = true; break; } } if (!$has_single_qty && !$has_multi_qty) { echo json_encode(['ok'=>false,'err'=>'Enter qty (single ya multiple colors me se koi ek)']); exit; } if (!$pdo) { echo json_encode(['ok'=>false,'err'=>'DB missing']); exit; } // For OUT mode: stock validation for both single and multi-color entries if ($mode === 'out') { try { // Helper function to check available stock for given attributes $checkStock = function($quality, $design, $color) use ($pdo, $company_id) { $where = "company_id = :cid"; $params = [':cid'=>$company_id]; if ($quality !== '') { $where .= " AND TRIM(IFNULL(quality,'')) = :quality"; $params[':quality'] = $quality; } if ($design !== '') { $where .= " AND TRIM(IFNULL(design,'')) = :design"; $params[':design'] = $design; } if ($color !== '') { $where .= " AND TRIM(IFNULL(color,'')) = :color"; $params[':color'] = $color; } $sqlIn = "SELECT IFNULL(SUM(saree_qty),0) AS total_in FROM saree_stock_in WHERE {$where}"; $stmIn = $pdo->prepare($sqlIn); $stmIn->execute($params); $totalIn = (int)($stmIn->fetchColumn() ?: 0); $sqlOut = "SELECT IFNULL(SUM(saree_qty),0) AS total_out FROM saree_stock_out WHERE {$where}"; $stmOut = $pdo->prepare($sqlOut); $stmOut->execute($params); $totalOut = (int)($stmOut->fetchColumn() ?: 0); return $totalIn - $totalOut; }; // Check multi-color entries for ($i = 0; $i < count($multi_colors); $i++) { $color = trim($multi_colors[$i] ?? ''); $reqQty = (int)($multi_qtys[$i] ?? 0); if ($reqQty > 0) { $available = $checkStock($quality_text, $design_text, $color); if ($available <= 0) { echo json_encode(['ok'=>false,'err'=>"No stock available for color '{$color}'. Available: 0"]); exit; } if ($reqQty > $available) { echo json_encode(['ok'=>false,'err'=>"Insufficient stock for color '{$color}'. Available: {$available}, requested: {$reqQty}"]); exit; } } } // Check single entry if exists if ($saree_qty > 0) { $available = $checkStock($quality_text, $design_text, $color_text); if ($available <= 0) { echo json_encode(['ok'=>false,'err'=>'No stock available for the selected attributes. Available: 0']); exit; } if ($saree_qty > $available) { echo json_encode(['ok'=>false,'err'=>"Insufficient stock. Available: {$available}, requested: {$saree_qty}"]); exit; } } } catch (Exception $e) { // If stock check fails due to DB error, block the OUT to be safe echo json_encode(['ok'=>false,'err'=>'Stock validation failed: '.$e->getMessage()]); exit; } } // Proceed to insert if ($mode === 'in') { $table = 'saree_stock_in'; try { $pdo->beginTransaction(); $inserted_ids = []; // Insert single entry if exists if ($saree_qty > 0) { $ins = $pdo->prepare("INSERT INTO {$table} (company_id, entry_date, quality, design, color, saree_qty, remark, created_by) VALUES (:cid, :edate, :quality, :design, :color, :qty, :remark, :uid) "); $ins->execute([ ':cid'=>$company_id, ':edate'=>$entry_date, ':quality'=>$quality_text ?: null, ':design'=>$design_text ?: null, ':color'=>$color_text ?: null, ':qty'=>$saree_qty, ':remark'=>$remark ?: null, ':uid'=>$user['id'] ?? 0 ]); $inserted_ids[] = (int)$pdo->lastInsertId(); } // Insert multi-color entries for ($i = 0; $i < count($multi_colors); $i++) { $color = trim($multi_colors[$i] ?? ''); $reqQty = (int)($multi_qtys[$i] ?? 0); if ($reqQty > 0) { $ins = $pdo->prepare("INSERT INTO {$table} (company_id, entry_date, quality, design, color, saree_qty, remark, created_by) VALUES (:cid, :edate, :quality, :design, :color, :qty, :remark, :uid) "); $ins->execute([ ':cid'=>$company_id, ':edate'=>$entry_date, ':quality'=>$quality_text ?: null, ':design'=>$design_text ?: null, ':color'=>$color ?: null, ':qty'=>$reqQty, ':remark'=>$remark ?: null, ':uid'=>$user['id'] ?? 0 ]); $inserted_ids[] = (int)$pdo->lastInsertId(); } } $pdo->commit(); echo json_encode(['ok'=>true,'insert_ids'=>$inserted_ids]); exit; } catch (PDOException $ex) { $pdo->rollBack(); echo json_encode(['ok'=>false,'err'=>$ex->getMessage()]); exit; } } else { $table = 'saree_stock_out'; try { $pdo->beginTransaction(); $inserted_ids = []; // Insert single entry if exists if ($saree_qty > 0) { $ins = $pdo->prepare("INSERT INTO {$table} (company_id, entry_date, quality, design, color, saree_qty, party, created_by) VALUES (:cid, :edate, :quality, :design, :color, :qty, :party, :uid) "); $ins->execute([ ':cid'=>$company_id, ':edate'=>$entry_date, ':quality'=>$quality_text ?: null, ':design'=>$design_text ?: null, ':color'=>$color_text ?: null, ':qty'=>$saree_qty, ':party'=>$party_text ?: null, ':uid'=>$user['id'] ?? 0 ]); $inserted_ids[] = (int)$pdo->lastInsertId(); } // Insert multi-color entries for ($i = 0; $i < count($multi_colors); $i++) { $color = trim($multi_colors[$i] ?? ''); $reqQty = (int)($multi_qtys[$i] ?? 0); if ($reqQty > 0) { $ins = $pdo->prepare("INSERT INTO {$table} (company_id, entry_date, quality, design, color, saree_qty, party, created_by) VALUES (:cid, :edate, :quality, :design, :color, :qty, :party, :uid) "); $ins->execute([ ':cid'=>$company_id, ':edate'=>$entry_date, ':quality'=>$quality_text ?: null, ':design'=>$design_text ?: null, ':color'=>$color ?: null, ':qty'=>$reqQty, ':party'=>$party_text ?: null, ':uid'=>$user['id'] ?? 0 ]); $inserted_ids[] = (int)$pdo->lastInsertId(); } } $pdo->commit(); echo json_encode(['ok'=>true,'insert_ids'=>$inserted_ids]); exit; } catch (PDOException $ex) { $pdo->rollBack(); echo json_encode(['ok'=>false,'err'=>$ex->getMessage()]); exit; } } } echo json_encode(['ok'=>false,'err'=>'Unknown POST action']); exit; } /* -------------------- GET endpoints -------------------- */ if ($method === 'GET' && $action) { header('Content-Type: application/json; charset=utf-8'); if (!$pdo) { echo json_encode(['ok'=>false,'err'=>'DB missing']); exit; } if ($action === 'get_lookups') { $out = ['qualities'=>[], 'designs'=>[], 'colors'=>[], 'parties'=>[]]; $tryLookupThenDistinct = function($lookupTable, $stockColumn) use ($pdo, $company_id) { $res = []; try { $r = $pdo->query("SHOW TABLES LIKE " . $pdo->quote($lookupTable))->fetchAll(); if ($r) { $s = $pdo->prepare("SELECT id, name FROM {$lookupTable} WHERE company_id = :cid ORDER BY name"); $s->execute([':cid'=>$company_id]); $rows = $s->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $rw) $res[] = ['id'=> (int)$rw['id'], 'name'=>$rw['name']]; $u = $pdo->prepare(" SELECT DISTINCT TRIM({$stockColumn}) AS name FROM saree_stock_in WHERE company_id = :cid AND {$stockColumn} IS NOT NULL AND {$stockColumn} <> '' UNION SELECT DISTINCT TRIM({$stockColumn}) AS name FROM saree_stock_out WHERE company_id = :cid AND {$stockColumn} IS NOT NULL AND {$stockColumn} <> '' ORDER BY name "); $u->execute([':cid'=>$company_id]); $distinct = array_column($u->fetchAll(PDO::FETCH_ASSOC), 'name'); $existing = array_map('strtolower', array_column($rows, 'name')); foreach ($distinct as $name) { if (!in_array(strtolower($name), $existing)) $res[] = ['id'=>null,'name'=>$name]; } return $res; } else { $u = $pdo->prepare(" SELECT DISTINCT TRIM({$stockColumn}) AS name FROM saree_stock_in WHERE company_id = :cid AND {$stockColumn} IS NOT NULL AND {$stockColumn} <> '' UNION SELECT DISTINCT TRIM({$stockColumn}) AS name FROM saree_stock_out WHERE company_id = :cid AND {$stockColumn} IS NOT NULL AND {$stockColumn} <> '' ORDER BY name "); $u->execute([':cid'=>$company_id]); $distinct = $u->fetchAll(PDO::FETCH_ASSOC); foreach ($distinct as $d) $res[] = ['id'=>null,'name'=>$d['name']]; return $res; } } catch (Exception $e) { return $res; } }; $out['qualities'] = $tryLookupThenDistinct('saree_qualities','quality'); $out['designs'] = $tryLookupThenDistinct('saree_designs','design'); $out['colors'] = $tryLookupThenDistinct('saree_colors','color'); $out['parties'] = $tryLookupThenDistinct('saree_parties','party'); echo json_encode($out); exit; } // NEW: specific design ke liye saree colors list if ($action === 'get_colors_for_design') { $design = trim($_GET['design'] ?? ''); if ($design === '') { echo json_encode(['ok'=>true,'colors'=>[]]); exit; } try { $sql = " SELECT DISTINCT TRIM(color) AS color FROM saree_stock_in WHERE company_id = :cid AND design = :design AND color IS NOT NULL AND color <> '' UNION SELECT DISTINCT TRIM(color) AS color FROM saree_stock_out WHERE company_id = :cid AND design = :design AND color IS NOT NULL AND color <> '' ORDER BY color "; $st = $pdo->prepare($sql); $st->execute([':cid'=>$company_id, ':design'=>$design]); $colors = array_column($st->fetchAll(PDO::FETCH_ASSOC), 'color'); echo json_encode(['ok'=>true,'colors'=>$colors]); exit; } catch(Exception $e) { echo json_encode(['ok'=>false,'err'=>$e->getMessage()]); exit; } } if ($action === 'get_last_entry') { $mode = ($_GET['mode'] ?? 'in') === 'out' ? 'out' : 'in'; $table = $mode === 'in' ? 'saree_stock_in' : 'saree_stock_out'; $q = $pdo->prepare("SELECT * FROM {$table} WHERE company_id = :cid ORDER BY id DESC LIMIT 1"); $q->execute([':cid' => $company_id]); $row = $q->fetch(PDO::FETCH_ASSOC); echo json_encode(['ok'=>true,'row'=>$row ?: null]); exit; } if ($action === 'get_list') { $mode = ($_GET['mode'] ?? 'in') === 'out' ? 'out' : 'in'; $limit = (int)($_GET['limit'] ?? 200); $table = $mode === 'in' ? 'saree_stock_in' : 'saree_stock_out'; $q = $pdo->prepare("SELECT * FROM {$table} WHERE company_id = :cid ORDER BY id DESC LIMIT :lim"); $q->bindValue(':cid', $company_id, PDO::PARAM_INT); $q->bindValue(':lim', $limit, PDO::PARAM_INT); $q->execute(); $rows = $q->fetchAll(PDO::FETCH_ASSOC); echo json_encode(['ok'=>true,'rows'=>$rows]); exit; } if ($action === 'get_total') { $mode = ($_GET['mode'] ?? 'in') === 'out' ? 'out' : 'in'; $table = $mode === 'in' ? 'saree_stock_in' : 'saree_stock_out'; $q = $pdo->prepare("SELECT IFNULL(SUM(saree_qty),0) AS total FROM {$table} WHERE company_id = :cid"); $q->execute([':cid'=>$company_id]); $r = $q->fetch(PDO::FETCH_ASSOC); echo json_encode(['ok'=>true,'total'=> (int)($r['total'] ?? 0)]); exit; } echo json_encode(['ok'=>false,'err'=>'Unknown GET action']); exit; } /* -------------------- RENDER PAGE -------------------- */ $header_candidates = [ __DIR__ . '/partials/header.php', ]; $header_found = require_first_existing($header_candidates) ? true : false; ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Saree Stock Entry</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> /* preserved UI CSS (kept concise) */ body{background:#f5fff6;font-family:'Segoe UI',Arial,sans-serif;margin:0;padding:0} .container{width:650px;max-width:97vw;margin:30px auto} .panel{background:#89a7ba;border-radius:15px;padding:28px 35px 22px 35px;margin-bottom:18px;box-shadow:0 4px 20px #9fbed37c} .row{display:flex;gap:32px;flex-wrap:wrap;margin-bottom:10px} .panel-header{display:flex;justify-content:flex-end;gap:14px;margin-bottom:12px} .toggle-group{display:flex;align-items:center;gap:0} .toggle-btn{font-size:16px;padding:6px 22px;border:none;border-radius:18px;color:#444;cursor:pointer;background:#d5e5e4} .toggle-btn:last-child{border-radius:0 18px 18px 0;margin-left:-3px} .toggle-btn.selected{background:#4fc666;color:#fff;font-weight:bold} label{display:block;font-size:13px;color:#f4f8fa;margin-bottom:5px} /* FIXED: Select element styling - IMPORTANT CHANGES */ select { font-size: 18px; padding: 8px 12px; border: 1px solid #ccc; border-radius: 6px; background: #f4f8fa; width: 100%; box-sizing: border-box; height: 40px; min-height: 40px; display: block !important; visibility: visible !important; opacity: 1 !important; } input[type="text"], input[type="date"] { font-size: 18px; padding: 8px 12px; border: none; border-radius: 6px; background: #f4f8fa; width: 100%; box-sizing: border-box; } input[type="date"]{height:40px} .button-row{display:flex;justify-content:center;gap:18px;margin-top:16px;flex-wrap:wrap} button{font-size:18px;min-width:120px;padding:8px 0;background:#2faf4a;color:#fff;border:none;border-radius:6px;cursor:pointer} .btn-secondary{background:#2394c7} .entry-label{font-size:15px;color:#555;margin-bottom:12px;margin-left:6px} .entry-box{width:97%;padding:8px 10px;border-radius:7px;background:#f4f8fa;font-size:17px;color:#343;margin-left:6px} .modal-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.35);display:none;align-items:center;justify-content:center;z-index:9999} .modal{background:white;border-radius:10px;width:92%;max-width:680px;padding:14px;max-height:86vh;overflow:auto} .row-item{padding:8px;border-radius:6px;background:#f6f9f9;margin-bottom:8px} /* Multi-color wrapper styles */ #multi-color-wrapper {margin-top:10px; display:none;background:rgba(255,255,255,0.1);padding:10px;border-radius:5px;} .mc-row {display:flex;gap:8px;margin-bottom:6px;align-items:center} .mc-color {flex:2;padding:6px 8px;border-radius:5px;border:none;background:#eef3f4;font-size:16px;} .mc-qty {flex:1;padding:6px 8px;border-radius:5px;border:1px solid #ccc;background:#fff;font-size:16px;} @media (max-width:700px){ .container{width:99vw;padding:1vw} input[type="text"]{width:100%} input[type="date"]{width:100%} select{width:100%} .row{flex-direction:column} .button-row{gap:14px} .entry-box{width:96%} .mc-row {flex-direction:column; align-items:stretch;} .mc-color, .mc-qty {width:100%;} } /* Emergency dropdown fix */ #quality-select, #design-select, #color-select, #party-select { display: block !important; visibility: visible !important; opacity: 1 !important; height: 40px !important; min-height: 40px !important; background-color: #f4f8fa !important; border: 1px solid #ccc !important; padding: 8px !important; font-size: 16px !important; width: 100% !important; } </style> </head> <body> <div class="container" id="app"> <?php if (!$header_found): ?> <div style="background:#fff3cd;padding:10px;border-radius:6px;margin-bottom:12px;">Header not found in candidate paths. Page will still work.</div> <?php endif; ?> <input type="hidden" id="company_id" value="<?php echo h($company_id ?: 1); ?>"> <input type="hidden" id="csrf_token" value="<?php echo h($csrf_token); ?>"> <div class="panel"> <div class="panel-header"> <div class="toggle-group" role="tablist" aria-label="mode"> <button type="button" class="toggle-btn selected" id="btn-in" data-mode="in">in</button> <button type="button" class="toggle-btn" id="btn-out" data-mode="out">out</button> </div> </div> <div class="row"> <div style="flex:1 1 220px"> <label for="month">Date</label> <input type="date" id="month" value="<?php echo date('Y-m-d'); ?>"> </div> <div style="flex:1 1 220px"> <label for="quality">Quality</label> <div style="display:flex;gap:8px;align-items:center;"> <select id="quality-select" style="flex:1;"></select> </div> </div> </div> </div> <div class="panel"> <div class="row"> <div style="flex:1 1 220px"> <label for="design">Designe</label> <div style="display:flex;gap:8px;align-items:center"> <select id="design-select" style="flex:1;"></select> </div> </div> <div style="flex:1 1 220px"> <label for="color">Color</label> <div style="display:flex;gap:8px;align-items:center"> <select id="color-select" style="flex:1;"></select> </div> </div> <div style="flex:1 1 160px"> <label for="saree">Saree (qty)</label> <input type="text" id="saree" placeholder="12"> </div> <div style="flex:1 1 220px"> <label for="remark">Remark</label> <input type="text" id="remark" placeholder="All Fresh"> <select id="party-select" style="display:none;"></select> </div> </div> <!-- Multi-color wrapper section --> <div id="multi-color-wrapper"> <div style="font-weight:bold;color:#f4f8fa;margin-bottom:5px;">Colors for this design</div> <div id="multi-color-rows"></div> </div> <div class="button-row"> <button type="button" id="btn-save">save</button> <button type="button" id="btn-stock">stock</button> <button type="button" class="btn-secondary" id="btn-show">show entry</button> <button type="button" id="btn-print" style="background:#6b5bd6">print</button> </div> </div> <div class="panel"> <div class="entry-label">Last entry (<span id="current-mode-label">IN</span>)</div> <input type="text" class="entry-box" id="last-entry-box" value="Loading..." readonly> <div class="small muted" style="margin-left:6px;margin-top:6px;">Company: <span id="company-label"><?php echo h($company_id ?: 1); ?></span></div> </div> </div> <div class="modal-backdrop" id="modal-backdrop" aria-hidden="true"> <div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title"> <button class="close-btn btn-outline" id="modal-close">Close</button> <h3 id="modal-title">Entries</h3> <div id="modal-list" class="list"></div> </div> </div> <script> (() => { const csrf = document.getElementById('csrf_token').value; const companyId = document.getElementById('company_id').value || '1'; const btnIn = document.getElementById('btn-in'); const btnOut = document.getElementById('btn-out'); const currentModeLabel = document.getElementById('current-mode-label'); const lastEntryBox = document.getElementById('last-entry-box'); const selQuality = document.getElementById('quality-select'); const selDesign = document.getElementById('design-select'); const selColor = document.getElementById('color-select'); const selParty = document.getElementById('party-select'); const mcWrapper = document.getElementById('multi-color-wrapper'); const mcRowsBox = document.getElementById('multi-color-rows'); const fields = { month: document.getElementById('month'), saree: document.getElementById('saree'), remark: document.getElementById('remark') }; const btnSave = document.getElementById('btn-save'); const btnStock = document.getElementById('btn-stock'); const btnShow = document.getElementById('btn-show'); const btnPrint = document.getElementById('btn-print'); const modalBackdrop = document.getElementById('modal-backdrop'); const modalList = document.getElementById('modal-list'); const modalClose = document.getElementById('modal-close'); const modalTitle = document.getElementById('modal-title'); let mode = 'in'; // FIX 1: Ensure dropdowns are visible immediately function ensureDropdownsVisible() { [selQuality, selDesign, selColor, selParty].forEach(sel => { if (sel) { sel.style.display = 'block'; sel.style.visibility = 'visible'; sel.style.opacity = '1'; sel.style.height = '40px'; sel.style.minHeight = '40px'; } }); } function getSelectValue(sel) { const v = sel.value; if (!v) return ''; if (v.startsWith('id:')) return sel.options[sel.selectedIndex].text; if (v.startsWith('name:')) return v.slice(5); return sel.options[sel.selectedIndex].text; } // Robust setMode: toggle UI for remark vs party function setMode(m) { mode = m === 'out' ? 'out' : 'in'; btnIn.classList.toggle('selected', mode === 'in'); btnOut.classList.toggle('selected', mode === 'out'); currentModeLabel.textContent = mode === 'in' ? 'IN' : 'OUT'; try { if (mode === 'out') { fields.remark.style.display = 'none'; selParty.style.display = 'block'; selParty.required = true; fields.remark.required = false; fields.remark.value = ''; } else { fields.remark.style.display = 'block'; selParty.style.display = 'none'; selParty.required = false; selParty.value = ''; } } catch(e) { console.warn('setMode toggle issue', e); } loadLastEntry(); } btnIn.addEventListener('click', ()=> setMode('in')); btnOut.addEventListener('click', ()=> setMode('out')); // Design change -> load all colors for that design selDesign.addEventListener('change', async () => { const design_text = getSelectValue(selDesign); mcRowsBox.innerHTML = ''; mcWrapper.style.display = 'none'; if (!design_text) return; try { const res = await fetch(location.href + '?action=get_colors_for_design&design=' + encodeURIComponent(design_text)); const json = await res.json(); if (json.ok && Array.isArray(json.colors) && json.colors.length) { json.colors.forEach(c => { const row = document.createElement('div'); row.className = 'mc-row'; row.innerHTML = ` <input type="text" class="mc-color" value="${c}" readonly> <input type="number" class="mc-qty" placeholder="Qty" min="0"> `; mcRowsBox.appendChild(row); }); mcWrapper.style.display = 'block'; } else { mcWrapper.style.display = 'none'; } } catch (e) { console.error('get_colors_for_design failed', e); mcWrapper.style.display = 'none'; } }); // FIX 2: populateSelect with default values if empty function populateSelect(sel, items, type) { sel.innerHTML = ''; const empty = document.createElement('option'); empty.value = ''; empty.textContent = '-- select or type --'; sel.appendChild(empty); items = Array.isArray(items) ? items : []; // Add items from API items.forEach(it => { const o = document.createElement('option'); o.value = (it.id ? ('id:'+it.id) : ('name:'+it.name)); o.textContent = it.name; sel.appendChild(o); }); // Add default options if empty if (items.length === 0) { const defaultOptions = getDefaultOptions(type); defaultOptions.forEach(item => { const o = document.createElement('option'); o.value = 'name:' + item; o.textContent = item; sel.appendChild(o); }); } // Add localStorage items try { const key = 'saree_lookup_' + type; const stored = JSON.parse(localStorage.getItem(key) || '[]'); if (Array.isArray(stored) && stored.length) { stored.forEach(name => { let exists = false; for (let i=0;i<sel.options.length;i++){ if (sel.options[i].textContent.toLowerCase() === String(name).toLowerCase()) { exists = true; break; } } if (!exists) { const o2 = document.createElement('option'); o2.value = 'name:' + name; o2.textContent = name; sel.appendChild(o2); } }); } } catch(e){ console.warn('localStorage merge failed', e); } const addOpt = document.createElement('option'); addOpt.value = '__add_new'; addOpt.textContent = '--- Add new ---'; sel.appendChild(addOpt); sel.onchange = function() { if (this.value === '__add_new') { const name = prompt('Add new ' + type + ':'); if (name && name.trim()) { addLookup(type, name.trim()); } else { this.value = ''; } } }; // Ensure visibility sel.style.display = 'block'; sel.style.visibility = 'visible'; sel.style.opacity = '1'; } // Default options for each type function getDefaultOptions(type) { switch(type) { case 'quality': return ['Cotton', 'Silk', 'Georgette', 'Chiffon', 'Synthetic']; case 'design': return ['Printed', 'Embroidered', 'Plain', 'Sequined', 'Handloom']; case 'color': return ['Red', 'Blue', 'Green', 'Black', 'White', 'Yellow', 'Pink', 'Purple']; case 'party': return ['Customer A', 'Customer B', 'Retail Store', 'Wholesaler']; default: return []; } } async function addLookup(type, name) { try { const form = new URLSearchParams(); form.append('action','add_lookup'); form.append('csrf_token', csrf); form.append('type', type); form.append('name', name); const res = await fetch(location.href, { method:'POST', body: form }); const json = await res.json(); const target = type === 'quality' ? selQuality : (type === 'design' ? selDesign : (type === 'color' ? selColor : selParty)); if (json.ok) { if (json.note === 'lookup_table_missing' || json.id === null) { try { const key = 'saree_lookup_' + type; const stored = JSON.parse(localStorage.getItem(key) || '[]'); if (!stored.includes(json.name)) { stored.push(json.name); localStorage.setItem(key, JSON.stringify(stored)); } } catch(e){ console.warn('persist fail', e); } } const o = document.createElement('option'); o.value = (json.id ? ('id:'+json.id) : ('name:'+json.name)); o.textContent = json.name; target.insertBefore(o, target.querySelector('option[value="__add_new"]')); target.value = o.value; } else { alert('Add failed: ' + (json.err || 'unknown')); } } catch (e) { const target = type === 'quality' ? selQuality : (type === 'design' ? selDesign : (type === 'color' ? selColor : selParty)); const o = document.createElement('option'); o.value = 'name:' + name; o.textContent = name; target.insertBefore(o, target.querySelector('option[value="__add_new"]')); target.value = o.value; try { const key = 'saree_lookup_' + type; const stored = JSON.parse(localStorage.getItem(key) || '[]'); if (!stored.includes(name)) { stored.push(name); localStorage.setItem(key, JSON.stringify(stored)); } } catch(err){ console.warn('local persist fail', err); } } } async function loadLookups() { try { // Ensure dropdowns are visible before loading ensureDropdownsVisible(); const res = await fetch(location.href + '?action=get_lookups', {cache:'no-cache'}); const json = await res.json(); if (!json) { // Load default options if API fails populateSelect(selQuality, [], 'quality'); populateSelect(selDesign, [], 'design'); populateSelect(selColor, [], 'color'); populateSelect(selParty, [], 'party'); return; } // Make sure we have arrays const qualities = Array.isArray(json.qualities) ? json.qualities : []; const designs = Array.isArray(json.designs) ? json.designs : []; const colors = Array.isArray(json.colors) ? json.colors : []; const parties = Array.isArray(json.parties) ? json.parties : []; populateSelect(selQuality, qualities, 'quality'); populateSelect(selDesign, designs, 'design'); populateSelect(selColor, colors, 'color'); populateSelect(selParty, parties, 'party'); } catch (e) { console.error('lookup load failed', e); // Fallback to default options populateSelect(selQuality, [], 'quality'); populateSelect(selDesign, [], 'design'); populateSelect(selColor, [], 'color'); populateSelect(selParty, [], 'party'); } } async function loadLastEntry() { lastEntryBox.value = 'Loading...'; try { const res = await fetch(location.href + '?action=get_last_entry&mode=' + mode, {cache:'no-cache'}); const json = await res.json(); if (json.ok && json.row) { const r = json.row; if (mode === 'in') { lastEntryBox.value = (r.entry_date||'') + ' | ' + (r.quality||'') + ' | ' + (r.design||'') + ' | ' + (r.color||'') + ' | ' + (r.saree_qty||'0') + (r.remark ? ' — ' + r.remark : ''); } else { lastEntryBox.value = (r.entry_date||'') + ' | ' + (r.quality||'') + ' | ' + (r.design||'') + ' | ' + (r.color||'') + ' | ' + (r.saree_qty||'0') + (r.party ? ' — ' + r.party : ''); } } else { lastEntryBox.value = 'No entries yet for ' + (mode === 'in' ? 'IN' : 'OUT'); } } catch (e) { lastEntryBox.value = 'Error loading last entry'; } } // Save entry (single + multi color) btnSave.addEventListener('click', async () => { const quality_text = getSelectValue(selQuality); const design_text = getSelectValue(selDesign); const color_text = getSelectValue(selColor); const party_text = getSelectValue(selParty); let entry_date = fields.month.value.trim(); const saree_qty = Number(fields.saree.value || 0); if (!entry_date) { alert('Enter date'); fields.month.focus(); return; } // Multi-color rows collect const mcColors = []; const mcQtys = []; const colorEls = document.querySelectorAll('#multi-color-rows .mc-color'); const qtyEls = document.querySelectorAll('#multi-color-rows .mc-qty'); colorEls.forEach((el, idx) => { const q = Number(qtyEls[idx].value || 0); if (q > 0) { mcColors.push(el.value); mcQtys.push(q); } }); if (!saree_qty && mcQtys.length === 0) { alert('Enter qty (single ya multiple colors me se koi ek)'); fields.saree.focus(); return; } const payload = new URLSearchParams(); payload.append('action','save_entry'); payload.append('csrf_token', csrf); payload.append('mode', mode); payload.append('entry_date', entry_date); payload.append('quality_text', quality_text); payload.append('design_text', design_text); payload.append('color_text', color_text); payload.append('saree_qty', String(saree_qty)); if (mcColors.length) { payload.append('multi_colors', JSON.stringify(mcColors)); payload.append('multi_qtys', JSON.stringify(mcQtys)); } if (mode === 'in') { payload.append('remark', fields.remark.value.trim()); } else { payload.append('party_text', party_text); } btnSave.disabled = true; try { const res = await fetch(location.href, { method:'POST', body: payload }); const json = await res.json(); if (json.ok) { btnSave.textContent = 'Saved ✓'; setTimeout(()=> btnSave.textContent = 'save', 900); await loadLastEntry(); if (mode === 'in') { selColor.value = ''; fields.saree.value = ''; const qtyElsAll = document.querySelectorAll('#multi-color-rows .mc-qty'); qtyElsAll.forEach(el => el.value = ''); } } else { alert('Save failed: ' + (json.err || 'unknown')); } } catch (e) { alert('Save request failed'); } finally { btnSave.disabled = false; } }); btnStock.addEventListener('click', () => { window.location.href = '/erp/saree_stock_report.php'; }); btnShow.addEventListener('click', async () => { modalList.innerHTML = '<div class="row-item">Loading...</div>'; modalTitle.textContent = (mode === 'in' ? 'IN' : 'OUT') + ' entries (latest first)'; modalBackdrop.style.display = 'flex'; try { const res = await fetch(location.href + '?action=get_list&mode=' + mode + '&limit=200', {cache:'no-cache'}); const json = await res.json(); modalList.innerHTML = ''; if (json.ok && json.rows && json.rows.length) { for (const r of json.rows) { const d = document.createElement('div'); d.className = 'row-item'; if (mode === 'in') { d.textContent = (r.entry_date || '') + ' | ' + (r.quality || '') + ' | ' + (r.design || '') + ' | ' + (r.color || '') + ' | ' + (r.saree_qty || '0') + (r.remark ? ' — ' + r.remark : ''); } else { d.textContent = (r.entry_date || '') + ' | ' + (r.quality || '') + ' | ' + (r.design || '') + ' | ' + (r.color || '') + ' | ' + (r.saree_qty || '0') + (r.party ? ' — ' + r.party : ''); } modalList.appendChild(d); } } else { modalList.innerHTML = '<div class="row-item">No entries</div>'; } } catch (e) { modalList.innerHTML = '<div class="row-item">Failed to load entries</div>'; } }); modalClose.addEventListener('click', ()=> modalBackdrop.style.display = 'none'); modalBackdrop.addEventListener('click', (ev)=> { if (ev.target === modalBackdrop) modalBackdrop.style.display = 'none'; }); btnPrint.addEventListener('click', async () => { try { const res = await fetch(location.href + '?action=get_last_entry&mode=' + mode, {cache:'no-cache'}); const json = await res.json(); if (!json.ok || !json.row) { alert('No entry to print'); return; } const r = json.row; const html = ` <!doctype html> <html> <head> <meta charset="utf-8"> <title>Print Entry</title> <style> body{font-family:Arial,Helvetica,sans-serif;padding:20px;color:#111} .h{font-size:18px;font-weight:700;margin-bottom:10px} .row{margin-bottom:8px} .label{font-weight:600;width:140px;display:inline-block} </style> </head> <body> <div class="h">Saree Stock ${mode === 'in' ? 'IN' : 'OUT'} - Record</div> <div class="row"><span class="label">Date:</span> ${r.entry_date || ''}</div> <div class="row"><span class="label">Quality:</span> ${r.quality || ''}</div> <div class="row"><span class="label">Design:</span> ${r.design || ''}</div> <div class="row"><span class="label">Color:</span> ${r.color || ''}</div> <div class="row"><span class="label">Qty (pcs):</span> ${r.saree_qty || '0'}</div> ${mode === 'in' ? `<div class="row"><span class="label">Remark:</span> ${r.remark || ''}</div>` : `<div class="row"><span class="label">Party:</span> ${r.party || ''}</div>`} <div style="margin-top:20px">Printed: ${new Date().toLocaleString()}</div> </body> </html> `; const w = window.open('', '_blank', 'width=700,height=600'); w.document.open(); w.document.write(html); w.document.close(); setTimeout(()=> w.print(), 400); } catch (e) { alert('Failed to prepare print: ' + (e.message || e)); } }); // FIX 3: Immediate visibility on load window.addEventListener('DOMContentLoaded', function() { ensureDropdownsVisible(); // Add manual dropdown content if still empty setTimeout(() => { if (selQuality && selQuality.options.length <= 1) { const defaultQualities = getDefaultOptions('quality'); defaultQualities.forEach(item => { const o = document.createElement('option'); o.value = 'name:' + item; o.textContent = item; selQuality.appendChild(o); }); } }, 100); }); (async function init(){ // First ensure dropdowns are visible ensureDropdownsVisible(); // Then load data await loadLookups(); setMode('in'); // Final visibility check setTimeout(ensureDropdownsVisible, 500); })(); })(); // Emergency fix for dropdowns - runs immediately (function() { // Force all selects to be visible const style = document.createElement('style'); style.textContent = ` select, #quality-select, #design-select, #color-select, #party-select { display: block !important; visibility: visible !important; opacity: 1 !important; height: 40px !important; min-height: 40px !important; background-color: #f4f8fa !important; border: 1px solid #ccc !important; padding: 8px !important; font-size: 16px !important; width: 100% !important; } `; document.head.appendChild(style); // Add debug info window.addEventListener('load', function() { console.log('Dropdowns should be visible now'); console.log('Quality select:', document.getElementById('quality-select') ? 'Found' : 'Not found'); }); })(); </script> <!-- Manual dropdown content as fallback --> <script> window.addEventListener('load', function() { // Check if dropdowns have options const qualitySelect = document.getElementById('quality-select'); const designSelect = document.getElementById('design-select'); const colorSelect = document.getElementById('color-select'); // If dropdowns are empty, add default options manually setTimeout(function() { if (qualitySelect && qualitySelect.options.length <= 1) { const defaultQualities = ['Cotton', 'Silk', 'Georgette', 'Chiffon', 'Synthetic']; defaultQualities.forEach(item => { const option = document.createElement('option'); option.value = 'name:' + item; option.textContent = item; qualitySelect.appendChild(option); }); } if (designSelect && designSelect.options.length <= 1) { const defaultDesigns = ['Printed', 'Embroidered', 'Plain', 'Sequined', 'Handloom']; defaultDesigns.forEach(item => { const option = document.createElement('option'); option.value = 'name:' + item; option.textContent = item; designSelect.appendChild(option); }); } if (colorSelect && colorSelect.options.length <= 1) { const defaultColors = ['Red', 'Blue', 'Green', 'Black', 'White', 'Yellow', 'Pink']; defaultColors.forEach(item => { const option = document.createElement('option'); option.value = 'name:' + item; option.textContent = item; colorSelect.appendChild(option); }); } }, 2000); }); </script> </body> </html> <?php /* footer include (non-fatal) */ $footer_candidates = [ __DIR__ . '/partials/footer.php', ]; require_first_existing($footer_candidates);