invoice.html
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Invoice</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script type="module" src="./js/invoice.js"></script>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr"
crossorigin="anonymous"
/>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ndDqU0Gzau9qJ1lfW4pNLlhNTkCfHzAVBReH9diLvGRem5+R9g2FzA8ZGN954O5Q"
crossorigin="anonymous"
></script>
</head>
<body>
<div class="container mt-3">
<div id="page-title-div"><h1 id="header-text">INVOICE PROCESSING</h1></div>
<div id="header-div" class="mt-5">
<div class="row">
<div class="col-lg-6">
<select class="form-select" id="students-select" title="Select Students"></select>
</div>
<div class="col-lg-6">
<input type="date" id="invoice-date" class="form-control" value="" />
</div>
</div>
</div>
<div class="mt-3 text-end">
<button type="button" class="btn btn-sm btn-primary" id="button-details">+</button>
</div>
<div id="details-div" class="mt-1">
<table class="table table-striped">
<thead>
<tr>
<th class="text-white bg-success">Product</th>
<th class="text-white bg-success">Qty</th>
<th class="text-white bg-success">Price</th>
<th class="text-white bg-success">Amount</th>
</tr>
</thead>
<tbody id="details-body"></tbody>
</table>
<div class="text-end"><h4 id="total-sales" style="color:green;">₱ 0.00</h4></div> </div>
<div class="row">
<div class="col-lg-12 text-end">
<button type="button" id="button-save" class="btn btn-primary">SAVE</button>
</div>
</div>
</div>
<!-- BLANK MODAL -->
<div class="modal fade" id="blank-modal" role="modal">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header text-white" style="background-color:#006400">
<h5 class="modal-title" id="blank-modal-title">Modal Title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="blank-modal-body">
<div class="row" id="blank-main-div" style="padding:5px;"></div>
</div>
<!-- <div class="modal-footer" id="blank-modal-footer">
<button type="button" class="btn btn-secondary btn-sm w-100" data-bs-dismiss="modal">Close</button>
</div> -->
</div>
</div>
</div>
</body>
</html>
invoice.js
const baseApiUrl = "http://localhost/jslecture/api";
let details = [];
let products = [];
const saveInvoice = async () => {
const header = {
studentId: document.getElementById("students-select").value,
invoiceDate: document.getElementById("invoice-date").value,
userId: 1, //the user's primary key (just defaulted to 1)
amount: getTotalSales(),
};
const jsonData = { header: header, details: details };
const formData = new FormData();
formData.append("operation", "saveInvoice");
formData.append("json", JSON.stringify(jsonData));
const response = await axios({
url: `${baseApiUrl}/invoice.php`,
method: "POST",
data: formData,
});
if (response.data == 1) {
alert("Invoice has been successfully saved!");
} else {
alert("ERROR!");
}
};
const openDetailsModal = async () => {
document.getElementById("blank-modal-title").innerText = "Add Invoice Details";
//get the products list and append them to the products select drop down
products = await getAllProducts();
let myHtml = `
<input type="number" class="form-control input-qty" id="qty" placeholder="qty" value="1" />
<select class="form-select product-select" id="product">
<option value="0">SELECT PRODUCT</option>
`;
products.forEach((product) => {
myHtml += `<option value="${product.product_id}">${product.product_name}</option>`;
});
myHtml += `</select>`;
const modalBody = document.getElementById("blank-main-div");
modalBody.innerHTML = myHtml;
//listen to change event of the product select
modalBody.querySelector(".product-select").addEventListener("change", (e) => {
//get the qty
const qty = modalBody.querySelector(".input-qty").value;
//get the selected product id
const productId = e.target.value;
//get price
const product = products.find((product) => product.product_id == productId);
if (product) {
const price = product.product_price;
//add to products list
const item = {
productId: product.product_id,
productName: product.product_name,
productPrice: product.product_price,
qty: qty,
amount: product.product_price * qty,
};
details.push(item);
displayDetails();
}
});
const myModal = new bootstrap.Modal(document.getElementById("blank-modal"), {
keyboard: true,
backdrop: "static",
});
myModal.show();
};
const displayDetails = () => {
//get the table body object
const tbody = document.getElementById("details-body");
//iterate thru the details list and display each in the table
let myHtml = ``;
details.forEach((detail) => {
myHtml += `
<tr>
<td>${detail.productName}</td>
<td>${detail.qty}</td>
<td>${detail.productPrice}</td>
<td style="text-align: right;">${formatCurrency(detail.amount)}</td>
</tr>
`;
});
tbody.innerHTML = myHtml;
//display total sales
document.getElementById("total-sales").innerText = `${formatCurrency(
getTotalSales()
)}`;
};
const getTotalSales = () => {
const totalSales = details.reduce((sum, item) => {
return sum + item.amount;
}, 0);
return totalSales;
};
const onPageLoad = async () => {
//load students to select element
const select = document.getElementById("students-select");
const students = await getAllStudents();
var html = `<option value="0">-- SELECT STUDENT --</option>`;
students.forEach((student) => {
html += `<option value=${student.stud_id}>${student.stud_last_name}, ${student.stud_first_name}</option>`;
});
select.innerHTML = html;
//set the date to today
document.getElementById("invoice-date").value = formatDateYYYYMMDD();
//set the onclick event of the details button
document.getElementById("button-details").addEventListener("click", () => {
openDetailsModal();
});
//set the onclick event of the save button
document.getElementById("button-save").addEventListener("click", () => {
saveInvoice();
});
};
const getAllStudents = async () => {
const response = await axios.get(`${baseApiUrl}/students.php`, {
params: { operation: "getAllStudents" },
});
if (response.status == 200) {
return response.data;
} else {
alert("Error!");
}
};
const getAllProducts = async () => {
const response = await axios.get(`${baseApiUrl}/products.php`, {
params: { operation: "getAllProducts" },
});
if (response.status == 200) {
return response.data;
} else {
alert("Error!");
}
};
const formatCurrency = (amount) => {
const formatted = new Intl.NumberFormat("en-PH", {
style: "currency",
currency: "PHP",
}).format(amount);
return formatted;
};
const formatDateYYYYMMDD = () => {
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, "0"); // Months start at 0
const dd = String(today.getDate()).padStart(2, "0");
return `${yyyy}-${mm}-${dd}`;
};
document.addEventListener("DOMContentLoaded", () => {
onPageLoad();
});
invoice.php
<?php
header('Content-Type: application/json');
header("Access-Control-Allow-Origin: *");
class Invoice
{
function saveInvoice($json)
{
include "connection-pdo.php";
$json = json_decode($json, true);
//get header and details data separately
$header = $json['header'];
$details = $json['details'];
try {
$conn->beginTransaction();
//save the header
$sql = "INSERT INTO tbl_invoice_header(hdr_student_id, hdr_date, hdr_user_id, hdr_total_amount)
VALUES(:studentId, :invoiceDate, :userId, :amount)";
$stmt = $conn->prepare($sql);
$stmt->bindParam(":studentId", $header['studentId']);
$stmt->bindParam(":invoiceDate", $header['invoiceDate']);
$stmt->bindParam(":userId", $header['userId']);
$stmt->bindParam(":amount", $header['amount']);
$stmt->execute();
//get the newly inserted record's Auto-Increment value
$newId = $conn->lastInsertId();
//iterate thru the details array and save each record
//which now includes the header id
$sqlDtl = "INSERT INTO tbl_invoice_details(dtl_header_id, dtl_product_id, dtl_price,
dtl_qty, dtl_amount) VALUES(:headerId, :productId, :price, :qty, :amount)
";
$stmtDtl = $conn->prepare($sqlDtl);
foreach ($details as $row) {
$stmtDtl->bindParam(":headerId", $newId);
$stmtDtl->bindParam(":productId", $row['productId']);
$stmtDtl->bindParam(":price", $row['productPrice']);
$stmtDtl->bindParam(":qty", $row['qty']);
$stmtDtl->bindParam(":amount", $row['amount']);
$stmtDtl->execute();
}
//commit changes
$conn->commit();
$returnValue = 1;
} catch (Exception $e) {
$conn->rollBack();
$returnValue = 0;
}
return json_encode($returnValue);
}
}
//submitted by the client - operation and json
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
$operation = $_GET['operation'];
$json = isset($_GET['json']) ? $_GET['json'] : "";
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$operation = $_POST['operation'];
$json = isset($_POST['json']) ? $_POST['json'] : "";
}
$invoice = new Invoice();
switch ($operation) {
case "saveInvoice":
echo $invoice->saveInvoice($json);
break;
}
products.php
<?php
header('Content-Type: application/json');
header("Access-Control-Allow-Origin: *");
class Product
{
function getAllProducts()
{
include "connection-pdo.php";
$sql = "SELECT * FROM tblproducts ORDER BY product_name";
$stmt = $conn->prepare($sql);
$stmt->execute();
$rs = $stmt->fetchAll(PDO::FETCH_ASSOC);
return json_encode($rs);
}
}
//submitted by the client - operation and json
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
$operation = $_GET['operation'];
$json = isset($_GET['json']) ? $_GET['json'] : "";
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$operation = $_POST['operation'];
$json = isset($_POST['json']) ? $_POST['json'] : "";
}
$product = new Product();
switch ($operation) {
case "getAllProducts":
echo $product->getAllProducts($json);
break;
}