mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-04-02 14:24:27 -04:00
The clear_all() calls in postComplete() were placed after return statements, making them unreachable dead code. This caused the completed sale to remain in the session and appear in the Register when navigating back. The fix moves clear_all() and clear_mode() calls before the return statements so they are actually executed, properly clearing the sale cart, customer, and payments from the session after sale completion. This fixes the regression reported by @odiea where users had to manually cancel sales after each transaction.
1733 lines
73 KiB
PHP
1733 lines
73 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers;
|
|
|
|
use App\Libraries\Barcode_lib;
|
|
use App\Libraries\Email_lib;
|
|
use App\Libraries\Sale_lib;
|
|
use App\Libraries\Tax_lib;
|
|
use App\Libraries\Token_lib;
|
|
use App\Models\Customer;
|
|
use App\Models\Customer_rewards;
|
|
use App\Models\Dinner_table;
|
|
use App\Models\Employee;
|
|
use App\Models\Giftcard;
|
|
use App\Models\Inventory;
|
|
use App\Models\Item;
|
|
use App\Models\Item_kit;
|
|
use App\Models\Sale;
|
|
use App\Models\Stock_location;
|
|
use App\Models\Tokens\Token_invoice_count;
|
|
use App\Models\Tokens\Token_customer;
|
|
use App\Models\Tokens\Token_invoice_sequence;
|
|
use CodeIgniter\HTTP\ResponseInterface;
|
|
use Config\Services;
|
|
use Config\OSPOS;
|
|
use ReflectionException;
|
|
use stdClass;
|
|
|
|
class Sales extends Secure_Controller
|
|
{
|
|
protected $helpers = ['file'];
|
|
private Barcode_lib $barcode_lib;
|
|
private Email_lib $email_lib;
|
|
private Sale_lib $sale_lib;
|
|
private Tax_lib $tax_lib;
|
|
private Token_lib $token_lib;
|
|
private Customer $customer;
|
|
private Customer_rewards $customer_rewards;
|
|
private Dinner_table $dinner_table;
|
|
protected Employee $employee;
|
|
private Item $item;
|
|
private Item_kit $item_kit;
|
|
private Sale $sale;
|
|
private Stock_location $stock_location;
|
|
private array $config;
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct('sales');
|
|
|
|
$this->session = session();
|
|
$this->barcode_lib = new Barcode_lib();
|
|
$this->email_lib = new Email_lib();
|
|
$this->sale_lib = new Sale_lib();
|
|
$this->tax_lib = new Tax_lib();
|
|
$this->token_lib = new Token_lib();
|
|
$this->config = config(OSPOS::class)->settings;
|
|
|
|
$this->customer = model(Customer::class);
|
|
$this->sale = model(Sale::class);
|
|
$this->item = model(Item::class);
|
|
$this->item_kit = model(Item_kit::class);
|
|
$this->stock_location = model(Stock_location::class);
|
|
$this->customer_rewards = model(Customer_rewards::class);
|
|
$this->dinner_table = model(Dinner_table::class);
|
|
$this->employee = model(Employee::class);
|
|
}
|
|
|
|
public function getIndex(): ResponseInterface|string
|
|
{
|
|
$this->session->set('allow_temp_items', 1);
|
|
return $this->_reload(); // TODO: Hungarian Notation
|
|
}
|
|
|
|
/**
|
|
* Load the sale edit modal. Used in app/Views/sales/register.php.
|
|
*
|
|
* @return ResponseInterface|string
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getManage(): ResponseInterface|string
|
|
{
|
|
$personId = $this->session->get('person_id');
|
|
|
|
if (!$this->employee->has_grant('reports_sales', $personId)) {
|
|
return redirect()->to('no_access/sales/reports_sales');
|
|
} else {
|
|
$data['table_headers'] = get_sales_manage_table_headers();
|
|
|
|
$data['filters'] = [
|
|
'only_cash' => lang('Sales.cash_filter'),
|
|
'only_due' => lang('Sales.due_filter'),
|
|
'only_check' => lang('Sales.check_filter'),
|
|
'only_creditcard' => lang('Sales.credit_filter'),
|
|
'only_debit' => lang('Sales.debit'),
|
|
'only_invoices' => lang('Sales.invoice_filter'),
|
|
'selected_customer' => lang('Sales.selected_customer')
|
|
];
|
|
|
|
if ($this->sale_lib->get_customer() != -1) {
|
|
$selectedFilters = ['selected_customer'];
|
|
$data['customer_selected'] = true;
|
|
} else {
|
|
$data['customer_selected'] = false;
|
|
$selectedFilters = [];
|
|
}
|
|
|
|
// Restore filters from URL query string
|
|
$filters = restoreTableFilters($this->request);
|
|
if (!empty($filters['selected_filters'])) {
|
|
$selectedFilters = array_merge($selectedFilters, $filters['selected_filters']);
|
|
}
|
|
if (isset($filters['start_date'])) {
|
|
$data['start_date'] = $filters['start_date'];
|
|
}
|
|
if (isset($filters['end_date'])) {
|
|
$data['end_date'] = $filters['end_date'];
|
|
}
|
|
$data['selected_filters'] = $selectedFilters;
|
|
|
|
return view('sales/manage', $data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param int $row_id
|
|
* @return ResponseInterface
|
|
*/
|
|
public function getRow(int $row_id): ResponseInterface
|
|
{
|
|
$sale_info = $this->sale->get_info($row_id)->getRow();
|
|
$data_row = get_sale_data_row($sale_info);
|
|
|
|
return $this->response->setJSON($data_row);
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function getSearch(): ResponseInterface
|
|
{
|
|
$search = $this->request->getGet('search', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
|
|
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
|
|
$sort = $this->sanitizeSortColumn(sales_headers(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'sale_id');
|
|
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
|
|
$filters = [
|
|
'sale_type' => 'all',
|
|
'location_id' => 'all',
|
|
'start_date' => $this->request->getGet('start_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
'end_date' => $this->request->getGet('end_date', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
'only_cash' => false,
|
|
'only_due' => false,
|
|
'only_check' => false,
|
|
'selected_customer' => false,
|
|
'only_creditcard' => false,
|
|
'only_debit' => false,
|
|
'only_invoices' => $this->config['invoice_enable'] && $this->request->getGet('only_invoices', FILTER_SANITIZE_NUMBER_INT),
|
|
'is_valid_receipt' => $this->sale->is_valid_receipt($search)
|
|
];
|
|
|
|
// Check if any filter is set in the multiselect dropdown
|
|
$request_filters = array_fill_keys($this->request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? [], true);
|
|
$filters = array_merge($filters, $request_filters);
|
|
|
|
$sales = $this->sale->search($search, $filters, $limit, $offset, $sort, $order);
|
|
$total_rows = $this->sale->get_found_rows($search, $filters);
|
|
$payments = $this->sale->get_payments_summary($search, $filters);
|
|
$payment_summary = get_sales_manage_payments_summary($payments);
|
|
|
|
$data_rows = [];
|
|
foreach ($sales->getResult() as $sale) {
|
|
$data_rows[] = get_sale_data_row($sale);
|
|
}
|
|
|
|
if ($total_rows > 0) {
|
|
$data_rows[] = get_sale_data_last_row($sales);
|
|
}
|
|
|
|
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows, 'payment_summary' => $payment_summary]);
|
|
}
|
|
|
|
/**
|
|
* Gets search suggestions for an item or item kit. Used in app/Views/sales/register.php.
|
|
*
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getItemSearch(): ResponseInterface
|
|
{
|
|
$suggestions = [];
|
|
$receipt = $search = $this->request->getGet('term') != ''
|
|
? $this->request->getGet('term')
|
|
: null;
|
|
|
|
if ($this->sale_lib->get_mode() == 'return' && $this->sale->is_valid_receipt($receipt)) {
|
|
// If a valid receipt or invoice was found the search term will be replaced with a receipt number (POS #)
|
|
$suggestions[] = $receipt;
|
|
}
|
|
$suggestions = array_merge($suggestions, $this->item->get_search_suggestions($search, ['search_custom' => false, 'is_deleted' => false], true));
|
|
$suggestions = array_merge($suggestions, $this->item_kit->get_search_suggestions($search));
|
|
|
|
return $this->response->setJSON($suggestions);
|
|
}
|
|
|
|
/**
|
|
* @return ResponseInterface
|
|
*/
|
|
public function suggest_search(): ResponseInterface
|
|
{
|
|
$search = $this->request->getPost('term') != ''
|
|
? $this->request->getPost('term')
|
|
: null;
|
|
|
|
$suggestions = $this->sale->get_search_suggestions($search);
|
|
|
|
return $this->response->setJSON($suggestions);
|
|
}
|
|
|
|
/**
|
|
* Set a given customer. Used in app/Views/sales/register.php.
|
|
*
|
|
* @return ResponseInterface|string
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postSelectCustomer(): ResponseInterface|string
|
|
{
|
|
$customer_id = (int)$this->request->getPost('customer', FILTER_SANITIZE_NUMBER_INT);
|
|
if ($this->customer->exists($customer_id)) {
|
|
$this->sale_lib->set_customer($customer_id);
|
|
$discount = $this->customer->get_info($customer_id)->discount;
|
|
$discount_type = $this->customer->get_info($customer_id)->discount_type;
|
|
|
|
// Apply customer default discount to items that have 0 discount
|
|
if ($discount != '') {
|
|
$this->sale_lib->apply_customer_discount($discount, $discount_type);
|
|
}
|
|
}
|
|
|
|
return $this->_reload();
|
|
}
|
|
|
|
/**
|
|
* Changes the sale mode in the register to carry out different types of sales
|
|
*
|
|
* @return ResponseInterface|string
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postChangeMode(): ResponseInterface|string
|
|
{
|
|
$mode = $this->request->getPost('mode', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
$this->sale_lib->set_mode($mode);
|
|
|
|
if ($mode == 'sale') {
|
|
$this->sale_lib->set_sale_type(SALE_TYPE_POS);
|
|
} elseif ($mode == 'sale_quote') {
|
|
$this->sale_lib->set_sale_type(SALE_TYPE_QUOTE);
|
|
} elseif ($mode == 'sale_work_order') {
|
|
$this->sale_lib->set_sale_type(SALE_TYPE_WORK_ORDER);
|
|
} elseif ($mode == 'sale_invoice') {
|
|
$this->sale_lib->set_sale_type(SALE_TYPE_INVOICE);
|
|
} else {
|
|
$this->sale_lib->set_sale_type(SALE_TYPE_RETURN);
|
|
}
|
|
|
|
if ($this->config['dinner_table_enable']) {
|
|
$occupied_dinner_table = $this->request->getPost('dinner_table', FILTER_SANITIZE_NUMBER_INT);
|
|
$released_dinner_table = $this->sale_lib->get_dinner_table();
|
|
$occupied = $this->dinner_table->is_occupied($released_dinner_table);
|
|
|
|
if ($occupied && ($occupied_dinner_table != $released_dinner_table)) {
|
|
$this->dinner_table->swap_tables($released_dinner_table, $occupied_dinner_table);
|
|
}
|
|
|
|
$this->sale_lib->set_dinner_table($occupied_dinner_table);
|
|
}
|
|
|
|
$stock_location = $this->request->getPost('stock_location', FILTER_SANITIZE_NUMBER_INT);
|
|
|
|
if (!$stock_location || $stock_location == $this->sale_lib->get_sale_location()) {
|
|
// TODO: The code below was removed in 2017 by @steveireland. We either need to reinstate some of it or remove this entire if block but we can't leave an empty if block
|
|
// $dinner_table = $this->request->getPost('dinner_table');
|
|
// $this->sale_lib->set_dinner_table($dinner_table);
|
|
} elseif ($this->stock_location->is_allowed_location($stock_location, 'sales')) {
|
|
$this->sale_lib->set_sale_location($stock_location);
|
|
}
|
|
|
|
$this->sale_lib->empty_payments();
|
|
|
|
return $this->_reload();
|
|
}
|
|
|
|
/**
|
|
* @param int $sale_type
|
|
* @return ResponseInterface|string
|
|
*/
|
|
public function change_register_mode(int $sale_type): ResponseInterface|string
|
|
{
|
|
$mode = match ($sale_type) {
|
|
SALE_TYPE_QUOTE => 'sale_quote',
|
|
SALE_TYPE_WORK_ORDER => 'sale_work_order',
|
|
SALE_TYPE_INVOICE => 'sale_invoice',
|
|
SALE_TYPE_RETURN => 'return',
|
|
default => 'sale' // SALE_TYPE_POS
|
|
};
|
|
|
|
$this->sale_lib->set_mode($mode);
|
|
return $this->_reload();
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the sales comment. Used in app/Views/sales/register.php
|
|
*
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postSetComment(): ResponseInterface
|
|
{
|
|
$this->sale_lib->set_comment($this->request->getPost('comment', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
|
|
return $this->response->setJSON(['success' => true]);
|
|
}
|
|
|
|
/**
|
|
* Sets the invoice number. Used in app/Views/sales/register.php
|
|
*
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postSetInvoiceNumber(): ResponseInterface|string
|
|
{
|
|
$this->sale_lib->set_invoice_number($this->request->getPost('sales_invoice_number', FILTER_SANITIZE_NUMBER_INT));
|
|
return $this->response->setJSON(['success' => true]);
|
|
}
|
|
|
|
/**
|
|
* @return ResponseInterface
|
|
*/
|
|
public function postSetPaymentType(): ResponseInterface|string // TODO: This function does not appear to be called anywhere in the code.
|
|
{
|
|
$this->sale_lib->set_payment_type($this->request->getPost('selected_payment_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
|
|
return $this->_reload(); // TODO: Hungarian notation.
|
|
}
|
|
|
|
/**
|
|
* Sets PrintAfterSale flag. Used in app/Views/sales/register.php
|
|
*
|
|
* @return ResponseInterface|string
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postSetPrintAfterSale(): ResponseInterface
|
|
{
|
|
$this->sale_lib->set_print_after_sale($this->request->getPost('sales_print_after_sale') != 'false');
|
|
return $this->response->setJSON(['success' => true]);
|
|
}
|
|
|
|
/**
|
|
* Sets the flag to include prices in the work order. Used in app/Views/sales/register.php
|
|
*
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postSetPriceWorkOrders(): ResponseInterface
|
|
{
|
|
$price_work_orders = parse_decimals($this->request->getPost('price_work_orders'));
|
|
$this->sale_lib->set_price_work_orders($price_work_orders);
|
|
return $this->response->setJSON(['success' => true]);
|
|
}
|
|
|
|
/**
|
|
* Sets the flag to email receipt to the customer. Used in app/Views/sales/register.php
|
|
*
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postSetEmailReceipt(): ResponseInterface
|
|
{
|
|
$this->sale_lib->set_email_receipt($this->request->getPost('email_receipt', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
|
|
return $this->response->setJSON(['success' => true]);
|
|
}
|
|
|
|
/**
|
|
* Add a payment to the sale. Used in app/Views/sales/register.php
|
|
*
|
|
* @return ResponseInterface|string
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postAddPayment(): ResponseInterface|string
|
|
{
|
|
$data = [];
|
|
$giftcard = model(Giftcard::class);
|
|
$payment_type = $this->request->getPost('payment_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
|
|
if ($payment_type !== lang('Sales.giftcard')) {
|
|
$rules = ['amount_tendered' => 'trim|required|decimal_locale',];
|
|
$messages = ['amount_tendered' => lang('Sales.must_enter_numeric')];
|
|
} else {
|
|
$rules = ['amount_tendered' => 'trim|required',];
|
|
$messages = ['amount_tendered' => lang('Sales.must_enter_numeric_giftcard')];
|
|
}
|
|
|
|
if (!$this->validate($rules, $messages)) {
|
|
$data['error'] = $payment_type === lang('Sales.giftcard')
|
|
? lang('Sales.must_enter_numeric_giftcard')
|
|
: lang('Sales.must_enter_numeric');
|
|
} else {
|
|
if ($payment_type === lang('Sales.giftcard')) {
|
|
// In the case of giftcard payment the register input amount_tendered becomes the giftcard number
|
|
$amount_tendered = parse_decimals($this->request->getPost('amount_tendered'));
|
|
$giftcard_num = $amount_tendered;
|
|
|
|
$payments = $this->sale_lib->get_payments();
|
|
$payment_type = $payment_type . ':' . $giftcard_num;
|
|
$current_payments_with_giftcard = isset($payments[$payment_type]) ? $payments[$payment_type]['payment_amount'] : 0;
|
|
$cur_giftcard_value = $giftcard->get_giftcard_value($giftcard_num);
|
|
$cur_giftcard_customer = $giftcard->get_giftcard_customer($giftcard_num);
|
|
$customer_id = $this->sale_lib->get_customer();
|
|
|
|
if (isset($cur_giftcard_customer) && $cur_giftcard_customer != $customer_id && $cur_giftcard_customer != null) {
|
|
$data['error'] = lang('Giftcards.cannot_use', [$giftcard_num]);
|
|
} elseif (($cur_giftcard_value - $current_payments_with_giftcard) <= 0 && $this->sale_lib->get_mode() === 'sale') {
|
|
$data['error'] = lang('Giftcards.remaining_balance', [$giftcard_num, $cur_giftcard_value]);
|
|
} else {
|
|
$new_giftcard_value = $giftcard->get_giftcard_value($giftcard_num) - $this->sale_lib->get_amount_due();
|
|
$new_giftcard_value = max($new_giftcard_value, 0);
|
|
$this->sale_lib->set_giftcard_remainder($new_giftcard_value);
|
|
$new_giftcard_value = str_replace('$', '\$', to_currency($new_giftcard_value));
|
|
$data['warning'] = lang('Giftcards.remaining_balance', [$giftcard_num, $new_giftcard_value]);
|
|
$amount_tendered = min($this->sale_lib->get_amount_due(), $giftcard->get_giftcard_value($giftcard_num));
|
|
|
|
$this->sale_lib->add_payment($payment_type, $amount_tendered);
|
|
}
|
|
} elseif ($payment_type === lang('Sales.rewards')) {
|
|
$customer_id = $this->sale_lib->get_customer();
|
|
$package_id = $this->customer->get_info($customer_id)->package_id;
|
|
if (!empty($package_id)) {
|
|
$points = $this->customer->get_info($customer_id)->points;
|
|
$points = ($points == null ? 0 : $points);
|
|
|
|
$payments = $this->sale_lib->get_payments();
|
|
$current_payments_with_rewards = isset($payments[$payment_type]) ? $payments[$payment_type]['payment_amount'] : 0;
|
|
$cur_rewards_value = $points;
|
|
|
|
if (($cur_rewards_value - $current_payments_with_rewards) <= 0) {
|
|
$data['error'] = lang('Sales.rewards_remaining_balance') . to_currency($cur_rewards_value);
|
|
} else {
|
|
$new_reward_value = $points - $this->sale_lib->get_amount_due();
|
|
$new_reward_value = max($new_reward_value, 0);
|
|
$this->sale_lib->set_rewards_remainder($new_reward_value);
|
|
$new_reward_value = str_replace('$', '\$', to_currency($new_reward_value));
|
|
$data['warning'] = lang('Sales.rewards_remaining_balance') . $new_reward_value;
|
|
$amount_tendered = min($this->sale_lib->get_amount_due(), $points);
|
|
|
|
$this->sale_lib->add_payment($payment_type, $amount_tendered);
|
|
}
|
|
}
|
|
} elseif ($payment_type === lang('Sales.cash')) {
|
|
$amount_due = $this->sale_lib->get_total();
|
|
$sales_total = $this->sale_lib->get_total(false);
|
|
$amount_tendered = parse_decimals($this->request->getPost('amount_tendered'));
|
|
$this->sale_lib->add_payment($payment_type, $amount_tendered);
|
|
$cash_adjustment_amount = $amount_due - $sales_total;
|
|
if ($cash_adjustment_amount <> 0) {
|
|
$this->session->set('cash_mode', CASH_MODE_TRUE);
|
|
$this->sale_lib->add_payment(lang('Sales.cash_adjustment'), $cash_adjustment_amount, CASH_ADJUSTMENT_TRUE);
|
|
}
|
|
} else {
|
|
$amount_tendered = parse_decimals($this->request->getPost('amount_tendered'));
|
|
$this->sale_lib->add_payment($payment_type, $amount_tendered);
|
|
}
|
|
}
|
|
|
|
return $this->_reload($data);
|
|
}
|
|
|
|
/**
|
|
* Multiple Payments. Used in app/Views/sales/register.php
|
|
*
|
|
* @param string $payment_id
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getDeletePayment(string $payment_id): ResponseInterface|string
|
|
{
|
|
helper('url');
|
|
|
|
$this->sale_lib->delete_payment(base64url_decode($payment_id));
|
|
|
|
return $this->_reload();
|
|
}
|
|
|
|
/**
|
|
* Add an item to the sale. Used in app/Views/sales/register.php
|
|
*
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postAdd(): ResponseInterface|string
|
|
{
|
|
$data = [];
|
|
|
|
$discount = $this->config['default_sales_discount'];
|
|
$discount_type = $this->config['default_sales_discount_type'];
|
|
|
|
// Check if any discount is assigned to the selected customer
|
|
$customer_id = $this->sale_lib->get_customer();
|
|
if ($customer_id != NEW_ENTRY) {
|
|
// Load the customer discount if any
|
|
$customer_discount = $this->customer->get_info($customer_id)->discount;
|
|
$customer_discount_type = $this->customer->get_info($customer_id)->discount_type;
|
|
if ($customer_discount != '') {
|
|
$discount = $customer_discount;
|
|
$discount_type = $customer_discount_type;
|
|
}
|
|
}
|
|
|
|
$item_id_or_number_or_item_kit_or_receipt = $this->request->getPost('item', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
$this->token_lib->parse_barcode($quantity, $price, $item_id_or_number_or_item_kit_or_receipt);
|
|
$mode = $this->sale_lib->get_mode();
|
|
$quantity = ($mode == 'return') ? -$quantity : $quantity;
|
|
$item_location = $this->sale_lib->get_sale_location();
|
|
|
|
if ($mode == 'return' && $this->sale->is_valid_receipt($item_id_or_number_or_item_kit_or_receipt)) {
|
|
$this->sale_lib->return_entire_sale($item_id_or_number_or_item_kit_or_receipt);
|
|
} elseif ($this->item_kit->is_valid_item_kit($item_id_or_number_or_item_kit_or_receipt)) {
|
|
// Add kit item to order if one is assigned
|
|
$pieces = explode(' ', $item_id_or_number_or_item_kit_or_receipt);
|
|
|
|
$item_kit_id = (count($pieces) > 1) ? $pieces[1] : $item_id_or_number_or_item_kit_or_receipt;
|
|
$item_kit_info = $this->item_kit->get_info($item_kit_id);
|
|
$kit_item_id = $item_kit_info->kit_item_id;
|
|
$kit_price_option = $item_kit_info->price_option;
|
|
$kit_print_option = $item_kit_info->print_option; // 0-all, 1-priced, 2-kit-only
|
|
|
|
if ($discount_type == $item_kit_info->kit_discount_type) {
|
|
if ($item_kit_info->kit_discount > $discount) {
|
|
$discount = $item_kit_info->kit_discount;
|
|
}
|
|
} else {
|
|
$discount = $item_kit_info->kit_discount;
|
|
$discount_type = $item_kit_info->kit_discount_type;
|
|
}
|
|
|
|
$print_option = PRINT_ALL; // Always include in list of items on invoice // TODO: This variable is never used in the code
|
|
|
|
if (!empty($kit_item_id)) {
|
|
if (!$this->sale_lib->add_item($kit_item_id, $item_location, $quantity, $discount, $discount_type, PRICE_MODE_KIT, $kit_price_option, $kit_print_option, $price)) {
|
|
$data['error'] = lang('Sales.unable_to_add_item');
|
|
} else {
|
|
$data['warning'] = $this->sale_lib->out_of_stock($item_kit_id, $item_location);
|
|
}
|
|
}
|
|
|
|
// Add item kit items to order
|
|
$stock_warning = null;
|
|
if (!$this->sale_lib->add_item_kit($item_id_or_number_or_item_kit_or_receipt, $item_location, $discount, $discount_type, $kit_price_option, $kit_print_option, $stock_warning)) {
|
|
$data['error'] = lang('Sales.unable_to_add_item');
|
|
} elseif ($stock_warning != null) {
|
|
$data['warning'] = $stock_warning;
|
|
}
|
|
} else {
|
|
if ($item_id_or_number_or_item_kit_or_receipt == '' || !$this->sale_lib->add_item($item_id_or_number_or_item_kit_or_receipt, $item_location, $quantity, $discount, $discount_type, PRICE_MODE_STANDARD, null, null, $price)) {
|
|
$data['error'] = lang('Sales.unable_to_add_item');
|
|
} else {
|
|
$data['warning'] = $this->sale_lib->out_of_stock($item_id_or_number_or_item_kit_or_receipt, $item_location);
|
|
}
|
|
}
|
|
|
|
return $this->_reload($data);
|
|
}
|
|
|
|
/**
|
|
* Edit an item in the sale. Used in app/Views/sales/register.php
|
|
*
|
|
* @param string $line
|
|
* @return ResponseInterface|string
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postEditItem(string $line): ResponseInterface|string
|
|
{
|
|
$data = [];
|
|
|
|
$rules = [
|
|
'price' => 'trim|required|decimal_locale',
|
|
'quantity' => 'trim|required|decimal_locale',
|
|
'discount' => 'trim|permit_empty|decimal_locale',
|
|
];
|
|
|
|
if ($this->validate($rules)) {
|
|
$description = $this->request->getPost('description', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
$serialnumber = $this->request->getPost('serialnumber', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
$price = parse_decimals($this->request->getPost('price'));
|
|
$quantity = parse_decimals($this->request->getPost('quantity'));
|
|
$discount_type = $this->request->getPost('discount_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
$discount = $discount_type
|
|
? parse_quantity($this->request->getPost('discount'))
|
|
: parse_decimals($this->request->getPost('discount'));
|
|
|
|
$item_location = $this->request->getPost('location', FILTER_SANITIZE_NUMBER_INT);
|
|
$discounted_total = $this->request->getPost('discounted_total') != ''
|
|
? parse_decimals($this->request->getPost('discounted_total') ?? '')
|
|
: null;
|
|
|
|
|
|
$this->sale_lib->edit_item($line, $description, $serialnumber, $quantity, $discount, $discount_type, $price, $discounted_total);
|
|
|
|
$this->sale_lib->empty_payments();
|
|
|
|
$data['warning'] = $this->sale_lib->out_of_stock($this->sale_lib->get_item_id($line), $item_location);
|
|
} else {
|
|
$data['error'] = lang('Sales.error_editing_item');
|
|
}
|
|
|
|
return $this->_reload($data);
|
|
}
|
|
|
|
/**
|
|
* Deletes an item specified in the parameter from the shopping cart. Used in app/Views/sales/register.php
|
|
*
|
|
* @param int $item_id
|
|
* @return ResponseInterface
|
|
* @throws ReflectionException
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getDeleteItem(int $item_id): ResponseInterface|string
|
|
{
|
|
$this->sale_lib->delete_item($item_id);
|
|
|
|
$this->sale_lib->empty_payments();
|
|
|
|
return $this->_reload();
|
|
}
|
|
|
|
/**
|
|
* Remove the current customer from the sale. Used in app/Views/sales/register.php
|
|
*
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getRemoveCustomer(): ResponseInterface|string
|
|
{
|
|
$this->sale_lib->clear_giftcard_remainder();
|
|
$this->sale_lib->clear_rewards_remainder();
|
|
$this->sale_lib->delete_payment(lang('Sales.rewards'));
|
|
$this->sale_lib->clear_invoice_number();
|
|
$this->sale_lib->clear_quote_number();
|
|
$this->sale_lib->remove_customer();
|
|
|
|
return $this->_reload();
|
|
}
|
|
|
|
/**
|
|
* Complete and finalize a sale. Used in app/Views/sales/register.php
|
|
*
|
|
* @return string
|
|
* @throws ReflectionException
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postComplete(): string // TODO: this function is huge. Probably should be refactored.
|
|
{
|
|
$sale_id = $this->sale_lib->get_sale_id();
|
|
$data = [];
|
|
$data['dinner_table'] = $this->sale_lib->get_dinner_table();
|
|
|
|
$data['cart'] = $this->sale_lib->get_cart();
|
|
|
|
$data['include_hsn'] = (bool)$this->config['include_hsn'];
|
|
$__time = time();
|
|
$data['transaction_time'] = to_datetime($__time);
|
|
$data['transaction_date'] = to_date($__time);
|
|
$data['show_stock_locations'] = $this->stock_location->show_locations('sales');
|
|
$data['comments'] = $this->sale_lib->get_comment();
|
|
$employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
|
$employee_info = $this->employee->get_info($employee_id);
|
|
$data['employee'] = $employee_info->first_name . ' ' . mb_substr($employee_info->last_name, 0, 1);
|
|
|
|
$data['company_info'] = implode("\n", [$this->config['address'], $this->config['phone']]);
|
|
|
|
if ($this->config['account_number']) {
|
|
$data['company_info'] .= "\n" . lang('Sales.account_number') . ": " . $this->config['account_number'];
|
|
}
|
|
|
|
if ($this->config['tax_id'] != '') {
|
|
$data['company_info'] .= "\n" . lang('Sales.tax_id') . ": " . $this->config['tax_id'];
|
|
}
|
|
|
|
$data['invoice_number_enabled'] = $this->sale_lib->is_invoice_mode();
|
|
$data['cur_giftcard_value'] = $this->sale_lib->get_giftcard_remainder();
|
|
$data['cur_rewards_value'] = $this->sale_lib->get_rewards_remainder();
|
|
$data['print_after_sale'] = $this->session->get('sales_print_after_sale');
|
|
$data['price_work_orders'] = $this->sale_lib->is_price_work_orders();
|
|
$data['email_receipt'] = $this->sale_lib->is_email_receipt();
|
|
$customer_id = $this->sale_lib->get_customer();
|
|
$invoice_number = $this->sale_lib->get_invoice_number();
|
|
$data["invoice_number"] = $invoice_number;
|
|
$work_order_number = $this->sale_lib->get_work_order_number();
|
|
$data["work_order_number"] = $work_order_number;
|
|
$quote_number = $this->sale_lib->get_quote_number();
|
|
$data["quote_number"] = $quote_number;
|
|
$customer_info = $this->_load_customer_data($customer_id, $data);
|
|
|
|
if ($customer_info != null) {
|
|
$data["customer_comments"] = $customer_info->comments;
|
|
$data['tax_id'] = $customer_info->tax_id;
|
|
}
|
|
$tax_details = $this->tax_lib->get_taxes($data['cart']); // TODO: Duplicated code
|
|
$data['taxes'] = $tax_details[0];
|
|
$data['discount'] = $this->sale_lib->get_discount();
|
|
$data['payments'] = $this->sale_lib->get_payments();
|
|
|
|
// Returns 'subtotal', 'total', 'cash_total', 'payment_total', 'amount_due', 'cash_amount_due', 'payments_cover_total'
|
|
$totals = $this->sale_lib->get_totals($tax_details[0]);
|
|
$data['subtotal'] = $totals['subtotal'];
|
|
$data['total'] = $totals['total'];
|
|
$data['payments_total'] = $totals['payment_total'];
|
|
$data['payments_cover_total'] = $totals['payments_cover_total'];
|
|
$data['cash_rounding'] = $this->session->get('cash_rounding');
|
|
$data['cash_mode'] = $this->session->get('cash_mode'); // TODO: Duplicated code
|
|
$data['prediscount_subtotal'] = $totals['prediscount_subtotal'];
|
|
$data['cash_total'] = $totals['cash_total'];
|
|
$data['non_cash_total'] = $totals['total'];
|
|
$data['cash_amount_due'] = $totals['cash_amount_due'];
|
|
$data['non_cash_amount_due'] = $totals['amount_due'];
|
|
|
|
if ($data['cash_mode']) { // TODO: Convert this to ternary notation
|
|
$data['amount_due'] = $totals['cash_amount_due'];
|
|
} else {
|
|
$data['amount_due'] = $totals['amount_due'];
|
|
}
|
|
|
|
$data['amount_change'] = $data['amount_due'] * -1;
|
|
|
|
if ($data['amount_change'] > 0) {
|
|
// Save cash refund to the cash payment transaction if found, if not then add as new Cash transaction
|
|
|
|
if (array_key_exists(lang('Sales.cash'), $data['payments'])) {
|
|
$data['payments'][lang('Sales.cash')]['cash_refund'] = $data['amount_change'];
|
|
} else {
|
|
$payment = [
|
|
lang('Sales.cash') => [
|
|
'payment_type' => lang('Sales.cash'),
|
|
'payment_amount' => 0,
|
|
'cash_refund' => $data['amount_change']
|
|
]
|
|
];
|
|
|
|
$data['payments'] += $payment;
|
|
}
|
|
}
|
|
|
|
$data['print_price_info'] = true;
|
|
|
|
if ($this->sale_lib->is_invoice_mode()) {
|
|
$invoice_format = $this->config['sales_invoice_format'];
|
|
|
|
// Generate final invoice number (if using the invoice in sales by receipt mode then the invoice number can be manually entered or altered in some way
|
|
if (!empty($invoice_format) && $invoice_number == null) {
|
|
// The user can retain the default encoded format or can manually override it. It still passes through the rendering step.
|
|
$invoice_number = $this->token_lib->render($invoice_format);
|
|
}
|
|
|
|
|
|
if ($sale_id == NEW_ENTRY && $this->sale->check_invoice_number_exists($invoice_number)) {
|
|
$data['error'] = lang('Sales.invoice_number_duplicate', [$invoice_number]);
|
|
$this->_reload($data);
|
|
} else {
|
|
$data['invoice_number'] = $invoice_number;
|
|
$data['sale_status'] = COMPLETED;
|
|
$sale_type = SALE_TYPE_INVOICE;
|
|
|
|
$invoice_type = $this->config['invoice_type'];
|
|
if (!Sale_lib::isValidInvoiceType($invoice_type)) {
|
|
$invoice_type = 'invoice';
|
|
}
|
|
$invoice_view = $invoice_type;
|
|
|
|
// Save the data to the sales table
|
|
$data['sale_id_num'] = $this->sale->save_value($sale_id, $data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $work_order_number, $quote_number, $sale_type, $data['payments'], $data['dinner_table'], $tax_details);
|
|
$data['sale_id'] = 'POS ' . $data['sale_id_num'];
|
|
|
|
// Resort and filter cart lines for printing
|
|
$data['cart'] = $this->sale_lib->sort_and_filter_cart($data['cart']);
|
|
|
|
if ($data['sale_id_num'] == NEW_ENTRY) {
|
|
$data['error_message'] = lang('Sales.transaction_failed');
|
|
} else {
|
|
$data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']);
|
|
$this->sale_lib->clear_all();
|
|
return view('sales/' . $invoice_view, $data);
|
|
}
|
|
}
|
|
} elseif ($this->sale_lib->is_work_order_mode()) {
|
|
|
|
if (!($data['price_work_orders'] == 1)) {
|
|
$data['print_price_info'] = false;
|
|
}
|
|
|
|
$data['sales_work_order'] = lang('Sales.work_order');
|
|
$data['work_order_number_label'] = lang('Sales.work_order_number');
|
|
|
|
if ($work_order_number == null) {
|
|
// Generate work order number
|
|
$work_order_format = $this->config['work_order_format'];
|
|
$work_order_number = $this->token_lib->render($work_order_format);
|
|
}
|
|
|
|
if ($sale_id == NEW_ENTRY && $this->sale->check_work_order_number_exists($work_order_number)) {
|
|
$data['error'] = lang('Sales.work_order_number_duplicate');
|
|
$this->_reload($data);
|
|
} else {
|
|
$data['work_order_number'] = $work_order_number;
|
|
$data['sale_status'] = SUSPENDED;
|
|
$sale_type = SALE_TYPE_WORK_ORDER;
|
|
|
|
$data['sale_id_num'] = $this->sale->save_value($sale_id, $data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $work_order_number, $quote_number, $sale_type, $data['payments'], $data['dinner_table'], $tax_details);
|
|
$this->sale_lib->set_suspended_id($data['sale_id_num']);
|
|
|
|
$data['cart'] = $this->sale_lib->sort_and_filter_cart($data['cart']);
|
|
|
|
$data['barcode'] = null;
|
|
|
|
$this->sale_lib->clear_mode();
|
|
$this->sale_lib->clear_all();
|
|
return view('sales/work_order', $data);
|
|
}
|
|
} elseif ($this->sale_lib->is_quote_mode()) {
|
|
$data['sales_quote'] = lang('Sales.quote');
|
|
$data['quote_number_label'] = lang('Sales.quote_number');
|
|
|
|
if ($quote_number == null) {
|
|
// Generate quote number
|
|
$quote_format = $this->config['sales_quote_format'];
|
|
$quote_number = $this->token_lib->render($quote_format);
|
|
}
|
|
|
|
if ($sale_id == NEW_ENTRY && $this->sale->check_quote_number_exists($quote_number)) {
|
|
$data['error'] = lang('Sales.quote_number_duplicate');
|
|
$this->_reload($data);
|
|
} else {
|
|
$data['quote_number'] = $quote_number;
|
|
$data['sale_status'] = SUSPENDED;
|
|
$sale_type = SALE_TYPE_QUOTE;
|
|
|
|
$data['sale_id_num'] = $this->sale->save_value($sale_id, $data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $work_order_number, $quote_number, $sale_type, $data['payments'], $data['dinner_table'], $tax_details);
|
|
$this->sale_lib->set_suspended_id($data['sale_id_num']);
|
|
|
|
$data['cart'] = $this->sale_lib->sort_and_filter_cart($data['cart']);
|
|
$data['barcode'] = null;
|
|
|
|
$this->sale_lib->clear_mode();
|
|
$this->sale_lib->clear_all();
|
|
return view('sales/quote', $data);
|
|
}
|
|
} else {
|
|
// Save the data to the sales table
|
|
$data['sale_status'] = COMPLETED;
|
|
if ($this->sale_lib->is_return_mode()) {
|
|
$sale_type = SALE_TYPE_RETURN;
|
|
} else {
|
|
$sale_type = SALE_TYPE_POS;
|
|
}
|
|
|
|
$data['sale_id_num'] = $this->sale->save_value($sale_id, $data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $work_order_number, $quote_number, $sale_type, $data['payments'], $data['dinner_table'], $tax_details);
|
|
|
|
$data['sale_id'] = 'POS ' . $data['sale_id_num'];
|
|
|
|
$data['cart'] = $this->sale_lib->sort_and_filter_cart($data['cart']);
|
|
|
|
if ($data['sale_id_num'] == NEW_ENTRY) {
|
|
$data['error_message'] = lang('Sales.transaction_failed');
|
|
} else {
|
|
$data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']);
|
|
$this->sale_lib->clear_all();
|
|
return view('sales/receipt', $data);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Email PDF invoice to customer. Used in app/Views/sales/form.php, invoice.php, quote.php, tax_invoice.php and work_order.php
|
|
*
|
|
* @param int $sale_id
|
|
* @param string $type
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getSendPdf(int $sale_id, string $type = 'invoice'): ResponseInterface
|
|
{
|
|
$sale_data = $this->_load_sale_data($sale_id);
|
|
|
|
$result = false;
|
|
$message = lang('Sales.invoice_no_email');
|
|
|
|
if (!empty($sale_data['customer_email'])) {
|
|
$to = $sale_data['customer_email'];
|
|
$number = array_key_exists($type . "_number", $sale_data) ? $sale_data[$type . "_number"] : "";
|
|
$subject = lang('Sales.' . $type) . ' ' . $number;
|
|
|
|
$text = $this->config['invoice_email_message'];
|
|
$tokens = [
|
|
new Token_invoice_sequence($number),
|
|
new Token_invoice_count('POS ' . $sale_data['sale_id']),
|
|
new Token_customer((array)$sale_data)
|
|
];
|
|
$text = $this->token_lib->render($text, $tokens);
|
|
$sale_data['mimetype'] = mime_content_type(FCPATH . 'uploads/' . $this->config['company_logo']);
|
|
|
|
// Generate email attachment: invoice in PDF format
|
|
$view = Services::renderer();
|
|
$html = $view->setData($sale_data)->render("sales/$type" . '_email', $sale_data);
|
|
|
|
// Load PDF helper
|
|
helper(['dompdf', 'file']);
|
|
$filename = sys_get_temp_dir() . '/' . lang('Sales.' . $type) . '-' . str_replace('/', '-', $number) . '.pdf';
|
|
if (file_put_contents($filename, create_pdf($html)) !== false) {
|
|
$result = $this->email_lib->sendEmail($to, $subject, $text, $filename);
|
|
}
|
|
|
|
$message = lang($result ? "Sales." . $type . "_sent" : "Sales." . $type . "_unsent") . ' ' . $to;
|
|
}
|
|
|
|
$this->sale_lib->clear_all();
|
|
|
|
return $this->response->setJSON(['success' => $result, 'message' => $message, 'id' => $sale_id]);
|
|
}
|
|
|
|
/**
|
|
* Emails sales receipt to customer. Used in app/Views/sales/receipt.php
|
|
*
|
|
* @param int $sale_id
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getSendReceipt(int $sale_id): ResponseInterface
|
|
{
|
|
$sale_data = $this->_load_sale_data($sale_id);
|
|
|
|
$result = false;
|
|
$message = lang('Sales.receipt_no_email');
|
|
|
|
if (!empty($sale_data['customer_email'])) {
|
|
$sale_data['barcode'] = $this->barcode_lib->generate_receipt_barcode($sale_data['sale_id']);
|
|
$sale_data['img_tag'] = '';
|
|
|
|
$logo_path = FCPATH . 'uploads/' . $this->config['company_logo'];
|
|
if (!empty($this->config['company_logo']) && file_exists($logo_path)) {
|
|
$logo_data = base64_encode(file_get_contents($logo_path));
|
|
$sale_data['img_tag'] = '<img id="image" src="data:image/png;base64,' . $logo_data . '" alt="company_logo">';
|
|
}
|
|
|
|
$to = $sale_data['customer_email'];
|
|
$subject = lang('Sales.receipt');
|
|
|
|
$view = Services::renderer();
|
|
$text = $view->setData($sale_data)->render('sales/receipt_email');
|
|
|
|
$result = $this->email_lib->sendEmail($to, $subject, $text);
|
|
|
|
$message = lang($result ? 'Sales.receipt_sent' : 'Sales.receipt_unsent') . ' ' . $to;
|
|
}
|
|
|
|
$this->sale_lib->clear_all();
|
|
|
|
return $this->response->setJSON(['success' => $result, 'message' => $message, 'id' => $sale_id]);
|
|
}
|
|
|
|
/**
|
|
* @param int $customer_id
|
|
* @param array $data
|
|
* @param bool $stats
|
|
* @return array|stdClass|string|null
|
|
*/
|
|
private function _load_customer_data(int $customer_id, array &$data, bool $stats = false): array|string|stdClass|null // TODO: Hungarian notation
|
|
{
|
|
$customer_info = '';
|
|
|
|
if ($customer_id != NEW_ENTRY) {
|
|
$customer_info = $this->customer->get_info($customer_id);
|
|
$data['customer_id'] = $customer_id;
|
|
|
|
if (!empty($customer_info->company_name)) {
|
|
$data['customer'] = $customer_info->company_name;
|
|
} else {
|
|
$data['customer'] = $customer_info->first_name . ' ' . $customer_info->last_name;
|
|
}
|
|
|
|
$data['first_name'] = $customer_info->first_name;
|
|
$data['last_name'] = $customer_info->last_name;
|
|
$data['customer_email'] = $customer_info->email;
|
|
$data['customer_address'] = $customer_info->address_1;
|
|
|
|
if (!empty($customer_info->zip) || !empty($customer_info->city)) {
|
|
$data['customer_location'] = $customer_info->zip . ' ' . $customer_info->city . "\n" . $customer_info->state;
|
|
} else {
|
|
$data['customer_location'] = '';
|
|
}
|
|
|
|
$data['customer_account_number'] = $customer_info->account_number;
|
|
$data['customer_discount'] = $customer_info->discount;
|
|
$data['customer_discount_type'] = $customer_info->discount_type;
|
|
$package_id = $this->customer->get_info($customer_id)->package_id;
|
|
|
|
if ($package_id != null) {
|
|
$package_name = $this->customer_rewards->get_name($package_id);
|
|
$points = $this->customer->get_info($customer_id)->points;
|
|
$data['customer_rewards']['package_id'] = $package_id;
|
|
$data['customer_rewards']['points'] = empty($points) ? 0 : $points;
|
|
$data['customer_rewards']['package_name'] = $package_name;
|
|
}
|
|
|
|
if ($stats) {
|
|
$cust_stats = $this->customer->get_stats($customer_id);
|
|
$data['customer_total'] = empty($cust_stats) ? 0 : $cust_stats->total;
|
|
}
|
|
|
|
$data['customer_info'] = implode("\n", [
|
|
$data['customer'],
|
|
$data['customer_address'],
|
|
$data['customer_location']
|
|
]);
|
|
|
|
if ($data['customer_account_number']) {
|
|
$data['customer_info'] .= "\n" . lang('Sales.account_number') . ": " . $data['customer_account_number'];
|
|
}
|
|
|
|
if ($customer_info->tax_id != '') {
|
|
$data['customer_info'] .= "\n" . lang('Sales.tax_id') . ": " . $customer_info->tax_id;
|
|
}
|
|
$data['tax_id'] = $customer_info->tax_id;
|
|
}
|
|
|
|
return $customer_info;
|
|
}
|
|
|
|
/**
|
|
* @param $sale_id
|
|
* @return array
|
|
*/
|
|
private function _load_sale_data($sale_id): array // TODO: Hungarian notation
|
|
{
|
|
$this->sale_lib->clear_all();
|
|
$cash_rounding = $this->sale_lib->reset_cash_rounding();
|
|
$data['cash_rounding'] = $cash_rounding;
|
|
|
|
$sale_info = $this->sale->get_info($sale_id)->getRowArray();
|
|
$this->sale_lib->copy_entire_sale($sale_id);
|
|
$data = [];
|
|
$data['cart'] = $this->sale_lib->get_cart();
|
|
$data['payments'] = $this->sale_lib->get_payments();
|
|
$data['selected_payment_type'] = $this->sale_lib->get_payment_type();
|
|
|
|
$tax_details = $this->tax_lib->get_taxes($data['cart'], $sale_id);
|
|
$data['taxes'] = $this->sale->get_sales_taxes($sale_id);
|
|
$data['discount'] = $this->sale_lib->get_discount();
|
|
$data['transaction_time'] = to_datetime(strtotime($sale_info['sale_time']));
|
|
$data['transaction_date'] = to_date(strtotime($sale_info['sale_time']));
|
|
$data['show_stock_locations'] = $this->stock_location->show_locations('sales');
|
|
|
|
$data['include_hsn'] = (bool)$this->config['include_hsn'];
|
|
|
|
// Returns 'subtotal', 'total', 'cash_total', 'payment_total', 'amount_due', 'cash_amount_due', 'payments_cover_total'
|
|
$totals = $this->sale_lib->get_totals($tax_details[0]);
|
|
$this->session->set('cash_adjustment_amount', $totals['cash_adjustment_amount']);
|
|
$data['subtotal'] = $totals['subtotal'];
|
|
$data['payments_total'] = $totals['payment_total'];
|
|
$data['payments_cover_total'] = $totals['payments_cover_total'];
|
|
$data['cash_mode'] = $this->session->get('cash_mode'); // TODO: Duplicated code.
|
|
$data['prediscount_subtotal'] = $totals['prediscount_subtotal'];
|
|
$data['cash_total'] = $totals['cash_total'];
|
|
$data['non_cash_total'] = $totals['total'];
|
|
$data['cash_amount_due'] = $totals['cash_amount_due'];
|
|
$data['non_cash_amount_due'] = $totals['amount_due'];
|
|
|
|
if ($data['cash_mode'] && ($data['selected_payment_type'] === lang('Sales.cash') || $data['payments_total'] > 0)) {
|
|
$data['total'] = $totals['cash_total'];
|
|
$data['amount_due'] = $totals['cash_amount_due'];
|
|
} else {
|
|
$data['total'] = $totals['total'];
|
|
$data['amount_due'] = $totals['amount_due'];
|
|
}
|
|
|
|
$data['amount_change'] = $data['amount_due'] * -1;
|
|
|
|
$employee_info = $this->employee->get_info($this->sale_lib->get_employee());
|
|
$data['employee'] = $employee_info->first_name . ' ' . mb_substr($employee_info->last_name, 0, 1);
|
|
$this->_load_customer_data($this->sale_lib->get_customer(), $data);
|
|
|
|
$data['sale_id_num'] = $sale_id;
|
|
$data['sale_id'] = 'POS ' . $sale_id;
|
|
$data['comments'] = $sale_info['comment'];
|
|
$data['invoice_number'] = $sale_info['invoice_number'];
|
|
$data['quote_number'] = $sale_info['quote_number'];
|
|
$data['sale_status'] = $sale_info['sale_status'];
|
|
|
|
$data['company_info'] = implode("\n", [$this->config['address'], $this->config['phone']]); // TODO: Duplicated code.
|
|
|
|
if ($this->config['account_number']) {
|
|
$data['company_info'] .= "\n" . lang('Sales.account_number') . ": " . $this->config['account_number'];
|
|
}
|
|
if ($this->config['tax_id'] != '') {
|
|
$data['company_info'] .= "\n" . lang('Sales.tax_id') . ": " . $this->config['tax_id'];
|
|
}
|
|
|
|
$data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']);
|
|
$data['print_after_sale'] = false;
|
|
$data['price_work_orders'] = false;
|
|
|
|
if ($this->sale_lib->get_mode() == 'sale_invoice') { // TODO: Duplicated code.
|
|
$data['mode_label'] = lang('Sales.invoice');
|
|
$data['customer_required'] = lang('Sales.customer_required');
|
|
} elseif ($this->sale_lib->get_mode() == 'sale_quote') {
|
|
$data['mode_label'] = lang('Sales.quote');
|
|
$data['customer_required'] = lang('Sales.customer_required');
|
|
} elseif ($this->sale_lib->get_mode() == 'sale_work_order') {
|
|
$data['mode_label'] = lang('Sales.work_order');
|
|
$data['customer_required'] = lang('Sales.customer_required');
|
|
} elseif ($this->sale_lib->get_mode() == 'return') {
|
|
$data['mode_label'] = lang('Sales.return');
|
|
$data['customer_required'] = lang('Sales.customer_optional');
|
|
} else {
|
|
$data['mode_label'] = lang('Sales.receipt');
|
|
$data['customer_required'] = lang('Sales.customer_optional');
|
|
}
|
|
|
|
$invoice_type = $this->config['invoice_type'];
|
|
if (!Sale_lib::isValidInvoiceType($invoice_type)) {
|
|
$invoice_type = 'invoice';
|
|
}
|
|
$data['invoice_view'] = $invoice_type;
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* @param array $data
|
|
* @return void
|
|
*/
|
|
private function _reload(array $data = []): ResponseInterface|string // TODO: Hungarian notation
|
|
{
|
|
$sale_id = $this->session->get('sale_id'); // TODO: This variable is never used
|
|
|
|
if ($sale_id == '') {
|
|
$sale_id = NEW_ENTRY;
|
|
$this->session->set('sale_id', NEW_ENTRY);
|
|
}
|
|
$cash_rounding = $this->sale_lib->reset_cash_rounding();
|
|
|
|
// cash_rounding indicates only that the site is configured for cash rounding
|
|
$data['cash_rounding'] = $cash_rounding;
|
|
|
|
$data['cart'] = $this->sale_lib->get_cart();
|
|
$customer_info = $this->_load_customer_data($this->sale_lib->get_customer(), $data, true);
|
|
|
|
$data['modes'] = $this->sale_lib->get_register_mode_options();
|
|
$data['mode'] = $this->sale_lib->get_mode();
|
|
$data['selected_table'] = $this->sale_lib->get_dinner_table();
|
|
$data['empty_tables'] = $this->sale_lib->get_empty_tables($data['selected_table']);
|
|
$data['stock_locations'] = $this->stock_location->get_allowed_locations('sales');
|
|
$data['stock_location'] = $this->sale_lib->get_sale_location();
|
|
$data['tax_exclusive_subtotal'] = $this->sale_lib->get_subtotal(true, true);
|
|
$tax_details = $this->tax_lib->get_taxes($data['cart']); // TODO: Duplicated code.
|
|
$data['taxes'] = $tax_details[0];
|
|
$data['discount'] = $this->sale_lib->get_discount();
|
|
$data['payments'] = $this->sale_lib->get_payments();
|
|
|
|
// Returns 'subtotal', 'total', 'cash_total', 'payment_total', 'amount_due', 'cash_amount_due', 'payments_cover_total'
|
|
$totals = $this->sale_lib->get_totals($tax_details[0]);
|
|
|
|
$data['item_count'] = $totals['item_count'];
|
|
$data['total_units'] = $totals['total_units'];
|
|
$data['subtotal'] = $totals['subtotal'];
|
|
$data['total'] = $totals['total'];
|
|
$data['payments_total'] = $totals['payment_total'];
|
|
$data['payments_cover_total'] = $totals['payments_cover_total'];
|
|
|
|
// cash_mode indicates whether this sale is going to be processed using cash_rounding
|
|
$cash_mode = $this->session->get('cash_mode');
|
|
$data['cash_mode'] = $cash_mode;
|
|
$data['prediscount_subtotal'] = $totals['prediscount_subtotal']; // TODO: Duplicated code.
|
|
$data['cash_total'] = $totals['cash_total'];
|
|
$data['non_cash_total'] = $totals['total'];
|
|
$data['cash_amount_due'] = $totals['cash_amount_due'];
|
|
$data['non_cash_amount_due'] = $totals['amount_due'];
|
|
|
|
$data['selected_payment_type'] = $this->sale_lib->get_payment_type();
|
|
|
|
if ($data['cash_mode'] && ($data['selected_payment_type'] == lang('Sales.cash') || $data['payments_total'] > 0)) {
|
|
$data['total'] = $totals['cash_total'];
|
|
$data['amount_due'] = $totals['cash_amount_due'];
|
|
} else {
|
|
$data['total'] = $totals['total'];
|
|
$data['amount_due'] = $totals['amount_due'];
|
|
}
|
|
|
|
$data['amount_change'] = $data['amount_due'] * -1;
|
|
|
|
$data['comment'] = $this->sale_lib->get_comment();
|
|
$data['email_receipt'] = $this->sale_lib->is_email_receipt();
|
|
|
|
if ($customer_info && $this->config['customer_reward_enable']) {
|
|
$data['payment_options'] = $this->sale->get_payment_options(true, true);
|
|
} else {
|
|
$data['payment_options'] = $this->sale->get_payment_options();
|
|
}
|
|
|
|
$data['items_module_allowed'] = $this->employee->has_grant('items', $this->employee->get_logged_in_employee_info()->person_id);
|
|
$data['change_price'] = $this->employee->has_grant('sales_change_price', $this->employee->get_logged_in_employee_info()->person_id);
|
|
|
|
$temp_invoice_number = $this->sale_lib->get_invoice_number();
|
|
$invoice_format = $this->config['sales_invoice_format'];
|
|
|
|
if ($temp_invoice_number == null || $temp_invoice_number == '') {
|
|
$temp_invoice_number = $this->token_lib->render($invoice_format, [], false);
|
|
}
|
|
|
|
$data['invoice_number'] = $temp_invoice_number;
|
|
|
|
$data['print_after_sale'] = $this->sale_lib->is_print_after_sale();
|
|
$data['price_work_orders'] = $this->sale_lib->is_price_work_orders();
|
|
|
|
$data['pos_mode'] = $data['mode'] == 'sale' || $data['mode'] == 'return';
|
|
|
|
$data['quote_number'] = $this->sale_lib->get_quote_number();
|
|
$data['work_order_number'] = $this->sale_lib->get_work_order_number();
|
|
|
|
// TODO: the if/else set below should be converted to a switch
|
|
if ($this->sale_lib->get_mode() == 'sale_invoice') { // TODO: Duplicated code.
|
|
$data['mode_label'] = lang('Sales.invoice');
|
|
$data['customer_required'] = lang('Sales.customer_required');
|
|
} elseif ($this->sale_lib->get_mode() == 'sale_quote') {
|
|
$data['mode_label'] = lang('Sales.quote');
|
|
$data['customer_required'] = lang('Sales.customer_required');
|
|
} elseif ($this->sale_lib->get_mode() == 'sale_work_order') {
|
|
$data['mode_label'] = lang('Sales.work_order');
|
|
$data['customer_required'] = lang('Sales.customer_required');
|
|
} elseif ($this->sale_lib->get_mode() == 'return') {
|
|
$data['mode_label'] = lang('Sales.return');
|
|
$data['customer_required'] = lang('Sales.customer_optional');
|
|
} else {
|
|
$data['mode_label'] = lang('Sales.receipt');
|
|
$data['customer_required'] = lang('Sales.customer_optional');
|
|
}
|
|
|
|
return view("sales/register", $data);
|
|
}
|
|
|
|
/**
|
|
* Load the sales receipt for a sale. Used in app/Views/sales/form.php
|
|
*
|
|
* @param int $sale_id
|
|
* @return string
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getReceipt(int $sale_id): string
|
|
{
|
|
$data = $this->_load_sale_data($sale_id);
|
|
$this->sale_lib->clear_all();
|
|
|
|
return view('sales/receipt', $data);
|
|
}
|
|
|
|
/**
|
|
* Loads the sales invoice for a sale. Used in app/Views/sales/form.php
|
|
*
|
|
* @param int $sale_id
|
|
* @return string
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getInvoice(int $sale_id): string
|
|
{
|
|
$data = $this->_load_sale_data($sale_id);
|
|
$this->sale_lib->clear_all();
|
|
|
|
return view('sales/' . $data['invoice_view'], $data);
|
|
}
|
|
|
|
/**
|
|
* Edits an existing sale or work order. Used in app/Views/sales/form.php
|
|
*
|
|
* @param int $sale_id
|
|
* @return string
|
|
* @throws ReflectionException
|
|
*/
|
|
public function getEdit(int $sale_id): string
|
|
{
|
|
$data = [];
|
|
|
|
$sale_info = $this->sale->get_info($sale_id)->getRowArray();
|
|
$data['selected_customer_id'] = $sale_info['customer_id'];
|
|
$data['selected_customer_name'] = $sale_info['customer_name'];
|
|
$employee_info = $this->employee->get_info($sale_info['employee_id']);
|
|
$data['selected_employee_id'] = $sale_info['employee_id'];
|
|
$data['selected_employee_name'] = $employee_info->first_name . ' ' . $employee_info->last_name;
|
|
$data['sale_info'] = $sale_info;
|
|
$balance_due = round($sale_info['amount_due'] - $sale_info['amount_tendered'] + $sale_info['cash_refund'], totals_decimals(), PHP_ROUND_HALF_UP);
|
|
|
|
if (!$this->sale_lib->reset_cash_rounding() && $balance_due < 0) {
|
|
$balance_due = 0;
|
|
}
|
|
|
|
$data['payments'] = [];
|
|
|
|
foreach ($this->sale->get_sale_payments($sale_id)->getResult() as $payment) {
|
|
foreach (get_object_vars($payment) as $property => $value) {
|
|
$payment->$property = $value;
|
|
}
|
|
$data['payments'][] = $payment;
|
|
}
|
|
|
|
$data['payment_type_new'] = PAYMENT_TYPE_UNASSIGNED;
|
|
$data['payment_amount_new'] = $balance_due;
|
|
|
|
$data['balance_due'] = $balance_due != 0;
|
|
|
|
// Don't allow gift card to be a payment option in a sale transaction edit because it's a complex change
|
|
$payment_options = $this->sale->get_payment_options(false);
|
|
|
|
if ($this->sale_lib->reset_cash_rounding()) {
|
|
$payment_options[lang('Sales.cash_adjustment')] = lang('Sales.cash_adjustment');
|
|
}
|
|
|
|
$data['payment_options'] = $payment_options;
|
|
|
|
// Set up a slightly modified list of payment types for new payment entry
|
|
$payment_options["--"] = lang('Common.none_selected_text');
|
|
|
|
$data['new_payment_options'] = $payment_options;
|
|
|
|
return view('sales/form', $data);
|
|
}
|
|
|
|
/**
|
|
* @param int $sale_id
|
|
* @return ResponseInterface
|
|
* @throws ReflectionException
|
|
*/
|
|
public function postDelete(int $sale_id = NEW_ENTRY, bool $update_inventory = true): ResponseInterface
|
|
{
|
|
$employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
|
$has_grant = $this->employee->has_grant('sales_delete', $employee_id);
|
|
|
|
if (!$has_grant) {
|
|
return $this->response->setJSON(['success' => false, 'message' => lang('Sales.not_authorized')]);
|
|
} else {
|
|
$sale_ids = $sale_id == NEW_ENTRY ? $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT) : [$sale_id];
|
|
|
|
if ($this->sale->delete_list($sale_ids, $employee_id, $update_inventory)) {
|
|
return $this->response->setJSON([
|
|
'success' => true,
|
|
'message' => lang('Sales.successfully_deleted') . ' ' . count($sale_ids) . ' ' . lang('Sales.one_or_multiple'),
|
|
'ids' => $sale_ids
|
|
]);
|
|
} else {
|
|
return $this->response->setJSON(['success' => false, 'message' => lang('Sales.unsuccessfully_deleted')]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param int $sale_id
|
|
* @param bool $update_inventory
|
|
* @return ResponseInterface
|
|
*/
|
|
public function restore(int $sale_id = NEW_ENTRY, bool $update_inventory = true): ResponseInterface
|
|
{
|
|
$employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
|
$has_grant = $this->employee->has_grant('sales_delete', $employee_id);
|
|
|
|
if (!$has_grant) {
|
|
return $this->response->setJSON(['success' => false, 'message' => lang('Sales.not_authorized')]);
|
|
} else {
|
|
$sale_ids = $sale_id == NEW_ENTRY ? $this->request->getPost('ids', FILTER_SANITIZE_NUMBER_INT) : [$sale_id];
|
|
|
|
if ($this->sale->restore_list($sale_ids, $employee_id, $update_inventory)) {
|
|
return $this->response->setJSON([
|
|
'success' => true,
|
|
'message' => lang('Sales.successfully_restored') . ' ' . count($sale_ids) . ' ' . lang('Sales.one_or_multiple'),
|
|
'ids' => $sale_ids
|
|
]);
|
|
} else {
|
|
return $this->response->setJSON(['success' => false, 'message' => lang('Sales.unsuccessfully_restored')]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This saves the sale from the update sale view (sales/form).
|
|
* It only updates the sales table and payments.
|
|
* @param int $sale_id
|
|
* @return ResponseInterface
|
|
* @throws ReflectionException
|
|
*/
|
|
public function postSave(int $sale_id = NEW_ENTRY): ResponseInterface
|
|
{
|
|
$newdate = $this->request->getPost('date', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
$employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
|
$inventory = model(Inventory::class);
|
|
$date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $newdate);
|
|
$sale_time = $date_formatter->format('Y-m-d H:i:s');
|
|
|
|
$sale_data = [
|
|
'sale_time' => $sale_time,
|
|
'customer_id' => $this->request->getPost('customer_id') != '' ? $this->request->getPost('customer_id', FILTER_SANITIZE_NUMBER_INT) : null,
|
|
'employee_id' => $this->request->getPost('employee_id') != '' ? $this->request->getPost('employee_id', FILTER_SANITIZE_NUMBER_INT) : null,
|
|
'comment' => $this->request->getPost('comment', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
|
|
'invoice_number' => $this->request->getPost('invoice_number') != '' ? $this->request->getPost('invoice_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS) : null
|
|
];
|
|
|
|
// In order to maintain tradition the only element that can change on prior payments is the payment type
|
|
$amount_tendered = 0;
|
|
$number_of_payments = $this->request->getPost('number_of_payments', FILTER_SANITIZE_NUMBER_INT);
|
|
for ($i = 0; $i < $number_of_payments; ++$i) {
|
|
$payment_id = $this->request->getPost("payment_id_$i", FILTER_SANITIZE_NUMBER_INT);
|
|
$payment_type = $this->request->getPost("payment_type_$i", FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
$payment_amount = parse_decimals($this->request->getPost("payment_amount_$i"));
|
|
$refund_type = $this->request->getPost("refund_type_$i", FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
$cash_refund = parse_decimals($this->request->getPost("refund_amount_$i"));
|
|
|
|
$cash_adjustment = $payment_type == lang('Sales.cash_adjustment') ? CASH_ADJUSTMENT_TRUE : CASH_ADJUSTMENT_FALSE;
|
|
|
|
if (!$cash_adjustment) {
|
|
$amount_tendered += $payment_amount - $cash_refund;
|
|
}
|
|
|
|
// Non-cash positive refund amounts
|
|
if (empty(strstr($refund_type, lang('Sales.cash'))) && $cash_refund > 0) { // TODO: This if and the one below can be combined.
|
|
// Change it to be a new negative payment (a "non-cash refund")
|
|
$payment_type = $refund_type;
|
|
$payment_amount = $payment_amount - $cash_refund;
|
|
$cash_refund = 0.00;
|
|
}
|
|
|
|
$sale_data['payments'][] = [
|
|
'payment_id' => $payment_id,
|
|
'payment_type' => $payment_type,
|
|
'payment_amount' => $payment_amount,
|
|
'cash_refund' => $cash_refund,
|
|
'cash_adjustment' => $cash_adjustment,
|
|
'employee_id' => $employee_id
|
|
];
|
|
}
|
|
|
|
$payment_id = NEW_ENTRY;
|
|
$payment_amount_new = $this->request->getPost('payment_amount_new');
|
|
$payment_type = $this->request->getPost('payment_type_new', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
|
|
if ($payment_type != PAYMENT_TYPE_UNASSIGNED && !empty($payment_amount_new)) {
|
|
$payment_amount = parse_decimals($payment_amount_new);
|
|
$cash_refund = 0;
|
|
if ($payment_type == lang('Sales.cash_adjustment')) {
|
|
$cash_adjustment = CASH_ADJUSTMENT_TRUE;
|
|
} else {
|
|
$cash_adjustment = CASH_ADJUSTMENT_FALSE;
|
|
$amount_tendered += $payment_amount;
|
|
$sale_info = $this->sale->get_info($sale_id)->getRowArray();
|
|
|
|
if ($amount_tendered > $sale_info['amount_due']) {
|
|
$cash_refund = $amount_tendered - $sale_info['amount_due'];
|
|
}
|
|
}
|
|
|
|
$sale_data['payments'][] = [
|
|
'payment_id' => $payment_id,
|
|
'payment_type' => $payment_type,
|
|
'payment_amount' => $payment_amount,
|
|
'cash_refund' => $cash_refund,
|
|
'cash_adjustment' => $cash_adjustment,
|
|
'employee_id' => $employee_id
|
|
];
|
|
}
|
|
|
|
$inventory->update('POS ' . $sale_id, ['trans_date' => $sale_time]); // TODO: Reflection Exception
|
|
if ($this->sale->update($sale_id, $sale_data)) {
|
|
return $this->response->setJSON(['success' => true, 'message' => lang('Sales.successfully_updated'), 'id' => $sale_id]);
|
|
} else {
|
|
return $this->response->setJSON(['success' => false, 'message' => lang('Sales.unsuccessfully_updated'), 'id' => $sale_id]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is used to cancel a suspended pos sale, quote.
|
|
* Completed sales (POS Sales or Invoiced Sales) can not be removed from the system
|
|
* Work orders can be canceled but are not physically removed from the sales history.
|
|
* Used in app/Views/sales/register.php
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws ReflectionException
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postCancel(): ResponseInterface|string
|
|
{
|
|
$sale_id = $this->sale_lib->get_sale_id();
|
|
if ($sale_id != NEW_ENTRY && $sale_id != '') {
|
|
$sale_type = $this->sale_lib->get_sale_type();
|
|
|
|
if ($this->config['dinner_table_enable']) {
|
|
$dinner_table = $this->sale_lib->get_dinner_table();
|
|
$this->dinner_table->release($dinner_table);
|
|
}
|
|
|
|
if ($sale_type == SALE_TYPE_WORK_ORDER) {
|
|
$this->sale->update_sale_status($sale_id, CANCELED);
|
|
} else {
|
|
$this->sale->delete($sale_id);
|
|
$this->session->set('sale_id', NEW_ENTRY);
|
|
}
|
|
} else {
|
|
$this->sale_lib->remove_temp_items();
|
|
}
|
|
|
|
$this->sale_lib->clear_all();
|
|
return $this->_reload();
|
|
}
|
|
|
|
/**
|
|
* Discards the suspended sale. Used in app/Views/sales/quote.php
|
|
*
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getDiscardSuspendedSale(): ResponseInterface|string
|
|
{
|
|
$suspended_id = $this->sale_lib->get_suspended_id();
|
|
$this->sale_lib->clear_all();
|
|
$this->sale->delete_suspended_sale($suspended_id);
|
|
return $this->_reload();
|
|
}
|
|
|
|
/**
|
|
* Suspend the current sale.
|
|
* If the current sale is already suspended then update the existing suspended sale otherwise create
|
|
* it as a new suspended sale. Used in app/Views/sales/register.php
|
|
*
|
|
* @return ResponseInterface|string
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postSuspend(): ResponseInterface|string
|
|
{
|
|
$sale_id = $this->sale_lib->get_sale_id();
|
|
$dinner_table = $this->sale_lib->get_dinner_table();
|
|
$cart = $this->sale_lib->get_cart();
|
|
$payments = $this->sale_lib->get_payments();
|
|
$employee_id = $this->employee->get_logged_in_employee_info()->person_id;
|
|
$customer_id = $this->sale_lib->get_customer();
|
|
$invoice_number = $this->sale_lib->get_invoice_number();
|
|
$work_order_number = $this->sale_lib->get_work_order_number();
|
|
$quote_number = $this->sale_lib->get_quote_number();
|
|
$sale_type = $this->sale_lib->get_sale_type();
|
|
|
|
if ($sale_type == '') {
|
|
$sale_type = SALE_TYPE_POS;
|
|
}
|
|
|
|
$comment = $this->sale_lib->get_comment();
|
|
$sale_status = SUSPENDED;
|
|
|
|
$data = [];
|
|
$sales_taxes = [[], []];
|
|
|
|
if ($this->sale->save_value($sale_id, $sale_status, $cart, $customer_id, $employee_id, $comment, $invoice_number, $work_order_number, $quote_number, $sale_type, $payments, $dinner_table, $sales_taxes) == '-1') {
|
|
$data['error'] = lang('Sales.unsuccessfully_suspended_sale');
|
|
} else {
|
|
$data['success'] = lang('Sales.successfully_suspended_sale');
|
|
}
|
|
|
|
$this->sale_lib->clear_all();
|
|
|
|
return $this->_reload($data);
|
|
}
|
|
|
|
/**
|
|
* List suspended sales
|
|
* @return string
|
|
*/
|
|
public function getSuspended(): string
|
|
{
|
|
$data = [];
|
|
$customer_id = $this->sale_lib->get_customer();
|
|
$data['suspended_sales'] = $this->sale->get_all_suspended($customer_id);
|
|
return view('sales/suspended', $data);
|
|
}
|
|
|
|
/**
|
|
* Unsuspended sales are now left in the tables and are only removed
|
|
* when they are intentionally cancelled. Used in app/Views/sales/suspended.php.
|
|
*
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postUnsuspend(): ResponseInterface|string
|
|
{
|
|
$sale_id = $this->request->getPost('suspended_sale_id', FILTER_SANITIZE_NUMBER_INT);
|
|
$this->sale_lib->clear_all();
|
|
|
|
if ($sale_id > 0) {
|
|
$this->sale_lib->copy_entire_sale($sale_id);
|
|
}
|
|
|
|
// Set current register mode to reflect that of unsuspended order type
|
|
$this->change_register_mode($this->sale_lib->get_sale_type());
|
|
|
|
return $this->_reload();
|
|
}
|
|
|
|
/**
|
|
* Show Keyboard shortcut modal. Used in app/Views/sales/register.php
|
|
*
|
|
* @return string
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function getSalesKeyboardHelp(): string
|
|
{
|
|
return view('sales/help');
|
|
}
|
|
|
|
/**
|
|
* Check the validity of an invoice number. Used in app/Views/sales/form.php.
|
|
*
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postCheckInvoiceNumber(): ResponseInterface
|
|
{
|
|
$sale_id = $this->request->getPost('sale_id', FILTER_SANITIZE_NUMBER_INT);
|
|
$invoice_number = $this->request->getPost('invoice_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
$exists = !empty($invoice_number) && $this->sale->check_invoice_number_exists($invoice_number, $sale_id);
|
|
return $this->response->setJSON(!$exists ? 'true' : 'false');
|
|
}
|
|
|
|
/**
|
|
* @param array $cart
|
|
* @return array
|
|
*/
|
|
public function get_filtered(array $cart): array
|
|
{
|
|
$filtered_cart = [];
|
|
foreach ($cart as $id => $item) {
|
|
if ($item['print_option'] == PRINT_ALL) // Always include
|
|
{
|
|
$filtered_cart[$id] = $item;
|
|
} elseif ($item['print_option'] == PRINT_PRICED && $item['price'] != 0) // Include only if the price is not zero
|
|
{
|
|
$filtered_cart[$id] = $item;
|
|
}
|
|
// print_option 2 is never included
|
|
}
|
|
|
|
return $filtered_cart;
|
|
}
|
|
|
|
/**
|
|
* Update the item number in the register. Used in app/Views/sales/register.php
|
|
*
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postChangeItemNumber(): ResponseInterface
|
|
{
|
|
$item_id = $this->request->getPost('item_id', FILTER_SANITIZE_NUMBER_INT);
|
|
$item_number = $this->request->getPost('item_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
$this->item->update_item_number($item_id, $item_number);
|
|
$cart = $this->sale_lib->get_cart();
|
|
$x = $this->search_cart_for_item_id($item_id, $cart);
|
|
if ($x != null) {
|
|
$cart[$x]['item_number'] = $item_number;
|
|
}
|
|
$this->sale_lib->set_cart($cart);
|
|
}
|
|
|
|
/**
|
|
* Change a given item name. Used in app/Views/sales/register.php.
|
|
*
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postChangeItemName(): ResponseInterface
|
|
{
|
|
$item_id = $this->request->getPost('item_id', FILTER_SANITIZE_NUMBER_INT);
|
|
$name = $this->request->getPost('item_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
|
|
$this->item->update_item_name($item_id, $name);
|
|
|
|
$cart = $this->sale_lib->get_cart();
|
|
$x = $this->search_cart_for_item_id($item_id, $cart);
|
|
|
|
if ($x != null) {
|
|
$cart[$x]['name'] = $name;
|
|
}
|
|
|
|
$this->sale_lib->set_cart($cart);
|
|
}
|
|
|
|
/**
|
|
* Update the given item description. Used in app/Views/sales/register.php
|
|
*
|
|
* @return ResponseInterface
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function postChangeItemDescription(): ResponseInterface
|
|
{
|
|
$item_id = $this->request->getPost('item_id', FILTER_SANITIZE_NUMBER_INT);
|
|
$description = $this->request->getPost('item_description', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
|
|
|
|
$this->item->update_item_description($item_id, $description);
|
|
|
|
$cart = $this->sale_lib->get_cart();
|
|
$x = $this->search_cart_for_item_id($item_id, $cart);
|
|
|
|
if ($x != null) {
|
|
$cart[$x]['description'] = $description;
|
|
}
|
|
|
|
$this->sale_lib->set_cart($cart);
|
|
}
|
|
|
|
/**
|
|
* @param int $search_item_id
|
|
* @param array $shopping_cart
|
|
* @return int|string|null
|
|
*/
|
|
public function search_cart_for_item_id(int $search_item_id, array $shopping_cart): int|string|null
|
|
{
|
|
foreach ($shopping_cart as $key => $val) {
|
|
if ($val['item_id'] === $search_item_id) {
|
|
return $key;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|