query("SELECT id, name FROM projects ORDER BY name ASC"); $items = $conn->query("SELECT id, item_name FROM items ORDER BY item_name ASC"); // levels (you had levels before — keep them; change if you want to remove) $levels = [ "foundation" => "Foundation", "walls" => "Walls", "roofing" => "Roofing", "floorfinishes" => "Floor Finishes", "windows_doors" => "Windows & Doors" ]; /** * Load BOQ with its items and sub-items */ function loadBoq($conn, $id) { $id = (int)$id; $boqQ = $conn->prepare("SELECT * FROM bill_of_quantities WHERE id = ?"); $boqQ->bind_param("i", $id); $boqQ->execute(); $boq = $boqQ->get_result()->fetch_assoc(); if (!$boq) return null; // Load items $items = []; $stmt = $conn->prepare("SELECT bi.*, i.item_name AS item_lookup_name FROM boq_items bi LEFT JOIN items i ON bi.item_id = i.id WHERE bi.boq_id = ? ORDER BY bi.id ASC"); $stmt->bind_param("i", $id); $stmt->execute(); $res = $stmt->get_result(); while ($r = $res->fetch_assoc()) { // load subitems $r['sub_items'] = []; $stmt2 = $conn->prepare("SELECT id, sub_item_name FROM boq_sub_items WHERE boq_item_id = ? ORDER BY id ASC"); $stmt2->bind_param("i", $r['id']); $stmt2->execute(); $res2 = $stmt2->get_result(); while ($s = $res2->fetch_assoc()) $r['sub_items'][] = $s; $items[] = $r; } $boq['items'] = $items; return $boq; } // If editing existing BOQ (id provided) $editing = false; $boq = null; if (isset($_GET['id'])) { $boq = loadBoq($conn, intval($_GET['id'])); if ($boq) $editing = true; else $error = "BOQ not found."; } // Handle POST save (create new or update existing) if ($_SERVER['REQUEST_METHOD'] === 'POST') { $boq_name = trim($_POST['boq_name'] ?? ''); $project_id = intval($_POST['project_id'] ?? 0); $dbl_number = trim($_POST['dbl_number'] ?? ''); $item_ids = $_POST['item_id'] ?? []; // may be empty strings $item_names = $_POST['item_name'] ?? []; // fallback manual names if used $quantities = $_POST['quantity'] ?? []; $unit_costs = $_POST['unit_cost'] ?? []; $levels_selected = $_POST['level'] ?? []; $sub_items = $_POST['sub_item'] ?? []; // sub_item[name][rowIndex][] // Basic validation if ($boq_name === '') { $error = "BOQ name is required."; } elseif ($project_id <= 0) { $error = "Please select a project."; } else { // If editing existing, update header; else insert header if (!empty($_POST['boq_id'])) { $boq_id = (int)$_POST['boq_id']; $stmt = $conn->prepare("UPDATE bill_of_quantities SET project_id=?, boq_name=?, dbl_number=?, created_by=? WHERE id=?"); $stmt->bind_param("isssi", $project_id, $boq_name, $dbl_number, $manager, $boq_id); $ok = $stmt->execute(); } else { $stmt = $conn->prepare("INSERT INTO bill_of_quantities (project_id, boq_name, dbl_number, created_by, created_at) VALUES (?, ?, ?, ?, NOW())"); $stmt->bind_param("isss", $project_id, $boq_name, $dbl_number, $manager); $ok = $stmt->execute(); $boq_id = $stmt->insert_id; } if (!$ok) { $error = "Failed saving BOQ header: " . $conn->error; } else { // Transaction: delete existing items/subitems for this BOQ then insert posted rows $conn->begin_transaction(); try { // delete existing sub-items -> items if editing $delSub = $conn->prepare("DELETE s FROM boq_sub_items s JOIN boq_items bi ON s.boq_item_id = bi.id WHERE bi.boq_id = ?"); $delSub->bind_param("i", $boq_id); $delSub->execute(); $delItems = $conn->prepare("DELETE FROM boq_items WHERE boq_id = ?"); $delItems->bind_param("i", $boq_id); $delItems->execute(); // Insert rows posted (respect array lengths) $rows = max(count($item_ids), count($item_names), count($quantities), count($unit_costs)); for ($i = 0; $i < $rows; $i++) { // prefer item_id if numeric >0, else use posted item_name (manual) $item_id = isset($item_ids[$i]) && intval($item_ids[$i])>0 ? intval($item_ids[$i]) : null; $manual_name = trim($item_names[$i] ?? ''); $qty = isset($quantities[$i]) ? floatval($quantities[$i]) : 0; $cost = isset($unit_costs[$i]) ? floatval($unit_costs[$i]) : 0; $level = $levels_selected[$i] ?? ''; // determine whether row is valid (require either item_id or manual_name) if ((empty($item_id) && $manual_name === '') || ($qty <= 0 && $cost <= 0 && $manual_name === '' && empty($item_id))) { // skip completely empty rows continue; } // Insert item record. Save item_id as NULL if manual name used; we'll store manual_name in 'manual_item_name' column // (assumes column 'manual_item_name' exists on boq_items; if not, you can add it) $stmtItem = $conn->prepare("INSERT INTO boq_items (boq_id, item_id, manual_item_name, quantity, unit_cost, level) VALUES (?, ?, ?, ?, ?, ?)"); // bind types: i (boq_id), i (item_id or null), s (manual name), d (qty), d (cost), s (level) // MySQLi requires null substitution; pass null for item_id when manual_name used if ($item_id === null) { $nullItemId = null; $stmtItem->bind_param("iisdss", $boq_id, $nullItemId, $manual_name, $qty, $cost, $level); } else { $stmtItem->bind_param("iisdss", $boq_id, $item_id, $manual_name, $qty, $cost, $level); } // Above bind_param with null for integer field works in mysqli if using variables; to be safe we set item_id var // We'll use a safer approach: set $iid variable (int or NULL) and use mysqli_stmt::bind_param with 'i' but NULL must be passed correctly. // To handle NULL properly, re-prepare with two paths: $stmtItem->close(); if ($item_id === null) { $stmtItem = $conn->prepare("INSERT INTO boq_items (boq_id, item_id, manual_item_name, quantity, unit_cost, level) VALUES (?, NULL, ?, ?, ?, ?)"); $stmtItem->bind_param("idds", $boq_id, $manual_name, $qty, $cost, $level); } else { $stmtItem = $conn->prepare("INSERT INTO boq_items (boq_id, item_id, manual_item_name, quantity, unit_cost, level) VALUES (?, ?, ?, ?, ?, ?)"); $stmtItem->bind_param("iissd s", $boq_id, $item_id, $manual_name, $qty, $cost, $level); // note: we'll not use this path to avoid confusion // but to be precise, use the following correct bind for item_id present: $stmtItem->close(); $stmtItem = $conn->prepare("INSERT INTO boq_items (boq_id, item_id, manual_item_name, quantity, unit_cost, level) VALUES (?, ?, ?, ?, ?, ?)"); $stmtItem->bind_param("iissds", $boq_id, $item_id, $manual_name, $qty, $cost, $level); } // execute item insert if (!$stmtItem->execute()) { throw new Exception("Failed to insert BOQ item: " . $stmtItem->error); } $boq_item_id = $stmtItem->insert_id; $stmtItem->close(); // sub-items (only names required) - support multiple sub-items per item if (!empty($sub_items['name'][$i]) && is_array($sub_items['name'][$i])) { foreach ($sub_items['name'][$i] as $sub_name) { $sub_name = trim($sub_name); if ($sub_name === '') continue; $stmtSub = $conn->prepare("INSERT INTO boq_sub_items (boq_item_id, sub_item_name) VALUES (?, ?)"); $stmtSub->bind_param("is", $boq_item_id, $sub_name); if (!$stmtSub->execute()) { throw new Exception("Failed to insert sub-item: " . $stmtSub->error); } $stmtSub->close(); } } } $conn->commit(); $success = "BOQ saved successfully."; // reload BOQ for display $boq = loadBoq($conn, $boq_id); $editing = true; } catch (Exception $e) { $conn->rollback(); $error = "Failed saving BOQ items: " . $e->getMessage(); } } } } ?> <?php echo $editing ? "Edit BOQ" : "Create BOQ"; ?>


$row): $item_total = (float)$row['quantity'] * (float)$row['unit_cost']; ?>
Item (select existing or leave blank and type manual name below) Level Qty Unit Cost Item Total Sub Items (options / types only) Remove
×
0.00
×
BOQ Grand Total: 0.00
View / Print BOQ Delete BOQ
Your BOQs
prepare("SELECT b.id, b.boq_name, b.dbl_number, p.name as project_name, b.created_at FROM bill_of_quantities b JOIN projects p ON b.project_id = p.id WHERE b.created_by = ? ORDER BY b.created_at DESC"); $list->bind_param("s", $manager); $list->execute(); $resList = $list->get_result(); ?> fetch_assoc()): ?>
#NameDBLProjectCreatedAction
Edit View Delete