Skip to content

Instantly share code, notes, and snippets.

@code-boxx
Last active May 26, 2023 12:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save code-boxx/c81e1baf81d8ac5c435720de3228087c to your computer and use it in GitHub Desktop.
Save code-boxx/c81e1baf81d8ac5c435720de3228087c to your computer and use it in GitHub Desktop.
PHP MYSQL Billing System

PHP MYSQL BILLING SYSTEM

https://code-boxx.com/billing-system-php-mysql/

IMAGES

code-boxx-logo

NOTES

  1. Create a database and import 1-database.sql.
  2. Change the database settings in 2-lib.php to your own.
  3. Download Invoicr
  4. Edit invlib/invoicr.php, set the company information to your own in (C1).
  5. Set the theme and output mode in 3-print.php.
  6. That's all – Launch 5a-admin.html in the browser.

LICENSE

Copyright by Code Boxx

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

-- (A) BILLS
CREATE TABLE `bills` (
`bill_id` bigint(20) NOT NULL,
`bill_to` text NOT NULL,
`bill_ship` text NOT NULL,
`bill_dop` date NOT NULL DEFAULT current_timestamp(),
`bill_due` date NOT NULL DEFAULT current_timestamp(),
`bill_notes` text DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `bills`
ADD PRIMARY KEY (`bill_id`),
ADD KEY (`bill_dop`),
ADD KEY (`bill_due`);
ALTER TABLE `bills`
MODIFY `bill_id` bigint(20) NOT NULL AUTO_INCREMENT;
-- (B) BILL ITEMS
CREATE TABLE `bill_items` (
`bill_id` bigint(20) NOT NULL,
`item_id` bigint(20) NOT NULL,
`item_name` varchar(255) NOT NULL,
`item_desc` varchar(255) DEFAULT NULL,
`item_qty` bigint(20) NOT NULL,
`item_each` decimal(12,2) NOT NULL,
`item_amt` decimal(12,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `bill_items`
ADD PRIMARY KEY (`bill_id`,`item_id`);
-- (C) BILL TOTALS
CREATE TABLE `bill_totals` (
`bill_id` bigint(20) NOT NULL,
`total_id` bigint(20) NOT NULL,
`total_name` varchar(255) NOT NULL,
`total_amt` decimal(12,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `bill_totals`
ADD PRIMARY KEY (`bill_id`,`total_id`);
<?php
class Bill {
// (A) CONSTRUCTOR - CONNECT TO DATABASE
private $pdo = null;
private $stmt = null;
public $error = "";
function __construct() {
$this->pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
}
// (B) DESTRUCTOR - CLOSE DATABASE CONNECTION
function __destruct () {
if ($this->stmt!==null) { $this->stmt = null; }
if ($this->pdo!==null) { $this->pdo = null; }
}
// (C) HELPER - EXECUTE SQL QUERY
function exec ($sql, $data=null) : void {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($data);
}
// (D) SAVE BILL
function save ($to, $ship, $dop, $due, $notes, $items, $totals, $id=null) {
// (D1) AUTO-COMMIT OFF
$this->pdo->beginTransaction();
// (D2) UPDATE ONLY - DELETE OLD ITEMS & TOTALS
if ($id!=null) {
$this->exec("DELETE FROM `bill_items` WHERE `bill_id`=?", [$id]);
$this->exec("DELETE FROM `bill_totals` WHERE `bill_id`=?", [$id]);
}
// (D3) MAIN ENTRY
if ($id==null) {
$sql = "INSERT INTO `bills` (`bill_to`, `bill_ship`, `bill_dop`, `bill_due`, `bill_notes`) VALUES (?,?,?,?,?)";
$data = [$to, $ship, $dop, $due, $notes];
} else {
$sql = "UPDATE `bills` SET `bill_to`=?, `bill_ship`=?, `bill_dop`=?, `bill_due`=?, `bill_notes`=? WHERE `bill_id`=?";
$data = [$to, $ship, $dop, $due, $notes, $id];
}
$this->exec($sql, $data);
if ($id==null) { $id = $this->pdo->lastInsertId(); }
// (D4) ITEMS
// (D4-1) ITEMS LIST
$data = []; $j = 1;
foreach ($items as $i) {
array_push($data, $id, $j, $i["n"], isset($i["d"])?$i["d"]:null, $i["q"], $i["e"], $i["a"]);
$j++;
}
// (D4-2) ITEMS SQL
$sql = "INSERT INTO `bill_items` (`bill_id`, `item_id`, `item_name`, `item_desc`, `item_qty`, `item_each`, `item_amt`) VALUES ";
$sql .= str_repeat("(?,?,?,?,?,?,?),", $j-1);
$sql = substr($sql, 0, -1) . ";";
// (D4-3) INSERT ITEMS
$this->exec($sql, $data);
// (D5) TOTALS
// (D5-1) TOTALS LIST
$data = []; $j = 1;
foreach ($totals as $t) {
array_push($data, $id, $j, $t["n"], $t["a"]);
$j++;
}
// (D5-2) ITEMS SQL
$sql = "INSERT INTO `bill_totals` (`bill_id`, `total_id`, `total_name`, `total_amt`) VALUES ";
$sql .= str_repeat("(?,?,?,?),", $j-1);
$sql = substr($sql, 0, -1) . ";";
// (D5-3) INSERT TOTALS
$this->exec($sql, $data);
// (D6) DONE
$this->pdo->commit();
return true;
}
// (E) DELETE BILL
function del ($id) {
$this->pdo->beginTransaction();
$this->exec("DELETE FROM `bills` WHERE `bill_id`=?", [$id]);
$this->exec("DELETE FROM `bill_items` WHERE `bill_id`=?", [$id]);
$this->exec("DELETE FROM `bill_totals` WHERE `bill_id`=?", [$id]);
$this->pdo->commit();
return true;
}
// (F) GET ALL BILLS
function getAll () {
$this->exec("SELECT * FROM `bills`");
return $this->stmt->fetchAll();
}
// (G) GET BILL
function get ($id) {
// (G1) MAIN ENTRY
$this->exec("SELECT * FROM `bills` WHERE `bill_id`=?", [$id]);
$bill = $this->stmt->fetch();
if ($bill===false) { return false; }
// (G2) ITEMS
$this->exec("SELECT `item_name`, `item_desc`, `item_qty`, `item_each`, `item_amt` FROM `bill_items` WHERE `bill_id`=?", [$id]);
$bill["items"] = $this->stmt->fetchAll(PDO::FETCH_NUM);
// (G3) TOTALS
$this->exec("SELECT `total_name`, `total_amt` FROM `bill_totals` WHERE `bill_id`=?", [$id]);
$bill["totals"] = $this->stmt->fetchAll(PDO::FETCH_NUM);
// (G4) DONE
return $bill;
}
}
// (H) SETTINGS - CHANGE THESE TO YOUR OWN !
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8mb4");
define("DB_USER", "root");
define("DB_PASSWORD", "");
// (I) DATABASE OBJECT
$_BILL = new Bill();
<?php
// (0) DO THESE FIRST!
// cd YOUR-HTTP-FOLDER/invlib
// composer require phpoffice/phpword
// composer require mpdf/mpdf
// edit invlib/invoicr.php > set your own company data (c1)
// download https://github.com/code-boxx/invoicr/
// (A) GET BILL
if (!isset($_GET["id"])) { exit("Invalid bill"); }
require "2-lib.php";
$bill = $_BILL->get($_GET["id"]);
if ($bill===false) { exit("Invalid bill"); }
// (B) GENERATE INVOICE
require "invlib/invoicr.php";
$invoicr->set("head", [
["Invoice #", $bill["bill_id"]],
["DOP", $bill["bill_dop"]],
["Due Date", $bill["bill_due"]]
]);
$invoicr->set("billto", preg_split("/\r\n|\r|\n/", $bill["bill_to"]));
$invoicr->set("shipto", preg_split("/\r\n|\r|\n/", $bill["bill_ship"]));
$invoicr->set("items", $bill["items"]);
$invoicr->set("totals", $bill["totals"]);
$invoicr->set("notes", preg_split("/\r\n|\r|\n/", $bill["bill_notes"]));
// (C) CHOOSE A TEMPLATE
$invoicr->template("apple");
// $invoicr->template("banana");
// $invoicr->template("blueberry");
// $invoicr->template("lime");
// $invoicr->template("simple");
// $invoicr->template("strawberry");
// (D) OUTPUT
// (D1) OUTPUT IN HTML
$invoicr->outputHTML(); // display in browser
// $invoicr->outputHTML(1); // display in browser
// $invoicr->outputHTML(2, "invoice.html"); // force download
// $invoicr->outputHTML(3, "invoice.html"); // save to file on server
// (D2) OUTPUT IN PDF
// $invoicr->outputPDF(); // display in browser
// $invoicr->outputPDF(1); // display in browser
// $invoicr->outputPDF(2, "invoice.pdf"); // force download
// $invoicr->outputPDF(3, "invoice.pdf"); // save to file on server
// (D3) OUTPUT IN DOCX
// $invoicr->outputDOCX(); // display in browser
// $invoicr->outputDOCX(1, "invoice.docx"); // force download
// $invoicr->outputDOCX(2, "invoice.docx"); // save to file on server
<?php
if (isset($_POST["req"])) {
require "2-lib.php";
switch($_POST["req"]) {
// (A) SAVE BILL
case "save":
echo $_BILL->save(
$_POST["to"], $_POST["ship"], $_POST["dop"], $_POST["due"], $_POST["notes"],
json_decode($_POST["items"], 1), json_decode($_POST["totals"], 1),
isset($_POST["id"]) ? $_POST["id"] : null
) ? "OK" : "ERROR" ;
break;
// (B) DELETE BILL
case "del":
echo $_BILL->del($_POST["id"]) ? "OK" : "ERROR" ;
break;
// (C) GET ALL BILLS
case "getAll":
echo json_encode($_BILL->getAll());
break;
// (D) GET BILL
case "get":
echo json_encode($_BILL->get($_POST["id"]));
break;
}}
/* (A) WHOLE PAGE */
:root {
--bg-color-a: #f2f2f2; /* body */
--bg-color-b: #fff; /* sections */
--bg-color-c: #f7f7f7; /* item rows */
--bd-color: #e2e2e2; /* section/item/row border */
--t-color-a: #e33636; /* text accent */
--t-color-b: #474747; /* "muted" text labels & info */
--b-color-t: #fff; /* button text */
--b-color-b: #e33636; /* button background */
}
* {
font-family: Arial, Helvetica, sans-serif;
box-sizing: border-box;
}
body {
max-width: 620px;
padding: 15px;
margin: 0 auto;
background: var(--bg-color-a);
}
/* (B) COMMON SHARED */
.hide { display: none !important; }
.section {
padding: 20px;
border: 1px solid var(--bd-color);
background: var(--bg-color-b);
}
.flex {
display: flex;
align-items: center;
}
.bRow {
margin-top: 10px;
padding: 10px;
border: 1px solid var(--bd-color);
background: var(--bg-color-c);
}
.ico {
font-size: 28px;
cursor: pointer;
}
/* (C) BILLS LIST */
#blAdd {
text-align: center;
font-size: 24px;
color: var(--t-color-a);
border: 2px dashed var(--t-color-a);
padding: 10px;
cursor: pointer;
}
.blInfo {
flex-grow: 1;
font-size: 14px;
color: var(--t-color-b);
}
.blBtn {
color: var(--t-color-a);
padding: 0 8px;
}
/* (D) BILL FORM */
#bfWrap h2 { margin: 0; }
#bfWrap h4 {
color: var(--t-color-a);
margin: 20px 0 10px 0;
}
#bfWrap label, #bfWrap textarea, #bfWrap input[type=text], #bfWrap input[type=date], #bfWrap input[type=number] {
display: block;
width: 100%;
resize: none;
}
#bfWrap label {
color: var(--t-color-b);
font-weight: 700;
padding: 10px 0;
}
#bfWrap textarea { height: 80px; }
#bfWrap textarea, #bfWrap input[type=text], #bfWrap input[type=date], #bfWrap input[type=number] {
padding: 10px;
border: 1px solid var(--bd-color);
}
#bfWrap input[type=button], #bfWrap input[type=submit] {
border: 0;
padding: 10px 20px;
margin-top: 20px;
font-weight: 700;
color: var(--b-color-t);
background: var(--b-color-b);
cursor: pointer;
}
/* (E) ITEM/TOTAL SHARED */
.bDel { padding: 0 20px 0 10px; }
.bfInfo { flex-grow: 1; }
.bfiName, .bftTitle { font-weight: 700; }
#bfiForm, #bftForm {
padding: 10px;
margin-top: 10px;
border: 1px solid var(--bd-color);
background: var(--bg-color-c);
}
#bfiForm *, #bftForm * { margin: 0 !important; }
/* (F) BILL FORM ADD ITEM */
#bfiForm {
display: grid;
grid-template-areas: "a a b b" "c d d e";
grid-gap: 5px;
}
#bfiName { grid-area: a; }
#bfiDesc { grid-area: b; }
#bfiQty { grid-area: c; }
#bfiEach { grid-area: d; }
#bfiAdd { grid-area: e; }
<!DOCTYPE html>
<html>
<head>
<title>Demo Billing Admin</title>
<meta charset="utf-8">
<link rel="stylesheet" href="5a-admin.css">
<script src="5b-admin.js"></script>
</head>
<body>
<!-- (A) BILLS LIST -->
<div id="blWrap" class="section">
<div id="blAdd" onclick="bill.toggle(0)">&#10010;</div>
<div id="blList"></div>
</div>
<!-- (B) BILL FORM -->
<div id="bfWrap" class="hide">
<!-- (B1) TITLE -->
<h2 id="bfTitle">ADD BILL</h2>
<!-- (B2) BILLING INFO -->
<h4>BILLING INFORMATION</h4>
<form id="bfForm" class="section">
<input type="hidden" id="bfID">
<label>Bill To</label>
<textarea id="bfBill" required></textarea>
<label>Ship To</label>
<textarea id="bfShip" required></textarea>
<label>Notes (if any)</label>
<textarea id="bfNotes"></textarea>
<label>DOP</label>
<input type="date" id="bfDOP" required>
<label>Due Date</label>
<input type="date" id="bfDue" required>
</form>
<!-- (B2) ITEMS -->
<h4>ITEMS</h4>
<div id="bfiWrap" class="section">
<div id="bfiList"></div>
<form id="bfiForm" onsubmit="return bill.aRow(1)">
<input type="text" id="bfiName" placeholder="Name" required>
<input type="text" id="bfiDesc" placeholder="Desc">
<input type="number" id="bfiQty" placeholder="Qty" min="0" step="0.1" required>
<input type="number" id="bfiEach" placeholder="Price Each" min="0" step="0.01" required>
<input type="submit" id="bfiAdd" value="Add">
</form>
</div>
<!-- (B3) TOTALS -->
<h4>TOTALS</h4>
<div id="bftWrap" class="section">
<div class="bRow flex">
<div class="bfInfo bftTitle">Sub Total</div>
<div class="bftAmt">$<span id="bftSub">0.00</span></div>
</div>
<div id="bftList"></div>
<div class="bRow flex">
<div class="bfInfo bftTitle">Grand Total</div>
<div class="bftAmt">$<span id="bftGrand">0.00</span></div>
</div>
<form id="bftForm" class="flex" onsubmit="return bill.aRow(2)">
<input type="text" id="bftName" placeholder="Name" required>
<input type="number" id="bftAmt" placeholder="Amount (negative for discounts)" step="0.01" required>
<input type="submit" id="bfiAdd" value="Add">
</form>
</div>
<input type="button" value="Back" onclick="bill.toggle(false)">
<input type="button" value="Save" onclick="bill.save()">
</div>
</body>
</html>
var bill = {
// (A) PROPERTIES
// (A1) HTML BILLS LIST
blWrap : null, // bills list wrapper
blList : null, // bills list
// (A2) HTML BILLING FORM
bfWrap : null, // form wrapper
bfTitle : null, // form title
bfForm : null, // billing information form
bfiForm : null, // add item form
bftForm : null, // add total form
bfiList : null, // items list
bftList : null, // totals list
bftSub : null, // sub total
bftGrand : null, // grand total
// (B) HELPER - AJAX FETCH
ajax : (data, onload) => {
// (B1) FORM DATA
let form = new FormData();
for (let [k,v] of Object.entries(data)) { form.append(k, v); }
// (B2) AJAX FETCH
fetch("4-ajax.php", { method:"post", body:form })
.then(res => res.text())
.then(txt => onload(txt))
.catch(err => console.error(err));
},
// (C) INIT
init : () => {
// (C1) GET HTML ELEMENTS
bill.blWrap = document.getElementById("blWrap");
bill.blList = document.getElementById("blList");
bill.bfWrap = document.getElementById("bfWrap");
bill.bfTitle = document.getElementById("bfTitle");
bill.bfForm = document.getElementById("bfForm");
bill.bfiForm = document.getElementById("bfiForm");
bill.bftForm = document.getElementById("bftForm");
bill.bfiList = document.getElementById("bfiList");
bill.bftList = document.getElementById("bftList");
bill.bftSub = document.getElementById("bftSub");
bill.bftGrand = document.getElementById("bftGrand");
// (C2) LOAD BILLS LIST
bill.list();
},
// (D) LIST BILLS
list : () => bill.ajax({ req:"getAll" }, data => {
// (D1) INIT DATA
data = JSON.parse(data);
// (D2) DRAW LIST
bill.blList.innerHTML = "";
if (data.length!=0) { for (let b of data) {
let row = document.createElement("div");
row.className = "bRow flex";
row.innerHTML = `<div class="blInfo">
<div>${b["bill_to"]}</div>
<div>${b["bill_ship"]}</div>
</div>
<div class="blBtn ico" onclick="bill.del(${b["bill_id"]})">&#10006;</div>
<div class="blBtn ico" onclick="bill.toggle(${b["bill_id"]})">&#9998;</div>
<a class="blBtn ico" target="_blank" href="3-print.php?id=${b["bill_id"]}">&#10151;</a>`;
bill.blList.appendChild(row);
}} else { bill.blList.innerHTML = "<div class='bRow flex'>No bills found.</div>"; }
}),
// (E) DELETE BILL
del : id => { if (confirm("Delete bill?")) {
bill.ajax({ req:"del", id:id }, res => {
if (res=="OK") { bill.list(); }
else { alert(res); }
});
}},
// (F) TOGGLE BILLS LIST & FORM
toggle : id => {
// (F1) SWITCH TO BILLS LIST
if (id===false) {
bill.bfWrap.classList.add("hide");
bill.blWrap.classList.remove("hide");
} else {
// (F2) RESET & SWITCH TO BILL FORM
bill.bfTitle.innerHTML = id==0 ? "NEW BILL" : "EDIT BILL";
bill.bfForm.reset();
bill.bfiForm.reset();
bill.bftForm.reset();
bill.bfiList.innerHTML = "";
bill.bftList.innerHTML = "";
bill.bftSub.innerHTML = "0.00";
bill.bftGrand.innerHTML = "0.00";
document.getElementById("bfID").value = (id!=0 ? id : "");
bill.blWrap.classList.add("hide");
bill.bfWrap.classList.remove("hide");
// (F3) EDIT - GET & SET BILL FORM
if (id!=0) {
bill.ajax({ req:"get", id:id }, data => {
// (F3-1) SET FORM FIELDS
data = JSON.parse(data);
document.getElementById("bfBill").value = data["bill_to"];
document.getElementById("bfShip").value = data["bill_ship"];
document.getElementById("bfNotes").value = data["bill_notes"];
document.getElementById("bfDOP").value = data["bill_dop"];
document.getElementById("bfDue").value = data["bill_due"];
// (F3-2) ITEMS & TOTALS
for (let i of data.items) { bill.hRow(1, i); }
for (let t of data.totals) {
if (t[0]=="Sub Total") { bill.bftSub.innerHTML = t[1]; }
else if (t[0]=="Grand Total") { bill.bftGrand.innerHTML = t[1]; }
else { bill.hRow(2, t); }
}
});
}
}
},
// (G) CREATE AN HTML ITEM/TOTAL ROW
hRow : (type, data) => {
// (G1) NEW HTML ROW
let row = document.createElement("div");
row.className = "bRow flex";
row.dataset.type = type;
// (G2) ITEM ROW
if (type==1) {
row.innerHTML = `<div class="bDel ico" onclick="bill.dRow(this);">&#10006;</div>
<div class="bfInfo">
<div class="bfiName">${data[0]}</div>
<div class="bfiDesc">${data[1]==null?"":data[1]}</div>
<div>
<span class="bfiQty">${data[2]}</span> X $
<span class="bfiEach">${data[3]}</span> =
$<span class="bfiAmt">${data[4]}</span>
</div>
</div>`;
}
// (G3) TOTAL ROW
else {
row.innerHTML = `<div class="bDel ico" onclick="bill.dRow(this);">&#10006;</div>
<div class="bfInfo bftTitle">${data[0]}</div>
<div class="bftAmt">$<i>${data[1]}</i></div>`;
}
// (G4) DRAGGABLE
row.draggable = true;
row.ondragstart = () => bill.dragged = row;
row.ondragover = evt => evt.preventDefault();
row.ondrop = evt => bill.sort(evt);
// (G5) ATTACH TO LIST
if (type==1) { bill.bfiList.appendChild(row); }
else { bill.bftList.appendChild(row); }
},
// (H) ADD ITEM/TOTAL ROW
aRow : type => {
// (H1) ITEM ROW
if (type==1) {
bill.hRow(1, [
document.getElementById("bfiName").value,
document.getElementById("bfiDesc").value,
document.getElementById("bfiQty").value,
document.getElementById("bfiEach").value,
(parseFloat(document.getElementById("bfiQty").value) * parseFloat(document.getElementById("bfiEach").value)).toFixed(2)
]);
bill.bfiForm.reset();
}
// (H2) TOTAL ROW
else {
bill.hRow(2, [
document.getElementById("bftName").value,
document.getElementById("bftAmt").value
]);
bill.bftForm.reset();
}
// (H3) RECALCULATE & DONE
bill.recalc();
return false;
},
// (I) DELETE ROW
dRow : row => {
row.parentElement.remove();
bill.recalc();
},
// (J) RECALCULATE TOTALS
recalc : () => {
// (J1) SUB TOTAL - ITEMS
let total = 0;
for (let i of document.querySelectorAll("#bfiList .bfiAmt")) {
total += parseFloat(i.innerHTML);
}
bill.bftSub.innerHTML = total.toFixed(2);
// (J2) GRAND TOTAL - ADD/MINUS USER-DEFINED
for (let i of document.querySelectorAll("#bftList .bftAmt i")) {
total += parseFloat(i.innerHTML);
}
bill.bftGrand.innerHTML = total.toFixed(2);
},
// (K) DRAG-DROP SORT
dragged : null, // current dragged row
sort : evt => {
// (K1) PREVENT DEFAULT DROP
evt.preventDefault();
// (K2) GET PROPER DROPPED TARGET
let target = evt.target;
while (target.dataset.type === undefined) {
target = target.parentElement;
}
// (K3) CHECK VALID DROP TARGET
if (bill.dragged == target) { return false; }
if (bill.dragged.dataset.type != target.dataset.type) { return false; }
// (K4) GET CURRENT AND DROPPED POSITION
let all = bill.dragged.dataset.type==1
? document.querySelectorAll("#bfiList .bRow")
: document.querySelectorAll("#bftList .bRow") ,
currentpos = 0, droppedpos = 0;
for (let i=0; i<all.length; i++) {
if (bill.dragged == all[i]) { currentpos = i; }
if (target == all[i]) { droppedpos = i; }
}
// (K5) REARRANGE
if (currentpos < droppedpos) {
target.parentNode.insertBefore(bill.dragged, target.nextSibling);
} else {
target.parentNode.insertBefore(bill.dragged, target);
}
},
// (L) SAVE
save : () => {
// (L1) FORM CHECK
if (!bill.bfForm.checkValidity()) {
bill.bfForm.reportValidity();
return false;
}
let dop = document.getElementById("bfDOP").value,
due = document.getElementById("bfDue").value;
if (new Date(due) < new Date(dop)) {
alert("Due date cannot be before date of purchase");
return false;
}
// (L2) COLLECT ITEMS + CHECK
let data = { items : [], totals : [] },
all = document.querySelectorAll("#bfiList .bRow");
if (all.length == 0) {
alert("There are no items!");
return false;
}
for (let i of all) {
data["items"].push({
n : i.querySelector(".bfiName").innerHTML,
d : i.querySelector(".bfiDesc").innerHTML,
q : i.querySelector(".bfiQty").innerHTML,
e : i.querySelector(".bfiEach").innerHTML,
a : i.querySelector(".bfiAmt").innerHTML
});
}
data["items"] = JSON.stringify(data["items"]);
// (L3) TOTALS
data.totals.push({ n : "Sub Total", a : bill.bftSub.innerHTML });
all = document.querySelectorAll("#bftList .bRow");
if (all.length>0) { for (let i of all) {
data.totals.push({
n : i.querySelector(".bftTitle").innerHTML,
a : i.querySelector(".bftAmt i").innerHTML
});
}}
data.totals.push({ n : "Grand Total", a : bill.bftGrand.innerHTML });
data["totals"] = JSON.stringify(data["totals"]);
// (L4) BILLING INFORMATION
data["req"] = "save";
data["id"] = document.getElementById("bfID").value;
data["to"] = document.getElementById("bfBill").value;
data["ship"] = document.getElementById("bfShip").value;
data["notes"] = document.getElementById("bfNotes").value;
data["notes"] = document.getElementById("bfNotes").value;
data["dop"] = document.getElementById("bfDOP").value;
data["due"] = document.getElementById("bfDue").value;
if (data["id"]=="") { delete data["id"]; }
// (L5) GO
bill.ajax(data, res => {
if (res=="OK") {
bill.toggle(false);
bill.list();
} else { alert(res); }
});
}
};
window.onload = bill.init;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment