Browse Source

Add X report (closes #2)

master
Skylar Ittner 1 year ago
parent
commit
2dd2374b12
10 changed files with 298 additions and 103 deletions
  1. 24
    96
      action.php
  2. 5
    1
      lang/en_us.php
  3. 192
    0
      lib/generatereceipt.php
  4. 14
    0
      lib/receipts.php
  5. 11
    0
      nbproject/customs.json
  6. 1
    0
      pages.php
  7. 29
    4
      pages/pos.php
  8. 6
    1
      static/css/pos.css
  9. 1
    1
      static/js/pos_finish.js
  10. 15
    0
      static/js/pos_management.js

+ 24
- 96
action.php View File

@@ -144,114 +144,23 @@ switch ($VARS['action']) {

break;
case "getreceipt":
require_once __DIR__ . "/lib/receipts.php";
require_once __DIR__ . "/lib/generatereceipt.php";
$format = "html";
$width = 64;
if (isset($VARS['width']) && preg_match("/[0-9]+/", $VARS['width']) && (int) $VARS['width'] > 0) {
$width = (int) $VARS['width'];
}
if (isset($VARS['format'])) {
switch ($VARS['format']) {
case "text":
$format = "text";
header("Content-Type: text/plain");
break;
case "json":
$format = "json";
header("Content-Type: application/json");
break;
default:
$format = "html";
header("Content-Type: text/html");
}
$format = $VARS['format'];
}
if (!$database->has('transactions', ['txid' => $VARS['txid']])) {
header("Content-Type: application/json");
exit(json_encode(["status" => "ERROR", "txid" => null]));
}
$receipt = new Receipt();
$tx = $database->get('transactions', ['txid', 'txdate', 'customerid', 'type', 'cashier', 'discountpercent'], ['txid' => $VARS['txid']]);
// Info
$txid = $tx['txid'];
$datetime = date(DATETIME_FORMAT, strtotime($tx['txdate']));
$type = $tx['type'];
$cashier = getUserByID($tx['cashier'])['name'];
$customerid = $tx['customerid'];

// Items
$itemlines = [];
$items = $database->select('lines', ['amount', 'name', 'itemid', 'qty'], ['txid' => $txid]);
$subtotal = 0.0;
$paid = 0.0;
foreach ($items as $i) {
$itemlines[] = new ReceiptLine(
$i['name'], (float) $i['qty'] . '@' . number_format($i['amount'], 2), '$' . number_format($i['qty'] * $i['amount'] * 1.0, 2)
);
$subtotal += $i['qty'] * $i['amount'] * 1.0;
}

// Payments
$total = $subtotal * (1.0 - ((float) $tx['discountpercent'] / 100));
$paymentlines = [];
$payments = $database->select('payments', [
'[>]payment_types' => ['type' => 'typeid']
], [
'amount', 'type', 'typename', 'text'
], [
'txid' => $txid
]);
foreach ($payments as $p) {
if ($p['amount'] < 0) {
continue;
}
$paymentlines[] = new ReceiptLine(lang($p['text'], false), "", '$' . number_format($p['amount'] * 1.0, 2));
$paid += $p['amount'] * 1.0;
}
$change = $paid - $total;
if ($change <= 0) {
$change = 0.0;
}

// Totals
$subtotalline = new ReceiptLine("Subtotal:", "", '$' . number_format($subtotal, 2));
$paidline = new ReceiptLine("Paid:", "", '$' . number_format($paid, 2), ReceiptLine::LINEFORMAT_BOLD);
$changeline = new ReceiptLine("Change:", "", '$' . number_format($change, 2), ReceiptLine::LINEFORMAT_BOLD);
$totalline = new ReceiptLine("Total:", "", '$' . number_format($subtotal, 2), ReceiptLine::LINEFORMAT_BOLD);

$receipt->appendLine(new ReceiptLine("Date: $datetime"));
$receipt->appendLine(new ReceiptLine("Tx. ID: $txid"));
$receipt->appendLine(new ReceiptLine("Cashier: $cashier"));
if (!is_null($customerid) && !empty($customerid)) {
$customer = $database->get('customers', 'name', ['customerid' => $customerid]);
$receipt->appendLine(new ReceiptLine("Customer: $customer"));
}
$receipt->appendBreak();
$receipt->appendLines($itemlines);
$receipt->appendBreak();
$receipt->appendLine($subtotalline);
if ($tx['discountpercent'] > 0) {
$receipt->appendLine(new ReceiptLine("Discount:", "", (float) $tx['discountpercent'] . '% off'));
$totalline = new ReceiptLine("Total:", "", '$' . number_format($total, 2), ReceiptLine::LINEFORMAT_BOLD);
}
$receipt->appendLine($totalline);
$receipt->appendBreak();
$receipt->appendLines($paymentlines);
$receipt->appendBreak();
$receipt->appendLine($paidline);
$receipt->appendLine($changeline);

$output = "";
switch ($format) {
case "text":
$output = $receipt->getPlainText($width);
break;
case "json":
$output = $receipt->getJson($width);
break;
default:
$output = $receipt->getHtml("Tx. #$txid");
}
exit($output);
$receipt = GenerateReceipt::getReceipt(GenerateReceipt::RECEIPT_TYPE_TRANSACTION, $VARS['txid']);

exit(GenerateReceipt::outputReceipt($receipt, $format, $width, "Tx. #" . $VARS['txid']));
break;
case "itemsearch":
header("Content-Type: application/json");
@@ -509,6 +418,25 @@ switch ($VARS['action']) {
}

returnToSender("register_saved");
case "xreport":
require_once __DIR__ . "/lib/generatereceipt.php";
$format = "html";
$width = 64;
if (isset($VARS['width']) && preg_match("/[0-9]+/", $VARS['width']) && (int) $VARS['width'] > 0) {
$width = (int) $VARS['width'];
}
if (isset($VARS['format'])) {
$format = $VARS['format'];
}
if (!$database->has('cash_drawer', ['AND' => ['registerid' => $VARS['register'], 'open[!]' => null, 'close' => null]])) {
header("Content-Type: application/json");
exit(json_encode(["status" => "ERROR"]));
}

$receipt = GenerateReceipt::getReceipt(GenerateReceipt::RECEIPT_TYPE_X, null, $VARS['register']);

exit(GenerateReceipt::outputReceipt($receipt, $format, $width, "X Report"));
break;
case "session_keepalive":
header("Content-Type: application/json");
exit(json_encode(["status" => "OK"]));

+ 5
- 1
lang/en_us.php View File

@@ -111,5 +111,9 @@ define("STRINGS", [
"adding register" => "Adding register",
"register saved" => "Register saved.",
"register name taken" => "Register name already taken. Use a different name.",
"no open registers" => "No open cash registers. Go to the Registers page to open one."
"no open registers" => "No open cash registers. Go to the Registers page to open one.",
"register management" => "Register Management",
"manage register" => "Manage register",
"manage" => "Manage",
"x report" => "X Report",
]);

+ 192
- 0
lib/generatereceipt.php View File

@@ -0,0 +1,192 @@
<?php

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

require_once __DIR__ . "/receipts.php";

class GenerateReceipt {

const RECEIPT_TYPE_TRANSACTION = 1;
const RECEIPT_TYPE_X = 2;
const RECEIPT_TYPE_Y = 3;

static function transactionReceipt($transaction) {
global $database;
$receipt = new Receipt();
$tx = $database->get('transactions', ['txid', 'txdate', 'customerid', 'type', 'cashier', 'discountpercent'], ['txid' => $transaction]);
// Info
$txid = $tx['txid'];
$datetime = date(DATETIME_FORMAT, strtotime($tx['txdate']));
$type = $tx['type'];
$cashier = getUserByID($tx['cashier'])['name'];
$customerid = $tx['customerid'];

// Items
$itemlines = [];
$items = $database->select('lines', ['amount', 'name', 'itemid', 'qty'], ['txid' => $txid]);
$subtotal = 0.0;
$paid = 0.0;
foreach ($items as $i) {
$itemlines[] = new ReceiptLine(
$i['name'], (float) $i['qty'] . '@' . number_format($i['amount'], 2), '$' . number_format($i['qty'] * $i['amount'] * 1.0, 2)
);
$subtotal += $i['qty'] * $i['amount'] * 1.0;
}

// Payments
$total = $subtotal * (1.0 - ((float) $tx['discountpercent'] / 100));
$paymentlines = [];
$payments = $database->select('payments', [
'[>]payment_types' => ['type' => 'typeid']
], [
'amount', 'type', 'typename', 'text'
], [
'txid' => $txid
]);
foreach ($payments as $p) {
if ($p['amount'] < 0) {
continue;
}
$paymentlines[] = new ReceiptLine(lang($p['text'], false), "", '$' . number_format($p['amount'] * 1.0, 2));
$paid += $p['amount'] * 1.0;
}
$change = $paid - $total;
if ($change <= 0) {
$change = 0.0;
}

// Totals
$subtotalline = new ReceiptLine("Subtotal:", "", '$' . number_format($subtotal, 2));
$paidline = new ReceiptLine("Paid:", "", '$' . number_format($paid, 2), ReceiptLine::LINEFORMAT_BOLD);
$changeline = new ReceiptLine("Change:", "", '$' . number_format($change, 2), ReceiptLine::LINEFORMAT_BOLD);
$totalline = new ReceiptLine("Total:", "", '$' . number_format($subtotal, 2), ReceiptLine::LINEFORMAT_BOLD);

$receipt->appendLine(new ReceiptLine("Date: $datetime"));
$receipt->appendLine(new ReceiptLine("Tx. ID: $txid"));
$receipt->appendLine(new ReceiptLine("Cashier: $cashier"));
if (!is_null($customerid) && !empty($customerid)) {
$customer = $database->get('customers', 'name', ['customerid' => $customerid]);
$receipt->appendLine(new ReceiptLine("Customer: $customer"));
}
$receipt->appendBreak();
$receipt->appendLines($itemlines);
$receipt->appendBreak();
$receipt->appendLine($subtotalline);
if ($tx['discountpercent'] > 0) {
$receipt->appendLine(new ReceiptLine("Discount:", "", (float) $tx['discountpercent'] . '% off'));
$totalline = new ReceiptLine("Total:", "", '$' . number_format($total, 2), ReceiptLine::LINEFORMAT_BOLD);
}
$receipt->appendLine($totalline);
$receipt->appendBreak();
$receipt->appendLines($paymentlines);
$receipt->appendBreak();
$receipt->appendLine($paidline);
$receipt->appendLine($changeline);

return $receipt;
}

static function xReceipt($registerid) {
global $database;
$receipt = new Receipt();

$registername = $database->get('registers', 'registername', ['registerid' => $registerid]);
$cash = $database->get('cash_drawer', ['open', 'start_amount', 'cashid'], ['AND' => ['open[!]' => null, 'close' => null, 'registerid' => $registerid]]);

$balance = [];
$paymenttypes = $database->select('payment_types', ['typename (type)', 'text']);
$payments = $database->select("payments", [
"[>]transactions" => ['txid' => 'txid'],
"[>]payment_types" => ['type' => 'typeid']
], ['amount', 'typename (type)'], [
'AND' => [
'transactions.cashid' => $cash['cashid'],
]
]);
foreach ($paymenttypes as $t) {
$balance[$t['type']] = 0.0;
}
foreach ($payments as $p) {
$balance[$p['type']] += $p['amount'];
}

$receipt->appendHeader(new ReceiptLine(lang("x report", false), "", "", ReceiptLine::LINEFORMAT_BOLD | ReceiptLine::LINEFORMAT_CENTER));

$receipt->appendLine(new ReceiptLine("Date:", "", date(DATETIME_FORMAT)));
$receipt->appendLine(new ReceiptLine("Register:", "", $registername));

$receipt->appendBlank();
$receipt->appendBreak();
$receipt->appendLine(new ReceiptLine("Opening", "", "", ReceiptLine::LINEFORMAT_CENTER));
$receipt->appendBreak();
$receipt->appendLine(new ReceiptLine("Date:", "", date(DATETIME_FORMAT, strtotime($cash['open']))));
$receipt->appendLine(new ReceiptLine("Cash:", "", '$' . number_format($cash['start_amount'], 2)));

$receipt->appendBlank();
$receipt->appendBreak();
$receipt->appendLine(new ReceiptLine("Sales", "", "", ReceiptLine::LINEFORMAT_CENTER));
$receipt->appendBreak();
foreach ($paymenttypes as $t) {
$receipt->appendLine(new ReceiptLine(lang($t['text'], false) . ":", "", '$' . number_format($balance[$t['type']], 2)));
}

$receipt->appendBlank();
$receipt->appendBreak();
$receipt->appendLine(new ReceiptLine("Balance", "", "", ReceiptLine::LINEFORMAT_CENTER));
$receipt->appendBreak();
$receipt->appendLine(new ReceiptLine("Cash:", "", '$' . number_format($balance['cash'] + $cash['start_amount'], 2)));

return $receipt;
}

static function getReceipt($type, $transaction = null, $register = null, $cashid = null) {
switch ($type) {
case GenerateReceipt::RECEIPT_TYPE_X:
return GenerateReceipt::xReceipt($register);
break;
case GenerateReceipt::RECEIPT_TYPE_Y:
return GenerateReceipt::Receipt($register, $cashid);
break;
case GenerateReceipt::RECEIPT_TYPE_TRANSACTION:
return GenerateReceipt::transactionReceipt($transaction);
break;
default:
return new Receipt();
break;
}
}

static function outputReceipt(Receipt $receipt, $format = "html", $width = 64, $title = "") {
switch ($format) {
case "text":
$format = "text";
header("Content-Type: text/plain");
break;
case "json":
$format = "json";
header("Content-Type: application/json");
break;
default:
$format = "html";
header("Content-Type: text/html");
}

$output = "";
switch ($format) {
case "text":
$output = $receipt->getPlainText($width);
break;
case "json":
$output = $receipt->getJson($width);
break;
default:
$output = $receipt->getHtml($title);
}
return $output;
}

}

+ 14
- 0
lib/receipts.php View File

@@ -15,6 +15,7 @@ class ReceiptLine {
const LINEFORMAT_BOLD = 2;
const LINEFORMAT_HR = 4;
const LINEFORMAT_CENTER = 8;
const LINEFORMAT_BLANK = 16;

private $left = "";
private $middle = "";
@@ -48,6 +49,9 @@ class ReceiptLine {
if ($this->hasFormat($this::LINEFORMAT_HR)) {
return "<hr />";
}
if ($this->hasFormat($this::LINEFORMAT_BLANK)) {
return "<br />";
}
$html = "";
if (!empty($this->left)) {
$html .= '<span>' . htmlspecialchars($this->left) . '&nbsp;</span>';
@@ -73,6 +77,9 @@ class ReceiptLine {
if ($this->hasFormat($this::LINEFORMAT_HR)) {
return str_repeat("-", $width);
}
if ($this->hasFormat($this::LINEFORMAT_BLANK)) {
return str_repeat(" ", $width);
}
$left = $this->left;
$middle = $this->middle;
$right = $this->right;
@@ -121,6 +128,9 @@ class ReceiptLine {
if ($this->hasFormat($this::LINEFORMAT_CENTER)) {
$data['format'][] = "center";
}
if ($this->hasFormat($this::LINEFORMAT_BLANK)) {
$data['format'][] = "blank";
}
return $data;
}

@@ -158,6 +168,10 @@ class Receipt {
$this->lines[] = new ReceiptLine("", "", "", ReceiptLine::LINEFORMAT_HR);
}

function appendBlank() {
$this->lines[] = new ReceiptLine("", "", "", ReceiptLine::LINEFORMAT_BLANK);
}

function getHtml($title = "") {
global $SECURE_NONCE;
$html = <<<END

+ 11
- 0
nbproject/customs.json View File

@@ -0,0 +1,11 @@
{
"elements": {},
"attributes": {
"role": {
"context": "div"
},
"data-dismiss": {
"context": "button"
}
}
}

+ 1
- 0
pages.php View File

@@ -26,6 +26,7 @@ define("PAGES", [
"static/js/pos_gridview.js",
"static/js/pos_payment.js",
"static/js/pos_finish.js",
"static/js/pos_management.js",
"static/js/pos.js",
]
],

+ 29
- 4
pages/pos.php View File

@@ -8,8 +8,8 @@ $register = [
"name" => lang("no cash", false),
"id" => ""
];
if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$database->has("registers", ['[>]cash_drawer' => ['registerid' => 'registerid']], ['AND' => ['open[!]' => null, 'close' => null, 'registerid' => $_SESSION['register']]])) {
$registeropen = $database->has("registers", ['[>]cash_drawer' => ['registerid' => 'registerid']], ['AND' => ['open[!]' => null, 'close' => null, 'registers.registerid' => $_SESSION['register']]]);
if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$registeropen) {
require_once __DIR__ . "/../lib/chooseregister.php";
} else {
$register = $database->get('registers', ['registerid (id)', 'registername (name)'], ['registerid' => $_SESSION['register']]);
@@ -26,7 +26,7 @@ if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$database->has("
</div>
<div class="modal-body">
<div class="display-4 text-center"><?php lang("change"); ?>: $<span id="receiptchange">0.00</span></div>
<iframe class="w-100" id="receiptframe"></iframe>
<iframe class="w-100 shadow-lg" id="receiptframe"></iframe>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal"><?php lang("new sale"); ?></button>
@@ -62,6 +62,30 @@ if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$database->has("
</div>
</div>

<div class="modal fade" tabindex="-1" role="dialog" id="managermodal">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-cog"></i> <?php lang("register management"); ?></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-12 col-md-6">
<button type="button" class="btn btn-primary" id="xprintbtn"><i class="fas fa-print"></i> <?php lang("print"); ?></button>
<iframe class="w-100 shadow-lg" id="xframe" src="action.php?action=xreport&format=html&register=<?php echo $register['id']; ?>"></iframe>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal"><?php lang("close"); ?></button>
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-12 col-md-6 order-1 order-md-0">
<div class="card d-flex">
@@ -98,7 +122,8 @@ if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$database->has("

<div class="col-12 col-md-6 order-0 order-md-1">
<div class="card mb-3 mb-md-0">
<div class="w-100 position-absolute d-flex align-items-start pr-3 pt-2">
<div class="w-100 position-absolute d-flex align-items-start px-3 pt-2">
<a href="#" class="mr-auto text-body" id="openmanagement" data-toggle="tooltip" title="<?php lang("manage register") ?>"><i class="fas fa-cog"></i> <?php lang("manage"); ?></a>
<a href="app.php?page=pos&switch" class="ml-auto text-body" id="register" data-id="<?php echo $register['id']; ?>" data-toggle="tooltip" title="<?php lang("change register") ?>"><i class="fas fa-exchange-alt"></i> <?php echo $register['name']; ?></a>
</div>
<div class="display-4 p-1 p-md-3 text-center">$<span id="grand-total">0.00</span></div>

+ 6
- 1
static/css/pos.css View File

@@ -27,6 +27,11 @@ input[type="number"] {
}

#receiptframe {
height: 50vh;
height: 60vh;
border: 0;
}

#xframe {
height: 60vh;
border: 0;
}

+ 1
- 1
static/js/pos_finish.js View File

@@ -80,7 +80,7 @@ $("#finishbtn").click(function () {

$("#receiptprintbtn").click(function () {
document.getElementById("receiptframe").contentWindow.print();
})
});

$("#receiptmodal").on("hide.bs.modal", function () {
window.location.reload();

+ 15
- 0
static/js/pos_management.js View File

@@ -0,0 +1,15 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/


$("#openmanagement").click(function () {
document.getElementById("xframe").contentDocument.location.reload(true);
$("#managermodal").modal();
});

$("#xprintbtn").click(function () {
document.getElementById("xframe").contentWindow.print();
});

Loading…
Cancel
Save