Merge branch 'master' into plugin-system-fresh

This commit is contained in:
objecttothis authored and GitHub committed 2026-08-31 16:18:59 +04:00
commit 459610cded
90 files changed
+4225 -2824

No files matched your search

+3 -3
View File
@@ -63,7 +63,7 @@ class Config extends Secure_Controller
$this->db = Database::connect();
helper('security');
if (check_encryption()) {
if (checkEncryption()) {
$this->encrypter = Services::encrypter();
} else {
log_message('alert', 'Error preparing encryption key');
@@ -505,7 +505,7 @@ class Config extends Secure_Controller
{
$password = '';
if (check_encryption() && !empty($this->request->getPost('smtp_pass'))) {
if (checkEncryption() && !empty($this->request->getPost('smtp_pass'))) {
$password = $this->encrypter->encrypt($this->request->getPost('smtp_pass'));
}
@@ -551,7 +551,7 @@ class Config extends Secure_Controller
{
$password = '';
if (check_encryption() && !empty($this->request->getPost('msg_pwd'))) {
if (checkEncryption() && !empty($this->request->getPost('msg_pwd'))) {
$password = $this->encrypter->encrypt($this->request->getPost('msg_pwd'));
}
+86 -31
View File
@@ -3,6 +3,7 @@
namespace App\Controllers;
use App\Models\Module;
use CodeIgniter\HTTP\Exceptions\RedirectException;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
@@ -79,8 +80,7 @@ class Employees extends Persons
$current_user = $this->employee->get_logged_in_employee_info();
if ($employee_id != NEW_ENTRY && !$this->employee->canModifyEmployee($person_info->person_id, $current_user->person_id)) {
header('Location: ' . base_url('no_access/employees/employees'));
exit();
throw new RedirectException('no_access/employees/employees');
}
foreach (get_object_vars($person_info) as $property => $value) {
@@ -114,13 +114,13 @@ class Employees extends Persons
* Inserts/updates an employee
* @return ResponseInterface
*/
public function postSave(int $employee_id = NEW_ENTRY): ResponseInterface
public function postSave(int $employeeId = NEW_ENTRY): ResponseInterface
{
$current_user = $this->employee->get_logged_in_employee_info();
$currentUser = $this->employee->get_logged_in_employee_info();
if ($employee_id != NEW_ENTRY) {
$target_employee = $this->employee->getInfo($employee_id);
if (!$this->employee->canModifyEmployee($target_employee->person_id, $current_user->person_id)) {
if ($employeeId != NEW_ENTRY) {
$targetEmployee = $this->employee->get_info($employeeId);
if (!$this->employee->canModifyEmployee($targetEmployee->person_id, $currentUser->person_id)) {
return $this->response->setJSON([
'success' => false,
'message' => lang('Employees.error_updating_admin'),
@@ -129,17 +129,17 @@ class Employees extends Persons
}
}
$first_name = $this->request->getPost('first_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); // TODO: duplicated code
$last_name = $this->request->getPost('last_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$firstName = $this->request->getPost('first_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS); // TODO: duplicated code
$lastName = $this->request->getPost('last_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$email = strtolower($this->request->getPost('email', FILTER_SANITIZE_EMAIL));
// format first and last name properly
$first_name = $this->nameize($first_name);
$last_name = $this->nameize($last_name);
$firstName = $this->nameize($firstName);
$lastName = $this->nameize($lastName);
$person_data = [
'first_name' => $first_name,
'last_name' => $last_name,
$personData = [
'first_name' => $firstName,
'last_name' => $lastName,
'gender' => $this->request->getPost('gender', FILTER_SANITIZE_NUMBER_INT),
'email' => $email,
'phone_number' => $this->request->getPost('phone_number', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
@@ -152,27 +152,55 @@ class Employees extends Persons
'comments' => $this->request->getPost('comments', FILTER_SANITIZE_FULL_SPECIAL_CHARS)
];
$grants_array = [];
$isAdmin = $this->employee->isAdmin($current_user->person_id);
$grantsArray = [];
$isAdmin = $this->employee->isAdmin($currentUser->person_id);
foreach ($this->module->get_all_permissions()->getResult() as $permission) {
$grants = [];
$grant = $this->request->getPost('grant_' . $permission->permission_id) != null ? $this->request->getPost('grant_' . $permission->permission_id, FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '';
if ($grant == $permission->permission_id) {
if (!$isAdmin && !$this->employee->has_grant($permission->permission_id, $current_user->person_id)) {
if (!$isAdmin && !$this->employee->has_grant($permission->permission_id, $currentUser->person_id)) {
continue;
}
$grants['permission_id'] = $permission->permission_id;
$grants['menu_group'] = $this->request->getPost('menu_group_' . $permission->permission_id) != null ? $this->request->getPost('menu_group_' . $permission->permission_id, FILTER_SANITIZE_FULL_SPECIAL_CHARS) : '--';
$grants_array[] = $grants;
$grantsArray[] = $grants;
}
}
$minimumGrants = ['employees', 'home', 'office'];
$missingMinimumGrant = array_diff($minimumGrants, array_column($grantsArray, 'permission_id'));
if ($isAdmin && $employeeId == $currentUser->person_id && !empty($missingMinimumGrant)) {
return $this->response->setJSON([
'success' => false,
'message' => lang('Employees.error_cannot_remove_own_minimum_grant'),
'id' => $employeeId
]);
}
if (filter_var(getenv('DISALLOW_GRANT_CHANGE'), FILTER_VALIDATE_BOOLEAN)
&& $this->hasGrantsChanged($employeeId, $isAdmin, $currentUser, $grantsArray)) {
return $this->response->setJSON([
'success' => false,
'message' => lang('Employees.error_grant_change_disallowed'),
'id' => $employeeId
]);
}
if (!empty($this->request->getPost('password')) && ENVIRONMENT != 'testing' && filter_var(getenv('DISALLOW_PASSWORD_CHANGE'), FILTER_VALIDATE_BOOLEAN)) {
return $this->response->setJSON([
'success' => false,
'message' => lang('Employees.error_password_change_disallowed'),
'id' => $employeeId
]);
}
// Password has been changed OR first time password set
if (!empty($this->request->getPost('password')) && ENVIRONMENT != 'testing') {
$exploded = explode(":", $this->request->getPost('language', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
$employee_data = [
$employeeData = [
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'password' => password_hash($this->request->getPost('password'), PASSWORD_DEFAULT),
'hash_version' => 2,
@@ -181,42 +209,69 @@ class Employees extends Persons
];
} else { // Password not changed
$exploded = explode(":", $this->request->getPost('language', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
$employee_data = [
$employeeData = [
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'language_code' => $exploded[0],
'language' => $exploded[1]
];
}
if ($this->employee->save_employee($person_data, $employee_data, $grants_array, $employee_id)) {
if ($this->employee->save_employee($personData, $employeeData, $grantsArray, $employeeId)) {
// New employee
if ($employee_id == NEW_ENTRY) {
if ($employeeId == NEW_ENTRY) {
return $this->response->setJSON([
'success' => true,
'message' => lang('Employees.successful_adding') . ' ' . $first_name . ' ' . $last_name,
'id' => $employee_data['person_id']
'message' => lang('Employees.successful_adding') . ' ' . $firstName . ' ' . $lastName,
'id' => $employeeData['person_id']
]);
} else { // Existing employee
$logged_in_employee_id = session()->get('person_id');
if ($employee_id == $logged_in_employee_id) {
session()->set('language_code', $employee_data['language_code']);
session()->set('language', $employee_data['language']);
$loggedInEmployeeId = session()->get('person_id');
if ($employeeId == $loggedInEmployeeId) {
session()->set('language_code', $employeeData['language_code']);
session()->set('language', $employeeData['language']);
}
return $this->response->setJSON([
'success' => true,
'message' => lang('Employees.successful_updating') . ' ' . $first_name . ' ' . $last_name,
'id' => $employee_id
'message' => lang('Employees.successful_updating') . ' ' . $firstName . ' ' . $lastName,
'id' => $employeeId
]);
}
} else { // Failure
return $this->response->setJSON([
'success' => false,
'message' => lang('Employees.error_adding_updating') . ' ' . $first_name . ' ' . $last_name,
'message' => lang('Employees.error_adding_updating') . ' ' . $firstName . ' ' . $lastName,
'id' => NEW_ENTRY
]);
}
}
/**
* Determines whether the submitted grants differ from the employee's current grants,
* limited to the permissions the current user has authority over when not an admin.
*/
private function hasGrantsChanged(int $employeeId, bool $isAdmin, object $currentUser, array $grantsArray): bool
{
$currentGrantIds = [];
if ($employeeId != NEW_ENTRY) {
$currentGrantIds = array_column($this->employee->get_employee_grants($employeeId), 'permission_id');
if (!$isAdmin) {
$currentGrantIds = array_values(array_filter(
$currentGrantIds,
fn ($permissionId) => $this->employee->has_grant($permissionId, $currentUser->person_id)
));
}
}
$submittedGrantIds = array_column($grantsArray, 'permission_id');
sort($currentGrantIds);
sort($submittedGrantIds);
return $currentGrantIds !== $submittedGrantIds;
}
/**
* This deletes employees from the employees table
* @return ResponseInterface
+8
View File
@@ -3,6 +3,7 @@
namespace App\Controllers;
use App\Libraries\MY_Migration;
use App\Models\Employee;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\ResponseInterface;
@@ -10,6 +11,13 @@ class Home extends Secure_Controller
{
public function __construct()
{
$methodName = urldecode(service('request')->getUri()->getSegment(2));
if ($methodName === 'logout') {
$this->employee = model(Employee::class);
return;
}
parent::__construct('home', null, 'home');
}
+16 -16
View File
@@ -255,39 +255,39 @@ class Item_kits extends Secure_Controller
/**
* AJAX called function that generates barcodes for selected item_kits.
*
* @param string $item_kit_ids Colon separated list of item_kit_id values to generate barcodes for.
* @param string $itemKitIds Colon separated list of item_kit_id values to generate barcodes for.
* @return string
* @noinspection PhpUnused
*/
public function getGenerateBarcodes(string $item_kit_ids): string
public function getGenerateBarcodes(string $itemKitIds): string
{
$barcode_lib = new Barcode_lib();
$barcodeLib = new Barcode_lib();
$result = [];
$item_kit_ids = explode(':', $item_kit_ids);
foreach ($item_kit_ids as $item_kid_id) {
$itemKitIds = explode(':', $itemKitIds);
foreach ($itemKitIds as $itemKitId) {
// Calculate the total cost and retail price of the Kit, so it can be added to the barcode text at the bottom
$item_kit = $this->_add_totals_to_item_kit($this->item_kit->get_info($item_kid_id));
$itemKit = $this->_add_totals_to_item_kit($this->item_kit->get_info($itemKitId));
$item_kid_id = 'KIT ' . urldecode($item_kid_id);
$itemKitId = 'KIT ' . $itemKitId;
$result[] = [
'name' => $item_kit->name,
'item_id' => $item_kid_id,
'item_number' => $item_kid_id,
'cost_price' => $item_kit->total_cost_price,
'unit_price' => $item_kit->total_unit_price
'name' => $itemKit->name,
'item_id' => $itemKitId,
'item_number' => $itemKitId,
'cost_price' => $itemKit->total_cost_price,
'unit_price' => $itemKit->total_unit_price
];
}
$data['items'] = $result;
$barcode_config = $barcode_lib->get_barcode_config();
$barcodeConfig = $barcodeLib->get_barcode_config();
// In case the selected barcode type is not Code39 or Code128 we set by default Code128
// The rationale for this is that EAN codes cannot have strings as seed, so 'KIT ' is not allowed
if ($barcode_config['barcode_type'] != 'C39' && $barcode_config['barcode_type'] != 'C128') {
$barcode_config['barcode_type'] = 'C128';
if ($barcodeConfig['barcode_type'] != 'C39' && $barcodeConfig['barcode_type'] != 'C128') {
$barcodeConfig['barcode_type'] = 'C128';
}
$data['barcode_config'] = $barcode_config;
$data['barcode_config'] = $barcodeConfig;
// Display barcodes
return view("barcodes/barcode_sheet", $data);
+19 -1
View File
@@ -2,8 +2,10 @@
namespace App\Controllers;
use App\Models\Employee;
use App\Models\Module;
use CodeIgniter\HTTP\ResponseInterface;
use Config\OSPOS;
/**
* Part of the grants mechanism to restrict access to modules that the user doesn't have permission for.
@@ -13,10 +15,12 @@ use CodeIgniter\HTTP\ResponseInterface;
*/
class No_access extends BaseController
{
private Employee $employee;
private Module $module;
public function __construct()
{
$this->employee = model(Employee::class);
$this->module = model(Module::class);
}
@@ -30,6 +34,20 @@ class No_access extends BaseController
$data['module_name'] = $this->module->get_module_name($module_id);
$data['permission_id'] = $permission_id;
return view('no_access', $data);
$userInfo = $this->employee->get_logged_in_employee_info();
if ($userInfo === false || $this->request->isAJAX()) {
return view('no_access', $data);
}
$menuGroup = session()->get('menu_group');
$allowedModules = $menuGroup == 'home'
? $this->module->get_allowed_home_modules($userInfo->person_id)
: $this->module->get_allowed_office_modules($userInfo->person_id);
$data['user_info'] = $userInfo;
$data['allowed_modules'] = $allowedModules->getResult();
$data['config'] = config(OSPOS::class)->settings;
return view('partial/header', $data) . view('no_access', $data) . view('partial/footer');
}
}
+11 -9
View File
@@ -25,6 +25,7 @@ use App\Models\Reports\Summary_sales;
use App\Models\Reports\Summary_sales_taxes;
use App\Models\Reports\Summary_suppliers;
use App\Models\Reports\Summary_taxes;
use CodeIgniter\HTTP\Exceptions\RedirectException;
use CodeIgniter\HTTP\ResponseInterface;
use Config\OSPOS;
use Config\Services;
@@ -55,8 +56,8 @@ class Reports extends Secure_Controller
{
parent::__construct('reports');
$request = Services::request();
$method_name = $request->getUri()->getSegment(2);
$exploder = explode('_', $method_name);
$methodName = urldecode($request->getUri()->getSegment(2));
$exploder = explode('_', $methodName);
$this->attribute = config(Attribute::class);
$this->config = config(OSPOS::class)->settings;
@@ -79,15 +80,16 @@ class Reports extends Secure_Controller
$this->inventory_summary = model(Inventory_summary::class);
if (sizeof($exploder) > 1) {
preg_match('/(?:inventory)|([^_.]*)(?:_graph|_row)?$/', $method_name, $matches);
preg_match('/(?:inventory)|([^_.]*)(?:_graph|_row)?$/', $methodName, $matches);
preg_match('/^(.*?)([sy])?$/', array_pop($matches), $matches);
$submodule_id = $matches[1] . ((count($matches) > 2) ? $matches[2] : 's');
$submoduleId = $matches[1] . ((count($matches) > 2) ? $matches[2] : 's');
} else {
$submoduleId = null;
}
// Check access to report submodule
if (!$this->employee->has_grant('reports_' . $submodule_id, $this->employee->get_logged_in_employee_info()->person_id)) {
header('Location: ' . base_url('no_access/reports/reports_' . $submodule_id));
exit();
}
// Check access to report submodule
if ($submoduleId !== null && !$this->employee->has_grant('reports_' . $submoduleId, $this->employee->get_logged_in_employee_info()->person_id)) {
throw new RedirectException('no_access/reports/reports_' . $submoduleId);
}
helper('report');
+175 -126
View File
@@ -126,26 +126,38 @@ class Sales extends Secure_Controller
}
/**
* @param int $row_id
* @param int $rowId
* @return ResponseInterface
*/
public function getRow(int $row_id): ResponseInterface
public function getRow(int $rowId): ResponseInterface
{
$sale_info = $this->sale->getInfo($row_id)->getRow();
$data_row = get_sale_data_row($sale_info);
$personId = $this->session->get('person_id');
return $this->response->setJSON($data_row);
if (!$this->employee->has_grant('reports_sales', $personId)) {
return $this->response->setStatusCode(403)->setJSON(['success' => false, 'message' => lang('Sales.not_authorized')]);
}
$saleInfo = $this->sale->get_info($rowId)->getRow();
$dataRow = getSaleDataRow($saleInfo);
return $this->response->setJSON($dataRow);
}
/**
* @return void
* @return ResponseInterface
*/
public function getSearch(): ResponseInterface
{
$personId = $this->session->get('person_id');
if (!$this->employee->has_grant('reports_sales', $personId)) {
return $this->response->setStatusCode(403)->setJSON(['success' => false, 'message' => lang('Sales.not_authorized')]);
}
$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');
$sort = $this->sanitizeSortColumn(salesHeaders(), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'sale_id');
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$filters = [
@@ -166,24 +178,24 @@ class Sales extends Secure_Controller
];
// 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);
$requestFilters = array_fill_keys($this->request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? [], true);
$filters = array_merge($filters, $requestFilters);
$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);
$totalRows = $this->sale->get_found_rows($search, $filters);
$payments = $this->sale->getPaymentsSummary($search, $filters);
$paymentSummary = getSalesManagePaymentsSummary($payments);
$data_rows = [];
$dataRows = [];
foreach ($sales->getResult() as $sale) {
$data_rows[] = get_sale_data_row($sale);
$dataRows[] = getSaleDataRow($sale);
}
if ($total_rows > 0) {
$data_rows[] = get_sale_data_last_row($sales);
if ($totalRows > 0) {
$dataRows[] = getSaleDataLastRow($sales);
}
return $this->response->setJSON(['total' => $total_rows, 'rows' => $data_rows, 'payment_summary' => $payment_summary]);
return $this->response->setJSON(['total' => $totalRows, 'rows' => $dataRows, 'payment_summary' => $paymentSummary]);
}
/**
@@ -633,6 +645,7 @@ class Sales extends Secure_Controller
$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'));
$price = $price !== false ? number_format((float) $price, totals_decimals(), '.', '') : $price;
$quantity = parse_decimals($this->request->getPost('quantity'));
$discount_type = $this->request->getPost('discount_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$discount = $discount_type
@@ -1007,38 +1020,44 @@ class Sales extends Secure_Controller
/**
* 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 int $saleId
* @param string $type
* @return ResponseInterface
* @noinspection PhpUnused
*/
public function getSendPdf(int $sale_id, string $type = 'invoice'): ResponseInterface
public function getSendPdf(int $saleId, string $type = 'invoice'): ResponseInterface
{
$sale_data = $this->_load_sale_data($sale_id);
$personId = $this->session->get('person_id');
if (!$this->employee->has_grant('reports_sales', $personId)) {
return $this->response->setStatusCode(403)->setJSON(['success' => false, 'message' => lang('Sales.not_authorized')]);
}
$saleData = $this->_load_sale_data($saleId);
$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"] : "";
if (!empty($saleData['customer_email'])) {
$to = $saleData['customer_email'];
$number = array_key_exists($type . "_number", $saleData) ? $saleData[$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)
new Token_invoice_count('POS ' . $saleData['sale_id']),
new Token_customer((array)$saleData)
];
$text = $this->token_lib->render($text, $tokens);
$sale_data['mimetype'] = $this->email_lib->getLogoMimeType();
$saleData['mimetype'] = $this->email_lib->getLogoMimeType();
// Build img_tag for email views that need it (receipt_email.php)
$sale_data['img_tag'] = $this->email_lib->buildLogoImgTag();
$saleData['img_tag'] = $this->email_lib->buildLogoImgTag();
// Generate email attachment: invoice in PDF format
$view = Services::renderer();
$html = $view->setData($sale_data)->render("sales/$type" . '_email', $sale_data);
$html = $view->setData($saleData)->render("sales/$type" . '_email', $saleData);
// Load PDF helper
helper(['dompdf', 'file']);
@@ -1052,32 +1071,38 @@ class Sales extends Secure_Controller
$this->sale_lib->clear_all();
return $this->response->setJSON(['success' => $result, 'message' => $message, 'id' => $sale_id]);
return $this->response->setJSON(['success' => $result, 'message' => $message, 'id' => $saleId]);
}
/**
* Emails sales receipt to customer. Used in app/Views/sales/receipt.php
*
* @param int $sale_id
* @param int $saleId
* @return ResponseInterface
* @noinspection PhpUnused
*/
public function getSendReceipt(int $sale_id): ResponseInterface
public function getSendReceipt(int $saleId): ResponseInterface
{
$sale_data = $this->_load_sale_data($sale_id);
$personId = $this->session->get('person_id');
if (!$this->employee->has_grant('reports_sales', $personId)) {
return $this->response->setStatusCode(403)->setJSON(['success' => false, 'message' => lang('Sales.not_authorized')]);
}
$saleData = $this->_load_sale_data($saleId);
$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'] = $this->email_lib->buildLogoImgTag();
if (!empty($saleData['customer_email'])) {
$saleData['barcode'] = $this->barcode_lib->generate_receipt_barcode($saleData['sale_id']);
$saleData['img_tag'] = $this->email_lib->buildLogoImgTag();
$to = $sale_data['customer_email'];
$to = $saleData['customer_email'];
$subject = lang('Sales.receipt');
$view = Services::renderer();
$text = $view->setData($sale_data)->render('sales/receipt_email');
$text = $view->setData($saleData)->render('sales/receipt_email');
$result = $this->email_lib->sendEmail($to, $subject, $text);
@@ -1086,7 +1111,7 @@ class Sales extends Secure_Controller
$this->sale_lib->clear_all();
return $this->response->setJSON(['success' => $result, 'message' => $message, 'id' => $sale_id]);
return $this->response->setJSON(['success' => $result, 'message' => $message, 'id' => $saleId]);
}
/**
@@ -1382,13 +1407,19 @@ class Sales extends Secure_Controller
/**
* Load the sales receipt for a sale. Used in app/Views/sales/form.php
*
* @param int $sale_id
* @param int $saleId
* @return string
* @noinspection PhpUnused
*/
public function getReceipt(int $sale_id): string
public function getReceipt(int $saleId): string|ResponseInterface
{
$data = $this->_load_sale_data($sale_id);
$personId = $this->session->get('person_id');
if (!$this->employee->has_grant('reports_sales', $personId)) {
return redirect()->to('no_access/sales/reports_sales');
}
$data = $this->_load_sale_data($saleId);
$this->sale_lib->clear_all();
return view('sales/receipt', $data);
@@ -1397,13 +1428,19 @@ class Sales extends Secure_Controller
/**
* Loads the sales invoice for a sale. Used in app/Views/sales/form.php
*
* @param int $sale_id
* @param int $saleId
* @return string
* @noinspection PhpUnused
*/
public function getInvoice(int $sale_id): string
public function getInvoice(int $saleId): string|ResponseInterface
{
$data = $this->_load_sale_data($sale_id);
$personId = $this->session->get('person_id');
if (!$this->employee->has_grant('reports_sales', $personId)) {
return redirect()->to('no_access/sales/reports_sales');
}
$data = $this->_load_sale_data($saleId);
$this->sale_lib->clear_all();
return view('sales/' . $data['invoice_view'], $data);
@@ -1412,25 +1449,31 @@ class Sales extends Secure_Controller
/**
* Edits an existing sale or work order. Used in app/Views/sales/form.php
*
* @param int $sale_id
* @param int $saleId
* @return string
* @throws ReflectionException
*/
public function getEdit(int $sale_id): string
public function getEdit(int $saleId): string|ResponseInterface
{
$personId = $this->session->get('person_id');
if (!$this->employee->has_grant('reports_sales', $personId)) {
return redirect()->to('no_access/sales/reports_sales');
}
$data = [];
$sale_info = $this->sale->getInfo($sale_id)->getRowArray();
$data['selected_customer_id'] = $sale_info['customer_id'];
$data['selected_customer_name'] = $sale_info['customer_name'];
$employee_info = $this->employee->getInfo($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);
$saleInfo = $this->sale->get_info($saleId)->getRowArray();
$data['selected_customer_id'] = $saleInfo['customer_id'];
$data['selected_customer_name'] = $saleInfo['customer_name'];
$employeeInfo = $this->employee->get_info($saleInfo['employee_id']);
$data['selected_employee_id'] = $saleInfo['employee_id'];
$data['selected_employee_name'] = $employeeInfo->first_name . ' ' . $employeeInfo->last_name;
$data['sale_info'] = $saleInfo;
$balanceDue = round($saleInfo['amount_due'] - $saleInfo['amount_tendered'] + $saleInfo['cash_refund'], totals_decimals(), PHP_ROUND_HALF_UP);
if (!$this->sale_lib->reset_cash_rounding() && $balance_due < 0) {
$balance_due = 0;
if (!$this->sale_lib->reset_cash_rounding() && $balanceDue < 0) {
$balanceDue = 0;
}
$data['payments'] = [];
@@ -1443,24 +1486,24 @@ class Sales extends Secure_Controller
}
$data['payment_type_new'] = PAYMENT_TYPE_UNASSIGNED;
$data['payment_amount_new'] = $balance_due;
$data['payment_amount_new'] = $balanceDue;
$data['balance_due'] = $balance_due != 0;
$data['balance_due'] = $balanceDue != 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);
$paymentOptions = $this->sale->get_payment_options(false);
if ($this->sale_lib->reset_cash_rounding()) {
$payment_options[lang('Sales.cash_adjustment')] = lang('Sales.cash_adjustment');
$paymentOptions[lang('Sales.cash_adjustment')] = lang('Sales.cash_adjustment');
}
$data['payment_options'] = $payment_options;
$data['payment_options'] = $paymentOptions;
$data['reference_code_payment_types'] = get_reference_code_payment_types();
// Set up a slightly modified list of payment types for new payment entry
$payment_options["--"] = lang('Common.none_selected_text');
$paymentOptions["--"] = lang('Common.none_selected_text');
$data['new_payment_options'] = $payment_options;
$data['new_payment_options'] = $paymentOptions;
return view('sales/form', $data);
}
@@ -1476,7 +1519,7 @@ class Sales extends Secure_Controller
$has_grant = $this->employee->has_grant('sales_delete', $employee_id);
if (!$has_grant) {
return $this->response->setJSON(['success' => false, 'message' => lang('Sales.not_authorized')]);
return $this->response->setStatusCode(403)->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];
@@ -1503,7 +1546,7 @@ class Sales extends Secure_Controller
$has_grant = $this->employee->has_grant('sales_delete', $employee_id);
if (!$has_grant) {
return $this->response->setJSON(['success' => false, 'message' => lang('Sales.not_authorized')]);
return $this->response->setStatusCode(403)->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];
@@ -1522,20 +1565,26 @@ class Sales extends Secure_Controller
/**
* This saves the sale from the update sale view (sales/form).
* It only updates the sales table and payments.
* @param int $sale_id
* @param int $saleId
* @return ResponseInterface
* @throws ReflectionException
*/
public function postSave(int $sale_id = NEW_ENTRY): ResponseInterface
public function postSave(int $saleId = 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');
$personId = $this->session->get('person_id');
$sale_data = [
'sale_time' => $sale_time,
if (!$this->employee->has_grant('reports_sales', $personId)) {
return $this->response->setStatusCode(403)->setJSON(['success' => false, 'message' => lang('Sales.not_authorized')]);
}
$newdate = $this->request->getPost('date', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$employeeId = $this->employee->get_logged_in_employee_info()->person_id;
$inventory = model(Inventory::class);
$dateFormatter = date_create_from_format($this->config['dateformat'] . ' ' . $this->config['timeformat'], $newdate);
$saleTime = $dateFormatter->format('Y-m-d H:i:s');
$saleData = [
'sale_time' => $saleTime,
'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),
@@ -1543,10 +1592,10 @@ class Sales extends Secure_Controller
];
// Validate reference_code for the new payment if applicable
$payment_type_new_check = $this->request->getPost('payment_type_new', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$payment_amount_new_check = $this->request->getPost('payment_amount_new');
if ($payment_type_new_check != PAYMENT_TYPE_UNASSIGNED && !empty($payment_amount_new_check)
&& in_array($payment_type_new_check, get_reference_code_payment_types())) {
$paymentTypeNewCheck = $this->request->getPost('payment_type_new', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$paymentAmountNewCheck = $this->request->getPost('payment_amount_new');
if ($paymentTypeNewCheck != PAYMENT_TYPE_UNASSIGNED && !empty($paymentAmountNewCheck)
&& in_array($paymentTypeNewCheck, get_reference_code_payment_types())) {
$min = (int)($this->config['payment_reference_code_min'] ?? 3);
$max = (int)($this->config['payment_reference_code_max'] ?? 40);
$rules = [
@@ -1562,82 +1611,82 @@ class Sales extends Secure_Controller
];
if (!$this->validate($rules, $messages)) {
$errors = $this->validator->getErrors();
return $this->response->setJSON(['success' => false, 'message' => reset($errors), 'id' => $sale_id]);
return $this->response->setJSON(['success' => false, 'message' => reset($errors), 'id' => $saleId]);
}
}
// 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"));
$reference_code = $this->request->getPost("reference_code_$i", FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?: null;
$amountTendered = 0;
$numberOfPayments = $this->request->getPost('number_of_payments', FILTER_SANITIZE_NUMBER_INT);
for ($i = 0; $i < $numberOfPayments; ++$i) {
$paymentId = $this->request->getPost("payment_id_$i", FILTER_SANITIZE_NUMBER_INT);
$paymentType = $this->request->getPost("payment_type_$i", FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$paymentAmount = parse_decimals($this->request->getPost("payment_amount_$i"));
$refundType = $this->request->getPost("refund_type_$i", FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$cashRefund = parse_decimals($this->request->getPost("refund_amount_$i"));
$referenceCode = $this->request->getPost("reference_code_$i", FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?: null;
$cash_adjustment = $payment_type == lang('Sales.cash_adjustment') ? CASH_ADJUSTMENT_TRUE : CASH_ADJUSTMENT_FALSE;
$cashAdjustment = $paymentType == lang('Sales.cash_adjustment') ? CASH_ADJUSTMENT_TRUE : CASH_ADJUSTMENT_FALSE;
if (!$cash_adjustment) {
$amount_tendered += $payment_amount - $cash_refund;
if (!$cashAdjustment) {
$amountTendered += $paymentAmount - $cashRefund;
}
// 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.
if (empty(strstr($refundType, lang('Sales.cash'))) && $cashRefund > 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;
$paymentType = $refundType;
$paymentAmount = $paymentAmount - $cashRefund;
$cashRefund = 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,
'reference_code' => $reference_code,
$saleData['payments'][] = [
'payment_id' => $paymentId,
'payment_type' => $paymentType,
'payment_amount' => $paymentAmount,
'cash_refund' => $cashRefund,
'cash_adjustment' => $cashAdjustment,
'employee_id' => $employeeId,
'reference_code' => $referenceCode,
];
}
$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);
$reference_code_new = $this->request->getPost('reference_code_new', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?: null;
$paymentId = NEW_ENTRY;
$paymentAmountNew = $this->request->getPost('payment_amount_new');
$paymentType = $this->request->getPost('payment_type_new', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$referenceCodeNew = $this->request->getPost('reference_code_new', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?: null;
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;
if ($paymentType != PAYMENT_TYPE_UNASSIGNED && !empty($paymentAmountNew)) {
$paymentAmount = parse_decimals($paymentAmountNew);
$cashRefund = 0;
if ($paymentType == lang('Sales.cash_adjustment')) {
$cashAdjustment = CASH_ADJUSTMENT_TRUE;
} else {
$cash_adjustment = CASH_ADJUSTMENT_FALSE;
$amount_tendered += $payment_amount;
$sale_info = $this->sale->getInfo($sale_id)->getRowArray();
if ($amount_tendered > $sale_info['amount_due']) {
$cash_refund = $amount_tendered - $sale_info['amount_due'];
if ($amountTendered > $saleInfo['amount_due']) {
$cashRefund = $amountTendered - $saleInfo['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,
'reference_code' => $reference_code_new,
$saleData['payments'][] = [
'payment_id' => $paymentId,
'payment_type' => $paymentType,
'payment_amount' => $paymentAmount,
'cash_refund' => $cashRefund,
'cash_adjustment' => $cashAdjustment,
'employee_id' => $employeeId,
'reference_code' => $referenceCodeNew,
];
}
$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]);
$inventory->update('POS ' . $saleId, ['trans_date' => $saleTime]); // TODO: Reflection Exception
if ($this->sale->update($saleId, $saleData)) {
return $this->response->setJSON(['success' => true, 'message' => lang('Sales.successfully_updated'), 'id' => $saleId]);
} else {
return $this->response->setJSON(['success' => false, 'message' => lang('Sales.unsuccessfully_updated'), 'id' => $sale_id]);
return $this->response->setJSON(['success' => false, 'message' => lang('Sales.unsuccessfully_updated'), 'id' => $saleId]);
}
}
+4 -9
View File
@@ -4,6 +4,7 @@ namespace App\Controllers;
use App\Models\Employee;
use App\Models\Module;
use CodeIgniter\HTTP\Exceptions\RedirectException;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Model;
use CodeIgniter\Session\Session;
@@ -39,18 +40,12 @@ class Secure_Controller extends BaseController
$config = config(OSPOS::class)->settings;
$validation = Services::validation();
if (!$this->employee->is_logged_in()) {
header("Location:" . base_url('login'));
exit();
}
$logged_in_employee_info = $this->employee->get_logged_in_employee_info();
if (
!$this->employee->has_module_grant($module_id, $logged_in_employee_info->person_id)
|| (isset($submodule_id) && !$this->employee->has_module_grant($submodule_id, $logged_in_employee_info->person_id))
) {
header("Location:" . base_url("no_access/$module_id/$submodule_id"));
exit();
throw new RedirectException("no_access/$module_id/$submodule_id");
}
// Load up global global_view_data visible to all the loaded views
@@ -144,9 +139,9 @@ class Secure_Controller extends BaseController
/**
* @param int $data_item_id
* @return false
* @return ResponseInterface|false
*/
public function postSave(int $data_item_id = -1)
public function postSave(int $data_item_id = -1): ResponseInterface|false
{
return false;
}