Compare commits

..

3 Commits

Author SHA1 Message Date
andriux1990
b64a93e483 Merging the postRequisitionComplete and complete functions 2025-01-18 20:58:26 -06:00
andriux1990
7f073bce8f Requisition (transfers) and payment methods in Receivings 2025-01-18 16:39:25 -06:00
jekkos
6ab72ce5c2 Fix for requisitions (#4147) 2025-01-15 23:47:32 +01:00
33 changed files with 4662 additions and 957 deletions

26
LICENSE
View File

@@ -1,30 +1,30 @@
MIT License
Copyright (c) 2013-2025 jekkos
Copyright (c) 2017-2025 objecttothis
Copyright (c) 2017-2024 Steve Ireland
Copyright (c) 2017-2025 odiea
Copyright (c) 2018-2024 WebShells
Copyright (c) 2021-2025 BudsieBuds
Copyright (c) 2013-2023 jekkos
Copyright (c) 2015-2023 FrancescoUK (aka daN4cat)
Copyright (c) 2015-2022 Aamir Shahzad (aka asakpke), RoshanTech, eSite.pk
Copyright (c) 2019-2020 Andriux1990
Copyright (c) 2018-2019 Erasto Marroquin (aka Erastus)
Copyright (c) 2017-2023 Steve Ireland
Copyright (c) 2017-2023 objecttothis
Copyright (c) 2017-2023 odiea
Copyright (c) 2017-2023 WebShells
Copyright (c) 2020-2021 Andriux1990
Copyright (c) 2021 BudsieBuds
Copyright (c) 2019 Loyd Jayme (aka loydjayme25)
Copyright (c) 2018 Erasto Marroquin (aka Erastus)
Copyright (c) 2018 Nathan Sas (aka nathanzky)
Copyright (c) 2018 Emilio Silva (aka emi-silva)
Copyright (c) 2016-2017 Ramkrishna Mondal (aka RamkrishnaMondal)
Copyright (c) 2016-2017 Jorge Colmenarez (aka jlctmaster), frontuari.com
Copyright (c) 2016-2017 Jesus Guerrero Botella (aka i92guboj)
Copyright (c) 2017 Jesus Guerrero Botella (aka i92guboj)
Copyright (c) 2017 Deep Shah (aka deepshah)
Copyright (c) 2017 Joshua Fernandez (aka joshua1234511)
Copyright (c) 2017 asadjaved63
Copyright (c) 2016-2017 Ramkrishna Mondal (aka RamkrishnaMondal)
Copyright (c) 2016-2017 Jorge Colmenarez (aka jlctmaster), frontuari.com
Copyright (c) 2016 Rinaldy@dbarber (aka rnld26)
Copyright (c) 2015-2022 Aamir Shahzad (aka asakpke), RoshanTech, eSite.pk
Copyright (c) 2015 Toni Haryanto (aka yllumi)
Copyright (c) 2012-2014 pappastech
Copyright (c) 2013 Rob Garrison
Copyright (c) 2013 Parq
Copyright (c) 2013 Ramel
Copyright (c) 2012-2014 pappastech
Copyright (c) 2012 Alain
Permission is hereby granted, free of charge, to any person obtaining a copy

View File

@@ -137,7 +137,7 @@ Any person or company found breaching the license agreement might find a bunch o
## 🙏 Credits
| <div align="center">DigitalOcean</div> | <div align="center">JetBrains</div> | <div align="center">Travis CI</div> |
| --- | --- | --- |
| <div align="center"><a href="https://www.digitalocean.com?utm_medium=opensource&utm_source=opensourcepos" target="_blank"><img src="https://github.com/user-attachments/assets/fbbf7433-ed35-407d-8946-fd03d236d350" alt="DigitalOcean Logo" height="50"></a></div> | <div align="center"><a href="https://www.jetbrains.com/idea/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/187f9bbe-4484-475c-9b58-5e5d5f931f09" alt="IntelliJ IDEA Logo" height="50"></a></div> | <div align="center"><a href="https://www.travis-ci.com/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/71cc2b44-83af-4510-a543-6358285f43c6" alt="Travis CI Logo" height="50"></a></div> |
| Many thanks to [DigitalOcean](https://www.digitalocean.com) for providing the project with hosting credits. | Many thanks to [JetBrains](https://www.jetbrains.com/) for providing a free license of [IntelliJ IDEA](https://www.jetbrains.com/idea/) to kindly support the development of OSPOS. | Many thanks to [Travis CI](https://www.travis-ci.com/) for providing a free continuous integration service for open source projects. |
| <div align="center">DigitalOcean</div> | <div align="center">JetBrains</div> | <div align="center">Travis CI</div> |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- | --- |
| <div align="center"><a href="https://www.digitalocean.com?utm_medium=opensource&utm_source=opensourcepos" target="_blank"><img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" alt="DigitalOcean Logo" height="50"></a></div> | <div align="center"><a href="https://www.jetbrains.com/idea/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/187f9bbe-4484-475c-9b58-5e5d5f931f09" alt="IntelliJ IDEA Logo" height="50"></a></div> | <div align="center"><a href="https://www.travis-ci.com/" target="_blank"><img src="https://github.com/opensourcepos/opensourcepos/assets/12870258/71cc2b44-83af-4510-a543-6358285f43c6" alt="Travis CI Logo" height="50"></a></div> |
| Many thanks to [Digital Ocean](https://www.digitalocean.com) for providing the project with hosting credits | Many thanks to [JetBrains](https://www.jetbrains.com/) for providing a free license of [IntelliJ IDEA](https://www.jetbrains.com/idea/) to kindly support the development of OSPOS. | Many thanks to [Travis CI](https://www.travis-ci.com/) for providing a free continuous integration service for open source projects. |

View File

@@ -109,7 +109,7 @@ class OSPOSRules
private function installation_check(): bool
{
$installed_extensions = implode(', ', get_loaded_extensions());
$required_extensions = ['bcmath', 'intl', 'gd', 'openssl', 'mbstring', 'curl', 'xml', 'json'];
$required_extensions = ['bcmath', 'intl', 'gd', 'openssl', 'mbstring', 'curl'];
$pattern = '/';
foreach($required_extensions as $extension)
@@ -140,6 +140,22 @@ class OSPOSRules
*/
public function decimal_locale(string $candidate, ?string &$error = null): bool
{
return parse_decimals($candidate) !== false;
$validation = Services::validation();
$validation->setRules([
'candidate' => 'decimal'
]);
$data = [
'candidate' => $candidate
];
if (!$validation->run($data))
{
$error = $validation->getErrors();
return false;
}
return true;
}
}

View File

@@ -82,7 +82,7 @@ class Config extends Secure_Controller
$license[$i]['title'] = 'Open Source Point Of Sale ' . config('App')->application_version;
if (file_exists('license/LICENSE')) {
$license[$i]['text'] = file_get_contents('license/LICENSE', false, null, 0, 3000);
$license[$i]['text'] = file_get_contents('license/LICENSE', false, null, 0, 2000);
} else {
$license[$i]['text'] = 'LICENSE file must be in OSPOS license directory. You are not allowed to use OSPOS application until the distribution copy of LICENSE file is present.';
}
@@ -139,13 +139,13 @@ class Config extends Secure_Controller
$license[$i]['text'] .= "$val3 ";
}
$license[$i]['text'] .= "\n";
$license[$i]['text'] .= '\n';
} else {
$license[$i]['text'] .= "$key2: $val2\n";
}
}
$license[$i]['text'] .= "\n";
$license[$i]['text'] .= '\n';
} else {
$license[$i]['text'] .= "$key1: $val1\n";
}

View File

@@ -207,7 +207,7 @@ class Items extends Secure_Controller
*/
public function getSuggestLowSell(): void
{
$suggestions = $this->item->get_low_sell_suggestions($this->request->getPostGet('name'));
$suggestions = $this->item->get_low_sell_suggestions($this->request->getPo1stGet('name'));
echo json_encode($suggestions);
}

View File

@@ -213,10 +213,13 @@ class Receivings extends Secure_Controller
$price = parse_decimals($this->request->getPost('price'));
$quantity = parse_quantity($this->request->getPost('quantity'));
$discount = parse_decimals($this->request->getPost('discount'));
$raw_receiving_quantity = parse_quantity($this->request->getPost('receiving_quantity'));
$description = $this->request->getPost('description', FILTER_SANITIZE_FULL_SPECIAL_CHARS); //TODO: Duplicated code
$serialnumber = $this->request->getPost('serialnumber', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? '';
$price = filter_var($price, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
$quantity = filter_var($quantity, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
$discount_type = $this->request->getPost('discount_type', FILTER_SANITIZE_NUMBER_INT);
$discount = $discount_type
? parse_quantity(filter_var($this->request->getPost('discount'), FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION))
@@ -263,6 +266,7 @@ class Receivings extends Secure_Controller
$data['selected_supplier_name'] = !empty($receiving_info['supplier_id']) ? $receiving_info['company_name'] : '';
$data['selected_supplier_id'] = $receiving_info['supplier_id'];
$data['receiving_info'] = $receiving_info;
$balance_due = round($receiving_info['amount_due'] - $receiving_info['amount_tendered'] + $receiving_info['cash_refund'], totals_decimals(), PHP_ROUND_HALF_UP);
echo view('receivings/form', $data);
}
@@ -324,22 +328,26 @@ class Receivings extends Secure_Controller
*/
public function postComplete(): void
{
$amount_tendered = parse_decimals($this->request->getPost('amount_tendered'));
$data = [];
$data['cart'] = $this->receiving_lib->get_cart();
$data['total'] = $this->receiving_lib->get_total();
$data['transaction_time'] = to_datetime(time());
$data['transaction_time'] = date('Y-m-d H:i:s'); // Fecha y hora actuales
$data['mode'] = $this->receiving_lib->get_mode();
$data['comment'] = $this->receiving_lib->get_comment();
$data['reference'] = $this->receiving_lib->get_reference();
$data['term_days'] = $this->receiving_lib->get_term_days();
$data['payment_type'] = $this->request->getPost('payment_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$data['payments'] = $this->receiving_lib->get_payments(); // Obtener pagos del carrito
$data['show_stock_locations'] = $this->stock_location->show_locations('receivings');
$data['stock_location'] = $this->receiving_lib->get_stock_source();
if($this->request->getPost('amount_tendered') != null)
{
$data['amount_tendered'] = parse_decimals($this->request->getPost('amount_tendered'));
$data['amount_change'] = to_currency($data['amount_tendered'] - $data['total']);
if ($this->request->getPost('amount_tendered') != null) {
$data['amount_tendered'] = filter_var($amount_tendered, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
$data['amount_change'] = $data['amount_tendered'] - $data['total'];
} else {
$data['amount_change'] = 0;
}
$employee_id = $this->employee->get_logged_in_employee_info()->person_id;
@@ -347,40 +355,62 @@ class Receivings extends Secure_Controller
$data['employee'] = $employee_info->first_name . ' ' . $employee_info->last_name;
$supplier_id = $this->receiving_lib->get_supplier();
if($supplier_id != -1)
{
if ($supplier_id != -1) {
$supplier_info = $this->supplier->get_info($supplier_id);
$data['supplier'] = $supplier_info->company_name; //TODO: duplicated code
$data['supplier'] = $supplier_info->company_name;
$data['first_name'] = $supplier_info->first_name;
$data['last_name'] = $supplier_info->last_name;
$data['supplier_email'] = $supplier_info->email;
$data['supplier_address'] = $supplier_info->address_1;
if(!empty($supplier_info->zip) or !empty($supplier_info->city))
{
if (!empty($supplier_info->zip) || !empty($supplier_info->city)) {
$data['supplier_location'] = $supplier_info->zip . ' ' . $supplier_info->city;
}
else
{
} else {
$data['supplier_location'] = '';
}
}
//SAVE receiving to database
$data['receiving_id'] = 'RECV ' . $this->receiving->save_value($data['cart'], $supplier_id, $employee_id, $data['comment'], $data['reference'], $data['payment_type'], $data['stock_location']);
// Guardar el registro de compra en la base de datos
$receiving_id = $this->receiving->save_value(
$data['cart'],
$supplier_id,
$employee_id,
$data['comment'],
$data['reference'],
$data['term_days'],
$data['payment_type']
);
if($data['receiving_id'] == 'RECV -1')
{
if ($receiving_id == -1) {
$data['error_message'] = lang('Receivings.transaction_failed');
}
else
{
$data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['receiving_id']);
} else {
$data['receiving_id'] = $receiving_id;
// Calcular el cambio (cash_refund)
$total_payments = array_sum(array_column($data['payments'], 'payment_amount'));
$cash_refund = max(0, $total_payments - $data['total']);
// Guardar los pagos en la base de datos
foreach ($data['payments'] as $payment) {
$this->receiving->save_payment([
'receiving_id' => $receiving_id, // Utiliza el ID generado
'payment_type' => $payment['payment_type'],
'payment_amount' => $payment['payment_amount'],
'cash_refund' => ($payment['payment_type'] === lang('Sales.cash')) ? $cash_refund : 0,
'employee_id' => $employee_id,
'payment_time' => $data['transaction_time'], // Fecha y hora actuales
]);
}
$data['barcode'] = $this->barcode_lib->generate_receipt_barcode($receiving_id);
}
$data['print_after_sale'] = $this->receiving_lib->is_print_after_sale();
echo view("receivings/receipt",$data);
echo view("receivings/receipt", $data);
// Limpiar pagos y otros datos
$this->receiving_lib->clear_payments(); // Limpia los pagos
$this->receiving_lib->clear_all();
}
@@ -392,23 +422,58 @@ class Receivings extends Secure_Controller
*/
public function postRequisitionComplete(): void
{
if($this->receiving_lib->get_stock_source() != $this->receiving_lib->get_stock_destination())
{
foreach($this->receiving_lib->get_cart() as $item)
{
$this->receiving_lib->delete_item($item['line']);
$this->receiving_lib->add_item($item['item_id'], $item['quantity'], $this->receiving_lib->get_stock_destination(), $item['discount_type']);
$this->receiving_lib->add_item($item['item_id'], -$item['quantity'], $this->receiving_lib->get_stock_source(), $item['discount_type']);
}
$data = [];
$this->postComplete();
}
else
{
$data['error'] = lang('Receivings.error_requisition');
// Check if stock locations are different
if ($this->receiving_lib->get_stock_source() != $this->receiving_lib->get_stock_destination()) {
// Process items in the cart
foreach ($this->receiving_lib->get_cart() as $item) {
$this->receiving_lib->delete_item($item['line']);
$this->receiving_lib->add_item($item['item_id'], $item['quantity'], $this->receiving_lib->get_stock_destination(), $item['discount_type']);
$this->receiving_lib->add_item($item['item_id'], -$item['quantity'], $this->receiving_lib->get_stock_source(), $item['discount_type']);
}
$this->_reload($data); //TODO: Hungarian notation
}
// Prepare data to complete the requisition
$data['cart'] = $this->receiving_lib->get_cart();
$data['total'] = $this->receiving_lib->get_total();
$data['transaction_time'] = to_datetime(time());
$data['mode'] = $this->receiving_lib->get_mode();
$data['comment'] = $this->receiving_lib->get_comment();
$data['term_days'] = $this->receiving_lib->get_term_days();
$data['reference'] = $this->receiving_lib->get_reference();
$data['show_stock_locations'] = $this->stock_location->show_locations('receivings');
$data['stock_location_source'] = $this->receiving_lib->get_stock_source();
$data['stock_location_destination'] = $this->receiving_lib->get_stock_destination();
$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 . ' ' . $employee_info->last_name;
// Save the requisition in the database
$data['receiving_id'] = 'REQ ' . $this->receiving->save_requisition(
$data['cart'],
$employee_id,
$data['comment'],
$data['reference'],
$data['stock_location_source'],
$data['stock_location_destination']
);
if ($data['receiving_id'] == 'REQ -1') {
$data['error_message'] = lang('Receivings.transaction_failed');
} else {
$data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['receiving_id']);
}
$data['print_after_sale'] = $this->receiving_lib->is_print_after_sale();
echo view("receivings/receipt", $data);
$this->receiving_lib->clear_all();
} else {
$data['error'] = lang('Receivings.error_requisition');
$this->_reload($data);
}
}
/**
@@ -464,7 +529,7 @@ class Receivings extends Secure_Controller
* @param array $data
* @return void
*/
private function _reload(array $data = []): void //TODO: Hungarian notation
private function _reload(array $data = []): void
{
$data['cart'] = $this->receiving_lib->get_cart();
$data['modes'] = ['receive' => lang('Receivings.receiving'), 'return' => lang('Receivings.return')];
@@ -481,15 +546,26 @@ class Receivings extends Secure_Controller
$data['total'] = $this->receiving_lib->get_total();
$data['items_module_allowed'] = $this->employee->has_grant('items', $this->employee->get_logged_in_employee_info()->person_id);
$data['comment'] = $this->receiving_lib->get_comment();
$data['term_days'] = $this->receiving_lib->get_term_days();
$data['reference'] = $this->receiving_lib->get_reference();
$data['payment_options'] = $this->receiving->get_payment_options();
$data['selected_payment_type'] = $this->receiving_lib->get_payment_type();
$data['amount_due'] = $this->receiving_lib->get_total() - $this->receiving_lib->get_payments_total();
// Agregar pagos al arreglo de datos
$data['payments'] = $this->receiving_lib->get_payments();
// Inicializar tabindex
$data['tabindex'] = 0;
$supplier_id = $this->receiving_lib->get_supplier();
if($supplier_id != -1) //TODO: Duplicated Code... replace -1 with a constant
if($supplier_id != -1)
{
$supplier_info = $this->supplier->get_info($supplier_id);
$data['supplier'] = $supplier_info->company_name;
$data['avatar'] = $supplier_info->avatar;
$data['gender'] = $supplier_info->gender;
$data['first_name'] = $supplier_info->first_name;
$data['last_name'] = $supplier_info->last_name;
$data['supplier_email'] = $supplier_info->email;
@@ -512,9 +588,9 @@ class Receivings extends Secure_Controller
/**
* @throws ReflectionException
*/
public function save(int $receiving_id = -1): void //TODO: Replace -1 with a constant
public function save(int $receiving_id = -1): void
{
$newdate = $this->request->getPost('date', FILTER_SANITIZE_FULL_SPECIAL_CHARS); //TODO: newdate does not follow naming conventions
$newdate = $this->request->getPost('date', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$date_formatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $newdate);
$receiving_time = $date_formatter->format('Y-m-d H:i:s');
@@ -554,8 +630,77 @@ class Receivings extends Secure_Controller
*/
public function postCancelReceiving(): void
{
// Limpiar los pagos
$this->receiving_lib->clear_payments();
// Limpiar todos los datos relacionados con la transacción
$this->receiving_lib->clear_all();
$this->_reload(); //TODO: Hungarian Notation
// Recargar la vista
$this->_reload();
}
public function postAddPayment(): void
{
$data = [];
$payment_type = $this->request->getPost('payment_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
// Validar el tipo de pago
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')) {
$amount_tendered = parse_decimals($this->request->getPost('amount_tendered'));
$giftcard_num = filter_var($amount_tendered, FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_ALLOW_FRACTION);
$payments = $this->receiving_lib->get_payments();
$payment_type = $payment_type . ':' . $giftcard_num;
$current_payments_with_giftcard = isset($payments[$payment_type]) ? $payments[$payment_type]['payment_amount'] : 0;
$giftcard = model(Giftcard::class);
$cur_giftcard_value = $giftcard->get_giftcard_value($giftcard_num);
if (($cur_giftcard_value - $current_payments_with_giftcard) <= 0) {
$data['error'] = lang('Giftcards.remaining_balance', [$giftcard_num, $cur_giftcard_value]);
} else {
$amount_tendered = min($this->receiving_lib->get_amount_due(), $cur_giftcard_value);
$this->receiving_lib->add_payment($payment_type, $amount_tendered);
}
} elseif ($payment_type === lang('Sales.cash')) {
$raw_amount_tendered = parse_decimals($this->request->getPost('amount_tendered'));
$amount_tendered = filter_var($raw_amount_tendered, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
$this->receiving_lib->add_payment($payment_type, $amount_tendered);
} else {
$raw_amount_tendered = parse_decimals($this->request->getPost('amount_tendered'));
$amount_tendered = filter_var($raw_amount_tendered, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
$this->receiving_lib->add_payment($payment_type, $amount_tendered);
}
}
$this->_reload($data);
}
/**
* Multiple Payments. Used in app/Views/sales/register.php
*
* @param string $payment_id
* @return void
* @noinspection PhpUnused
*/
public function getDeletePayment(string $payment_id): void
{
$this->receiving_lib->delete_payment($payment_id);
$this->_reload();
}
}

View File

@@ -606,7 +606,7 @@ class Sales extends Secure_Controller
}
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))
if(!$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');
}
@@ -984,21 +984,21 @@ class Sales extends Secure_Controller
if(!empty($sale_data['customer_email']))
{
$to = $sale_data['customer_email'];
$number = array_key_exists($type."_number", $sale_data) ? $sale_data[$type."_number"] : "";
$number = $sale_data[$type."_number"];
$subject = lang('Sales.' . $type) . ' ' . $number;
$text = $this->config['invoice_email_message'];
$tokens = [
new Token_invoice_sequence($sale_data['invoice_number']),
new Token_invoice_count('POS ' . $sale_data['sale_id']),
new Token_customer((array)$sale_data)
new Token_customer((object)$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);
$html = $view->render("sales/$type" . '_email', $sale_data);
// load pdf helper
helper (['dompdf', 'file']);
@@ -1040,7 +1040,7 @@ class Sales extends Secure_Controller
$subject = lang('Sales.receipt');
$view = Services::renderer();
$text = $view->setData($sale_data)->render('sales/receipt_email');
$text = $view->render('sales/receipt_email', $sale_data);
$result = $this->email_lib->sendEmail($to, $subject, $text);

View File

@@ -26,26 +26,27 @@ class Email_lib
$this->config = config(OSPOS::class)->settings;
$encrypter = Services::encrypter();
$smtp_pass = $this->config['smtp_pass'];
if(!empty($smtp_pass))
{
$smtp_pass = $encrypter->decrypt($smtp_pass);
$smtp_pass = $encrypter->decrypt($smtp_pass);
}
$email_config = [
'mailType' => 'html',
'userAgent' => 'OSPOS',
'validate' => true,
'protocol' => $this->config['protocol'],
'mailPath' => $this->config['mailpath'],
'SMTPHost' => $this->config['smtp_host'],
'SMTPUser' => $this->config['smtp_user'],
'SMTPPass' => $smtp_pass,
'SMTPPort' => (int)$this->config['smtp_port'],
'SMTPTimeout' => (int)$this->config['smtp_timeout'],
'SMTPCrypto' => $this->config['smtp_crypto']
'mailtype' => 'html',
'useragent' => 'OSPOS',
'validate' => true,
'protocol' => $this->config['protocol'],
'mailpath' => $this->config['mailpath'],
'smtp_host' => $this->config['smtp_host'],
'smtp_user' => $this->config['smtp_user'],
'smtp_pass' => $smtp_pass,
'smtp_port' => $this->config['smtp_port'],
'smtp_timeout' => $this->config['smtp_timeout'],
'smtp_crypto' => $this->config['smtp_crypto']
];
$this->email->initialize($email_config);
}

View File

@@ -6,11 +6,13 @@ use App\Models\Attribute;
use App\Models\Item;
use App\Models\Item_kit_items;
use App\Models\Item_quantity;
use App\Models\Enums\Rounding_mode;
use App\Models\Receiving;
use App\Models\Stock_location;
use CodeIgniter\Session\Session;
use Config\OSPOS;
use ReflectionException;
/**
* Receiving library
@@ -26,6 +28,7 @@ class Receiving_lib
private Receiving $receiving;
private Stock_location $stock_location;
private Session $session;
private array $config;
public function __construct()
{
@@ -35,6 +38,7 @@ class Receiving_lib
$this->item_quantity = model(Item_quantity::class);
$this->receiving = model(Receiving::class);
$this->stock_location = model(Stock_location::class);
$this->config = config(OSPOS::class)->settings;
$this->session = session();
}
@@ -194,6 +198,31 @@ class Receiving_lib
$this->session->remove('recv_reference');
}
/**
* @return string
*/
public function get_term_days(): string
{
return $this->session->get('recv_term_days') ?? '';
}
/**
* @param string $term_days
* @return void
*/
public function set_term_days(string $term_days): void
{
$this->session->set('recv_term_days', $term_days);
}
/**
* @return void
*/
public function clear_term_days(): void //TODO: This function verb is inconsistent from the others. Consider refactoring to remove_reference()
{
$this->session->remove('recv_term_days');
}
/**
* @return bool
*/
@@ -246,7 +275,7 @@ class Receiving_lib
* @param string $stock_destination
* @return void
*/
public function set_stock_destination(?string $stock_destination): void
public function set_stock_destination(string $stock_destination): void
{
$this->session->set('recv_stock_destination', $stock_destination);
}
@@ -516,6 +545,7 @@ class Receiving_lib
$this->remove_supplier();
$this->clear_comment();
$this->clear_reference();
$this->clear_term_days();
}
/**
@@ -554,4 +584,200 @@ class Receiving_lib
return $total;
}
/**
* @param string $payment_type
* @return void
*/
public function set_payment_type(string $payment_type): void
{
$this->session->set('payment_type', $payment_type);
}
/**
* @return string|null
*/
public function get_payment_type(): ?string
{
return $this->session->get('payment_type');
}
public function get_total_due(): float
{
return $this->get_total() - $this->get_payments_total();
}
public function get_payments_total(): string
{
$subtotal = '0.0';
$cash_mode_eligible = CASH_MODE_TRUE;
foreach($this->get_payments() as $payments)
{
if(!$payments['cash_adjustment'])
{
$subtotal = bcadd($payments['payment_amount'], $subtotal);
}
if(lang('Sales.cash') != $payments['payment_type'] && lang('Sales.cash_adjustment') != $payments['payment_type'])
{
$cash_mode_eligible = CASH_MODE_FALSE;
}
}
if($cash_mode_eligible && $this->session->get('cash_rounding'))
{
$this->session->set('cash_mode', CASH_MODE_TRUE);
}
return $subtotal;
}
/**
* Multiple Payments
*/
public function get_payments(): array
{
if(!$this->session->get('sales_payments'))
{
$this->set_payments ([]);
}
return $this->session->get('sales_payments');
}
/**
* Multiple Payments
*/
public function set_payments(array $payments_data): void
{
$this->session->set('sales_payments', $payments_data);
}
/**
* Adds a new payment to the payments array or updates an existing one.
* It will also disable cash_mode if a non-qualifying payment type is added.
* @param string $payment_id
* @param string $payment_amount
* @param int $cash_adjustment
*/
public function add_payment(string $payment_id, string $payment_amount, int $cash_adjustment = CASH_ADJUSTMENT_FALSE): void
{
$payments = $this->get_payments();
if(isset($payments[$payment_id]))
{
//payment_method already exists, add to payment_amount
$payments[$payment_id]['payment_amount'] = bcadd($payments[$payment_id]['payment_amount'], $payment_amount);
}
else
{
//add to existing array
$payment = [
$payment_id => [
'payment_type' => $payment_id,
'payment_amount' => $payment_amount,
'cash_refund' => 0,
'cash_adjustment' => $cash_adjustment
]
];
$payments += $payment;
}
if($this->session->get('cash_mode'))
{
if($this->session->get('cash_rounding') && $payment_id != lang('Sales.cash') && $payment_id != lang('Sales.cash_adjustment'))
{
$this->session->set('cash_mode', CASH_MODE_FALSE);
}
}
$this->set_payments($payments);
}
/**
* Multiple Payments
*/
public function edit_payment(string $payment_id, float $payment_amount): bool
{
$payments = $this->get_payments();
if(isset($payments[$payment_id]))
{
$payments[$payment_id]['payment_type'] = $payment_id;
$payments[$payment_id]['payment_amount'] = $payment_amount;
$this->set_payments($payments);
return true;
}
return false;
}
/**
* Delete the selected payment from the payment array and if cash rounding is enabled
* and the payment type is one of the cash types then automatically delete the other
* @param string $payment_id
*/
public function delete_payment(string $payment_id): void
{
$payments = $this->get_payments();
$decoded_payment_id = urldecode($payment_id);
unset($payments[$decoded_payment_id]);
$cash_rounding = $this->reset_cash_rounding();
if($cash_rounding)
{
if($decoded_payment_id == lang('Sales.cash'))
{
unset($payments[lang('Sales.cash_adjustment')]);
}
if($decoded_payment_id == lang('Sales.cash_adjustment'))
{
unset($payments[lang('Sales.cash')]);
}
}
$this->set_payments($payments);
}
/**
* Determines if cash rounding should be a consideration for this site
* It also set resets the cash mode to disabled which will then be re-evaluated when
* retrieving payments.
*/
public function reset_cash_rounding(): int
{
$cash_rounding_code = $this->config['cash_rounding_code'];
if(cash_decimals() < totals_decimals() || $cash_rounding_code == Rounding_mode::HALF_FIVE)
{
$cash_rounding = 1;
}
else
{
$cash_rounding = 0;
}
$this->session->set('cash_rounding', $cash_rounding);
$this->session->set('cash_mode', CASH_MODE_FALSE);
return $cash_rounding;
}
/**
* @param string $total
* @return string
*/
public function check_for_cash_rounding(string $total): string
{
$cash_decimals = cash_decimals();
$cash_rounding_code = $this->config['cash_rounding_code'];
return Rounding_mode::round_number($cash_rounding_code, (float)$total, $cash_decimals);
}
public function clear_payments(): void
{
$this->set_payments([]);
}
}

View File

@@ -104,7 +104,7 @@ class Receiving extends Model
/**
* @throws ReflectionException
*/
public function save_value(array $items, int $supplier_id, int $employee_id, string $comment, string $reference, ?string $payment_type, int $receiving_id = NEW_ENTRY): int //TODO: $receiving_id gets overwritten before it's evaluated. It doesn't make sense to pass this here.
public function save_value(array $items, int $supplier_id, int $employee_id, string $comment, string $reference, string $term_days, string $payment_type, int $receiving_id = NEW_ENTRY): int //TODO: $receiving_id gets overwritten before it's evaluated. It doesn't make sense to pass this here.
{
$attribute = model(Attribute::class);
$inventory = model('Inventory');
@@ -123,7 +123,8 @@ class Receiving extends Model
'employee_id' => $employee_id,
'payment_type' => $payment_type,
'comment' => $comment,
'reference' => $reference
'reference' => $reference,
'term_days' => $term_days
];
//Run these queries as a transaction, we want to make sure we do all or nothing
@@ -187,6 +188,7 @@ class Receiving extends Model
$inventory->insert($inv_data, false);
$attribute->copy_attribute_links($item_data['item_id'], 'receiving_id', $receiving_id);
// $supplier = $supplier->get_info($supplier_id); //TODO: supplier is never used after this.
}
$this->db->transComplete();
@@ -194,6 +196,153 @@ class Receiving extends Model
return $this->db->transStatus() ? $receiving_id : -1;
}
/**
* Save a requisition to the `ospos_receivings` and `ospos_receivings_items` tables
* and update stock quantities in the respective locations.
*
* @throws ReflectionException
*/
public function save_requisition(array $items, int $employee_id, string $comment, string $reference, int $stock_source, int $stock_destination): int
{
$inventory = model('Inventory');
$item_quantity_model = model(Item_quantity::class);
if (count($items) == 0) {
return -1; // No items to save
}
// Prepare the receiving data
$receivings_data = [
'receiving_time' => date('Y-m-d H:i:s'),
'employee_id' => $employee_id,
'comment' => $comment,
'reference' => $reference,
'total' => 0, // This will be updated later
'amount_change' => 0,
'amount_tendered' => 0,
'operation_type' => 'Requisition',
];
// Run these queries as a transaction
$this->db->transStart();
// Insert into `ospos_receivings`
$builder = $this->db->table('ospos_receivings');
$builder->insert($receivings_data);
$receiving_id = $this->db->insertID();
if (!$receiving_id) {
log_message('error', 'Failed to insert requisition header: ' . print_r($receivings_data, true));
$this->db->transRollback();
return -1;
}
// Insert items into `ospos_receivings_items`
$builder = $this->db->table('ospos_receivings_items');
$total_cost = 0;
foreach ($items as $item_data) {
$receivings_items_data = [
'receiving_id' => $receiving_id,
'item_id' => $item_data['item_id'],
'line' => $item_data['line'],
'description' => $item_data['description'] ?? '',
'serialnumber' => $item_data['serialnumber'] ?? '',
'quantity_purchased' => $item_data['quantity'],
'item_cost_price' => $item_data['price'],
'item_unit_price' => $item_data['price'],
'discount' => $item_data['discount'] ?? 0,
'discount_type' => $item_data['discount_type'] ?? 0,
'item_location' => $item_data['item_location'] ?? 1,
'receiving_quantity' => $item_data['receiving_quantity'] ?? 1,
];
$builder->insert($receivings_items_data);
if (!$this->db->affectedRows()) {
log_message('error', 'Failed to insert requisition item: ' . print_r($receivings_items_data, true));
$this->db->transRollback();
return -1;
}
// Calculate the total cost
$total_cost += ($item_data['price'] * $item_data['quantity']) - ($item_data['discount'] ?? 0);
// Update stock quantities
$item_id = $item_data['item_id'] > 0 ? $item_data['item_id'] : 0;
$quantity = $item_data['quantity'] > 0 ? $item_data['quantity'] : 0;
if ($item_id === 0 || $quantity === 0) {
continue; // Skip if item or quantity is invalid
}
// Subtract from stock at the source
$source_quantity = $item_quantity_model->get_item_quantity($item_id, $stock_source);
$item_quantity_model->save_value([
'quantity' => $source_quantity->quantity - $quantity,
'item_id' => $item_id,
'location_id' => $stock_source,
], $item_id, $stock_source);
// Add to stock at the destination
$destination_quantity = $item_quantity_model->get_item_quantity($item_id, $stock_destination);
$item_quantity_model->save_value([
'quantity' => $destination_quantity->quantity + $quantity,
'item_id' => $item_id,
'location_id' => $stock_destination,
], $item_id, $stock_destination);
// Register inventory for the outgoing location (only if not already registered)
$recv_remarks = 'REQ ' . $receiving_id;
$inv_data_source = [
'trans_date' => date('Y-m-d H:i:s'),
'trans_items' => $item_id,
'trans_user' => $employee_id,
'trans_location' => $stock_source,
'trans_comment' => $recv_remarks . ' OUT',
'trans_inventory' => -$quantity,
];
$existing_source = $inventory->where([
'trans_items' => $item_id,
'trans_location' => $stock_source,
'trans_comment' => $recv_remarks . ' OUT'
])->countAllResults();
if ($existing_source === 0) {
$inventory->insert($inv_data_source, false);
}
// Register inventory for the incoming location (only if not already registered)
$inv_data_destination = [
'trans_date' => date('Y-m-d H:i:s'),
'trans_items' => $item_id,
'trans_user' => $employee_id,
'trans_location' => $stock_destination,
'trans_comment' => $recv_remarks . ' IN',
'trans_inventory' => $quantity,
];
$existing_destination = $inventory->where([
'trans_items' => $item_id,
'trans_location' => $stock_destination,
'trans_comment' => $recv_remarks . ' IN'
])->countAllResults();
if ($existing_destination === 0) {
$inventory->insert($inv_data_destination, false);
}
}
// Update the total cost in ospos_receivings
$this->db->table('ospos_receivings')
->where('receiving_id', $receiving_id)
->update(['total' => $total_cost]);
if (!$this->db->transComplete()) {
log_message('error', 'Transaction failed for requisition: ' . print_r($receivings_data, true));
return -1;
}
return $receiving_id;
}
/**
* @throws ReflectionException
@@ -363,4 +512,9 @@ class Receiving extends Model
$this->db->query($sql);
}
public function save_payment(array $payment_data): bool
{
return $this->db->table('ospos_receivings_payments')->insert($payment_data);
}
}

View File

@@ -11,13 +11,13 @@ use App\Models\Customer;
**/
class Token_customer extends Token
{
private array $customer_info;
private string $customer_info;
private Sale_lib $sale_lib;
/**
* @param string $customer_info
*/
public function __construct(array $customer_info = [])
public function __construct(string $customer_info = '')
{
parent::__construct();
$this->customer_info = $customer_info;

View File

@@ -42,7 +42,6 @@ use Config\OSPOS;
echo "&#187; OpenSSL: ", extension_loaded('openssl') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red">Disabled &#x2717</span>', '<br>';
echo "&#187; MBString: ", extension_loaded('mbstring') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red">Disabled &#x2717</span>', '<br>';
echo "&#187; Curl: ", extension_loaded('curl') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red">Disabled &#x2717</span>', '<br>';
echo "&#187; Json: ", extension_loaded('json') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red">Disabled &#x2717</span>', '<br><br>';
echo "&#187; Xml: ", extension_loaded('xml') ? '<span style="color: green;">Enabled &#x2713</span>' : '<span style="color: red">Disabled &#x2717</span>', '<br><br>';
?>
User Configuration:<br>

View File

@@ -13,7 +13,7 @@
<html lang="<?= current_language_code() ?>">
<head>
<meta charset="utf-8">
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<base href="<?= base_url() ?>">
<title><?= $config['company'] . '&nbsp;|&nbsp;' . lang('Common.software_short') . '&nbsp;|&nbsp;' . lang('Login.login') ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -26,7 +26,7 @@
? 'flatly'
: $config['theme']);
?>
<link rel="stylesheet" type="text/css" href="resources/bootswatch5/<?= "$theme" ?>/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="<?= "resources/bootswatch5/$theme/bootstrap.min.css" ?>">
<!-- inject:login:css -->
<link rel="stylesheet" href="css/login.min.css">
<!-- endinject -->
@@ -107,11 +107,10 @@
<footer class="d-flex justify-content-center flex-shrink-0 text-center">
<div class="footer container-fluid bg-body rounded shadow p-3 mb-md-4 mx-md-3">
<span class="text-primary">
<svg height="1.25em" role="img" viewBox="0 0 308.57998 308.57997" xmlns="http://www.w3.org/2000/svg">
<title><?= lang('Common.software_title') . '&nbsp;' . lang('Common.logo') ?></title>
<circle cx="154.28999" cy="154.28999" r="154.28999" fill="currentColor"/>
<path fill="#fff" d="M154.88998 145.66999c-.03-1.26-.03-3.29.19-4.29 4.6-11.1 15.57-18.82 28.3-18.82h.41v58.3c0 .12-.03.78-.04.9-.54 16.46-14.01 29.7-30.59 29.7v27.08c21 0 39.17-11.27 49.29-28.07l.07-.11c2.9.45 5.86.75 8.9.75 31.95 0 57.81-26 57.81-57.81 0-30.87-24.37-56.46-55.1-57.81h-30.74c-17.18 0-32.61 7.64-43.22 19.63-10.59-11.92-25.86-19.59-43.02-19.59-31.86 0-57.77 25.91-57.77 57.77 0 31.86 25.91 57.77 57.77 57.77 31.86 0 57.77-25.91 57.77-57.77v-3.68c-.01.01-.02-3.31-.03-3.95zm-57.75 38.33c-16.92 0-30.69-13.77-30.69-30.69s13.77-30.69 30.69-30.69 30.69 13.77 30.69 30.69-13.77 30.69-30.69 30.69zm142.96-19.87c-4.33 11.64-15.57 19.9-28.7 19.9h-.54v-61.47h.54c13.13 0 24.37 8.26 28.7 19.9 1.35 3.25 2.03 6.91 2.03 10.83s-.67 7.59-2.03 10.84z"/>
<span class="text-body-secondary">
<svg height="1em" role="img" viewBox="0 0 229.85 143.05001" xmlns="http://www.w3.org/2000/svg">
<title><?= lang('Common.software_short') . '&nbsp;' . lang('Common.logo_mark') ?></title>
<path fill="currentColor" d="M115.51 50.18c-.03-1.26-.03-3.29.19-4.29 4.6-11.1 15.57-18.82 28.3-18.82h.41v58.3c0 .12-.03.78-.04.9-.54 16.46-14.01 29.7-30.59 29.7v27.08c21 0 39.17-11.27 49.29-28.07l.07-.11c2.9.45 5.86.75 8.9.75 31.95 0 57.81-26 57.81-57.81 0-30.87-24.37-56.46-55.1-57.81h-30.74c-17.18 0-32.61 7.64-43.22 19.63C90.2 7.71 74.93.04 57.77.04 25.91.04 0 25.95 0 57.81c0 31.86 25.91 57.77 57.77 57.77 31.86 0 57.77-25.91 57.77-57.77v-3.68c-.01.01-.02-3.31-.03-3.95zM57.76 88.51c-16.92 0-30.69-13.77-30.69-30.69s13.77-30.69 30.69-30.69S88.45 40.9 88.45 57.82 74.68 88.51 57.76 88.51zm142.96-19.87c-4.33 11.64-15.57 19.9-28.7 19.9h-.54V27.07h.54c13.13 0 24.37 8.26 28.7 19.9 1.35 3.25 2.03 6.91 2.03 10.83s-.67 7.59-2.03 10.84z"/>
</svg>
</span>
<span><?= lang('Common.software_title') ?></span>

View File

@@ -56,7 +56,7 @@ $request = Services::request();
<div class="navbar-right" style="margin:0">
<?= anchor("home/changePassword/$user_info->person_id", "$user_info->first_name $user_info->last_name", ['class' => 'modal-dlg', 'data-btn-submit' => lang('Common.submit'), 'title' => lang('Employees.change_password')]) ?>
<span>&nbsp;|&nbsp;</span>
<?= ' | ' . ((ENVIRONMENT == 'development' || $request->getGet('debugdebug') == 'true') ? session('session_sha1') . ' | ' : '') ?>
<?= anchor('home/logout', lang('Login.logout')) ?>
</div>

View File

@@ -37,9 +37,7 @@
if ($config['company_logo'] != '')
{
?>
<div id="company_name">
<img id="image" src="<?= base_url('uploads/' . esc($config['company_logo'], 'url')) ?>" alt="company_logo" />
</div>
<div id="company_name"><img id="image" src="<?= esc(base_url('uploads/' . $config['company_logo']), 'url') ?>" alt="company_logo" /></div>
<?php
}
?>
@@ -145,7 +143,7 @@
?>
<tr>
<td colspan="3" style='text-align:right;'><?= lang('Sales.amount_tendered') ?></td>
<td><div class="total-value"><?= to_currency($amount_tendered) ?></div></td>
<td><div class="total-value">0.00</div></td>
</tr>
<tr>

View File

@@ -155,77 +155,76 @@ if (isset($success))
<td><?= anchor("$controller_name/deleteItem/$line", '<span class="glyphicon glyphicon-trash"></span>') ?></td>
<td><?= esc($item['item_number']) ?></td>
<td style="text-align:center;">
<?= esc($item['name'] . ' '. implode(' ', [$item['attribute_values'], $item['attribute_dtvalues']])) ?><br /> <?= '[' . to_quantity_decimals($item['in_stock']) . ' in ' . $item['stock_name'] . ']' ?>
<?= esc($item['name'] . ' ' . implode(' ', [$item['attribute_values'], $item['attribute_dtvalues']])) ?><br />
<?= '[' . to_quantity_decimals($item['in_stock']) . ' in ' . $item['stock_name'] . ']' ?>
<?= form_hidden('location', (string)$item['item_location']) ?>
</td>
<?php
if ($items_module_allowed && $mode != 'requisition')
{
?>
<td><?= form_input ([
<?php if ($items_module_allowed && $mode != 'requisition') { ?>
<td><?= form_input([
'name' => 'price',
'class' => 'form-control input-sm',
'value' => to_currency_no_money($item['price']),
'value' => (string)to_currency_no_money($item['price']),
'onClick' => 'this.select();'
]) ?></td>
<?php
}
else
{
?>
<?php } else { ?>
<td>
<?= $item['price'] ?>
<?= form_hidden('price', to_currency_no_money($item['price'])) ?>
<?= (string)$item['price'] ?>
<?= form_hidden('price', (string)to_currency_no_money($item['price'])) ?>
</td>
<?php
}
?>
<?php } ?>
<td><?= form_input (['name' => 'quantity', 'class' => 'form-control input-sm', 'value' => to_quantity_decimals($item['quantity']),'onClick' => 'this.select();']) ?></td>
<td><?= form_input([
'name' => 'quantity',
'class' => 'form-control input-sm',
'value' => (string)to_quantity_decimals($item['quantity']),
'onClick' => 'this.select();'
]) ?></td>
<td><?= form_dropdown(
'receiving_quantity',
$item['receiving_quantity_choices'],
$item['receiving_quantity'],
['class' => 'form-control input-sm']
) ?></td>
'receiving_quantity',
$item['receiving_quantity_choices'],
(string)$item['receiving_quantity'],
['class' => 'form-control input-sm']
) ?></td>
<?php
if ($items_module_allowed && $mode != 'requisition')
{
?>
<?php if ($items_module_allowed && $mode != 'requisition') { ?>
<td>
<div class="input-group">
<?= form_input (['name' => 'discount', 'class' => 'form-control input-sm', 'value' => $item['discount_type'] ? to_currency_no_money($item['discount']) : to_decimals($item['discount']), 'onClick' => 'this.select();']) ?>
<span class="input-group-btn">
<?= form_checkbox ([
'id' => 'discount_toggle',
'name' => 'discount_toggle',
'value' => 1,
'data-toggle' => "toggle",
'data-size' => 'small',
'data-onstyle' => 'success',
'data-on' => '<b>' . $config['currency_symbol'] .'</b>',
'data-off' => '<b>%</b>',
'data-line' => $line,
'checked' => $item['discount_type'] == 1
<div class="input-group">
<?= form_input([
'name' => 'discount',
'class' => 'form-control input-sm',
'value' => (string)($item['discount_type'] ? to_currency_no_money($item['discount']) : to_decimals($item['discount'])),
'onClick' => 'this.select();'
]) ?>
</span>
</div>
</td>
<?php
}
else
{
?>
<td><?= $item['discount'] ?></td>
<span class="input-group-btn">
<?= form_checkbox([
'id' => 'discount_toggle',
'name' => 'discount_toggle',
'value' => 1,
'data-toggle' => "toggle",
'data-size' => 'small',
'data-onstyle' => 'success',
'data-on' => '<b>' . $config['currency_symbol'] . '</b>',
'data-off' => '<b>%</b>',
'data-line' => $line,
'checked' => $item['discount_type'] == 1
]) ?>
</span>
</div>
</td>
<?php } else { ?>
<td><?= (string)$item['discount'] ?></td>
<?= form_hidden('discount', (string)$item['discount']) ?>
<?php
}
?>
<?php } ?>
<td>
<?= to_currency(($item['discount_type'] == PERCENT) ? $item['price']*$item['quantity']*$item['receiving_quantity'] - $item['price'] * $item['quantity'] * $item['receiving_quantity'] * $item['discount'] / 100 : $item['price']*$item['quantity']*$item['receiving_quantity'] - $item['discount']) ?></td>
<td><a href="javascript:$('#<?= esc("cart_$line", 'js') ?>').submit();" title=<?= lang(ucfirst($controller_name) .'.update') ?> ><span class="glyphicon glyphicon-refresh"></span></a></td>
<?= to_currency(
($item['discount_type'] == PERCENT)
? $item['price'] * $item['quantity'] * $item['receiving_quantity'] - $item['price'] * $item['quantity'] * $item['receiving_quantity'] * $item['discount'] / 100
: $item['price'] * $item['quantity'] * $item['receiving_quantity'] - $item['discount']
) ?>
</td>
<td><a href="javascript:$('#<?= esc("cart_$line", 'js') ?>').submit();" title=<?= lang(ucfirst($controller_name) . '.update') ?>><span class="glyphicon glyphicon-refresh"></span></a></td>
</tr>
<tr>
<?php
@@ -375,6 +374,53 @@ if (isset($success))
</tr>
</table>
<?= form_open("$controller_name/addPayment", ['id' => 'add_payment_form', 'class' => 'form-horizontal']) ?>
<section class="mainContent">
<div class="formContainer">
<form action="">
<fieldset class="fieldInput">
<div class="dropdown">
<?= form_dropdown('payment_type', $payment_options, $selected_payment_type, ['id' => 'payment_types', 'class' => 'dropdown-toggle', 'data-style' => 'btn-default btn-sm', 'data-width' => 'fit']) ?>
</div>
<?= form_input (['name' => 'amount_tendered', 'id' => 'amount_tendered', 'class' => 'form-input', 'value' => to_currency_no_money($amount_due), 'size' => '5', 'tabindex' => ++$tabindex, 'onClick' => 'this.select();']) ?>
<?= form_input (['name' => 'amount_tendered', 'id' => 'amount_tendered', 'class' => 'form-input', 'disabled' => true, 'value' => to_currency_no_money($amount_due), 'size' => '5', 'tabindex' => ++$tabindex]) ?>
<button type="submit" class="form-submit" id='add_payment_button' tabindex="<?= ++$tabindex ?>"><i class="bi bi-credit-card-fill"></i> Pagar</button>
</fieldset>
</form>
</div>
</section>
<?= form_close() ?>
<table class="sales_table_100" id="registersss">
<thead>
<tr>
<th style="width: 10%;"><?= lang('Common.delete') ?></th>
<th style="width: 60%;"><?= lang(ucfirst($controller_name) .'.payment_type') ?></th>
<th style="width: 20%;"><?= lang(ucfirst($controller_name) .'.payment_amount') ?></th>
</tr>
</thead>
<tbody id="payment_contents">
<?php
$payment_types = [];
foreach($payments as $payment_id => $payment)
{
$payment_types[] = $payment['payment_type'];
?>
<tr>
<td><?= anchor("$controller_name/deletePayment/$payment_id", '<span class="glyphicon glyphicon-trash"></span>') ?></td>
<td><?= esc($payment['payment_type']) ?></td>
<td style="text-align: right;"><?= to_currency($payment['payment_amount']) ?></td>
</tr>
<?php
}
$payment_types_string = implode(', ', $payment_types);
?>
</tbody>
</table>
<?php
if(count($cart) > 0)
{

View File

@@ -255,7 +255,8 @@ $(document).ready(function()
<div id="sale_return_policy">
<h5>
<span><?= nl2br(esc($config['payment_message'])) ?></span>
<span style='padding:4%;'><?= empty($comments) ? esc($config['invoice_default_comments']) : lang('Sales.comments') . ': ' . esc($comments) ?></span>
<span style='padding:4%;'><?= empty($comments) ? '' : lang('Sales.comments') . ': ' . esc($comments) ?></span>
<span style='padding:4%;'><?= esc($config['invoice_default_comments']) ?></span>
</h5>
<div style='padding:2%;'><?= nl2br(esc($config['return_policy'])) ?></div>
</div>

View File

@@ -19,6 +19,7 @@
*/
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="<?= $this->request->getLocale() ?>">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="<?= base_url('css/invoice_email.css') ?>"/>
@@ -207,7 +208,7 @@ if(isset($error_message))
<?= nl2br($config['return_policy']) ?>
</div>
<div id='barcode'>
<img alt=<?='$sale_id'?> src='data:image/svg+xml;base64,<?= base64_encode($barcode) ?>' /><br>
<img alt='<?= esc($barcode, 'attr') ?>' src='data:image/png;base64,<?= esc($barcode, 'attr') ?>' /><br>
<?= $sale_id ?>
</div>
</div>

View File

@@ -24,7 +24,7 @@ if (isset($error_message))
{
var send_email = function()
{
$.get('<?= site_url() . esc("/sales/sendPdf/$sale_id_num/receipt") ?>',
$.get('<?= site_url() . esc("/sales/sendReceipt/$sale_id_num", 'url') ?>',
function(response)
{
$.notify( { message: response.message }, { type: response.success ? 'success' : 'danger'} )

View File

@@ -116,7 +116,7 @@
<?php
}
?>
<td class="total-value" style="text-align:right;"><?= to_currency($item['discounted_total']) ?></td>
<td class="total-value"><?= to_currency($item['discounted_total']) ?></td>
</tr>
<?php
}
@@ -170,6 +170,10 @@
<td style="<?= $border ? 'border-top: 2px solid black;' : '' ?>text-align:right"><?= to_currency($total) ?></td>
</tr>
<tr>
<td colspan="4">&nbsp;</td>
</tr>
<?php
$only_sale_check = false;
$show_giftcard_remainder = false;
@@ -186,7 +190,11 @@
<?php
}
?>
<tr>
<td colspan="4">&nbsp;</td>
</tr>
<?php
if(isset($cur_giftcard_value) && $show_giftcard_remainder)
{
@@ -208,14 +216,14 @@
</tr>
</table>
<div id="terms">
<div id="sale_return_policy" style="text-align:center">
<?= nl2br(esc($config['return_policy'])) ?>
</div>
<div id='barcode' style="text-align:center">
<img alt=<?='$sale_id'?> src='data:image/svg+xml;base64,<?= base64_encode($barcode) ?>' /><br>
<?= $sale_id ?>
</div>
<div id="sale_return_policy" style="text-align:center">
<?= nl2br(esc($config['return_policy'])) ?>
</div>
<br>
<div id="barcode" style="text-align:center">
<?= $barcode ?><br>
<?= $sale_id ?>
</div>
</div>

View File

@@ -11,7 +11,7 @@ services:
depends_on:
- mysql
ports:
- "80:80"
- "127.0.0.1:80:80"
networks:
- app_net
volumes:

View File

@@ -14,7 +14,6 @@ import header from 'gulp-header'
import tar from 'gulp-tar'
import gzip from 'gulp-gzip'
import zip from 'gulp-zip'
import run from 'gulp-run'
import { Stream } from 'readable-stream'
const {finished, pipeline} = Stream.promises
@@ -249,13 +248,6 @@ gulp.task('inject-login', function() {
);
});
gulp.task('update-licenses', function() {
run('composer licenses --format=json --no-dev > public/license/composer.LICENSES').exec();
return pipeline(gulp.src('LICENSE'),gulp.dest('public/license'));
});
gulp.task('build-database', function() {
return gulp.src(['./app/Database/tables.sql','./app/Database/constraints.sql'])
.pipe(header('-- >> This file is autogenerated from tables.sql and constraints.sql. Do not modify directly << --'))
@@ -274,6 +266,5 @@ gulp.task('default',
'prod-css',
'copy-fonts',
'inject-login',
'update-licenses',
'build-database'
));

4510
package-lock.json generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -10,9 +10,7 @@
"objecttothis <objecttothis - at - gmail.com>",
"SteveIreland <stevei - at - ruledomain.com>"
],
"files": [
"dist/opensourcepos.$version.tgz"
],
"files": ["dist/opensourcepos.$version.tgz"],
"publishConfig": {
"registry": "https://npm.pkg.github.com/"
},
@@ -21,8 +19,8 @@
"POS"
],
"repository": {
"type": "git",
"url": "https://github.com/opensourcepos/opensourcepos"
"type": "git",
"url": "https://github.com/opensourcepos/opensourcepos"
},
"scripts": {
"build": "gulp default",
@@ -51,6 +49,9 @@
"coffeescript": "^2.7.0",
"es6-promise": "^4.2.8",
"file-saver": "^2.0.5",
"gulp-gzip": "^1.4.2",
"gulp-tar": "^4.0.0",
"gulp-zip": "^6.0.0",
"html2canvas": "^1.4.1",
"jasny-bootstrap": "^3.1.3",
"jquery": "^3.7.1",
@@ -68,16 +69,12 @@
"gulp-clean-css": "^4.3.0",
"gulp-concat": "^2.6.1",
"gulp-debug": "^5.0.1",
"gulp-gzip": "^1.4.2",
"gulp-header": "^2.0.9",
"gulp-inject": "^5.0.5",
"gulp-rename": "^2.0.0",
"gulp-rev": "^10.0.0",
"gulp-run": "^1.7.1",
"gulp-tar": "^4.0.0",
"gulp-uglify": "^3.0.2",
"gulp-zip": "^6.1.0",
"npm-check-updates": "^17.1.14",
"npm-check-updates": "^16.14.11",
"readable-stream": "^4.4.2",
"stream-series": "^0.1.1"
}

View File

@@ -1,30 +1,30 @@
MIT License
Copyright (c) 2013-2024 jekkos
Copyright (c) 2017-2024 objecttothis
Copyright (c) 2017-2024 Steve Ireland
Copyright (c) 2017-2024 odiea
Copyright (c) 2018-2024 WebShells
Copyright (c) 2021-2024 BudsieBuds
Copyright (c) 2015-2023 FrancescoUK (aka daN4cat)
Copyright (c) 2015-2022 Aamir Shahzad (aka asakpke), RoshanTech, eSite.pk
Copyright (c) 2019-2020 Andriux1990
Copyright (c) 2018-2019 Erasto Marroquin (aka Erastus)
Copyright (c) 2013-2022 jekkos
Copyright (c) 2015-2021 FrancescoUK (aka daN4cat)
Copyright (c) 2017-2021 Steve Ireland
Copyright (c) 2017-2022 objecttothis
Copyright (c) 2017-2021 odiea
Copyright (c) 2017-2021 WebShells
Copyright (c) 2020-2021 Andriux1990
Copyright (c) 2021 BudsieBuds
Copyright (c) 2019 Loyd Jayme (aka loydjayme25)
Copyright (c) 2018 Erasto Marroquin (aka Erastus)
Copyright (c) 2018 Nathan Sas (aka nathanzky)
Copyright (c) 2018 Emilio Silva (aka emi-silva)
Copyright (c) 2016-2017 Ramkrishna Mondal (aka RamkrishnaMondal)
Copyright (c) 2016-2017 Jorge Colmenarez (aka jlctmaster), frontuari.com
Copyright (c) 2016-2017 Jesus Guerrero Botella (aka i92guboj)
Copyright (c) 2017 Jesus Guerrero Botella (aka i92guboj)
Copyright (c) 2017 Deep Shah (aka deepshah)
Copyright (c) 2017 Joshua Fernandez (aka joshua1234511)
Copyright (c) 2017 asadjaved63
Copyright (c) 2016-2017 Ramkrishna Mondal (aka RamkrishnaMondal)
Copyright (c) 2016-2017 Jorge Colmenarez (aka jlctmaster), frontuari.com
Copyright (c) 2016 Rinaldy@dbarber (aka rnld26)
Copyright (c) 2015-2022 Aamir Shahzad (aka asakpke), RoshanTech, eSite.pk
Copyright (c) 2015 Toni Haryanto (aka yllumi)
Copyright (c) 2012-2014 pappastech
Copyright (c) 2013 Rob Garrison
Copyright (c) 2013 Parq
Copyright (c) 2013 Ramel
Copyright (c) 2012-2014 pappastech
Copyright (c) 2012 Alain
Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -34,16 +34,16 @@ 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 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.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Additionally, you cannot claim copyright or ownership of the Software.

View File

@@ -1,6 +1,7 @@
The MIT License (MIT)
Copyright (c) 2011-2024 The Bootstrap Authors
Copyright (c) 2011-2021 Twitter, Inc.
Copyright (c) 2011-2021 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1 +1 @@
Bootstrap 5.3.3
Bootstrap 5.0.1

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2019-2024 The Bootstrap Authors
Copyright (c) 2019-2020 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1 +1 @@
Bootstrap Icons 1.11.3
Bootstrap Icons 1.5.0

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Thomas Park
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.

View File

@@ -0,0 +1 @@
Bootswatch 5.0.1

View File

@@ -1 +1 @@
Bootswatch 3.4.1+1 & 5.3.3
Bootswatch 3.4.1+1

View File

@@ -1,87 +1,63 @@
{
"name": "opensourcepos/opensourcepos",
"version": "dev-master",
"name": "OpenSourcePOS",
"version": "3.4.0",
"license": [
"MIT"
],
"dependencies": {
"codeigniter4/framework": {
"version": "v4.5.5",
"codeigniter/framework": {
"version": "4.1.3",
"license": [
"MIT"
]
},
"dompdf/dompdf": {
"version": "v2.0.4",
"license": [
"LGPL-2.1"
]
},
"ezyang/htmlpurifier": {
"version": "v4.17.0",
"version": "v0.8.6",
"license": [
"LGPL-2.1-or-later"
]
},
"laminas/laminas-escaper": {
"version": "2.13.0",
"mikey179/vfsStream": {
"version": "v1.1.0",
"license": [
"BSD-3-Clause"
]
},
"masterminds/html5": {
"version": "2.8.1",
"license": [
"MIT"
"BSD"
]
},
"paragonie/random_compat": {
"version": "v2.0.21",
"version": "2.0.19",
"license": [
"MIT"
]
},
"phenx/php-font-lib": {
"version": "0.5.6",
"license": [
"LGPL-2.1-or-later"
]
},
"phenx/php-svg-lib": {
"version": "0.5.2",
"license": [
"LGPL-3.0"
]
},
"picqer/php-barcode-generator": {
"version": "v2.4.0",
"license": [
"LGPL-3.0-or-later"
]
},
"sabberworm/php-css-parser": {
"version": "v8.5.1",
"phenx/php-svg-lib": {
"version": "0.3.3",
"license": [
"MIT"
]
},
"symfony/polyfill-ctype": {
"version": "v1.31.0",
"license": [
"MIT"
]
},
"symfony/polyfill-mbstring": {
"version": "v1.31.0",
"license": [
"MIT"
"LGPL-3.0-or-later"
]
},
"tamtamchik/namecase": {
"version": "3.0.0",
"version": "1.0.6",
"license": [
"MIT"
]
},
"vlucas/phpdotenv": {
"version": "2.6.6",
"license": [
"BSD-3-Clause-Attribution"
]
},
"symfony/polyfill-ctype": {
"version": "v1.20.0",
"license": [
"MIT"
]
}
}
}