Merge remote-tracking branch 'OpensourcePOS/master' into plugin-system-fresh

This commit is contained in:
objecttothis committed 2026-09-07 12:29:40 +04:00
commit e581cecdd8
53 files changed
+297 -126

No files matched your search

+22 -12
View File
@@ -431,21 +431,24 @@ class Sales extends Secure_Controller
return $this->reload($data);
}
$isReturnMode = $this->sale_lib->get_mode() === 'return';
$amountTenderedRule = $isReturnMode ? 'trim|required|decimal_locale' : 'trim|required|decimal_locale|nonNegativeDecimal';
if ($paymentType === lang('Sales.giftcard')) {
$rules = ['amount_tendered' => 'trim|required|integer']; //For giftcards, amount_tendered becomes the giftcard number which must be an integer
$rules = ['amount_tendered' => 'trim|required|integer'];
$messages = ['amount_tendered' => lang('Sales.must_enter_numeric_giftcard')];
} elseif (in_array($paymentType, get_reference_code_payment_types())) {
$min = (int)($this->config['payment_reference_code_min'] ?? 3);
$max = (int)($this->config['payment_reference_code_max'] ?? 20);
$rules = [
'amount_tendered' => 'trim|required|decimal_locale|nonNegativeDecimal',
'amount_tendered' => $amountTenderedRule,
'reference_code' => "trim|required|alpha_numeric|min_length[$min]|max_length[$max]",
];
$messages = [
'amount_tendered' => [
'required' => lang('Sales.must_enter_numeric'),
'decimal_locale' => lang('Sales.must_enter_numeric'),
'nonNegativeDecimal' => lang('Sales.negative_amount_invalid'),
'nonNegativeDecimal' => lang('Sales.negative_amount_tendered'),
],
'reference_code' => [
'required' => lang('Sales.must_enter_reference_code'),
@@ -455,12 +458,12 @@ class Sales extends Secure_Controller
],
];
} else {
$rules = ['amount_tendered' => 'trim|required|decimal_locale|nonNegativeDecimal'];
$rules = ['amount_tendered' => $amountTenderedRule];
$messages = [
'amount_tendered' => [
'required' => lang('Sales.must_enter_numeric'),
'decimal_locale' => lang('Sales.must_enter_numeric'),
'nonNegativeDecimal' => lang('Sales.negative_amount_invalid'),
'nonNegativeDecimal' => lang('Sales.negative_amount_tendered'),
],
];
}
@@ -1733,7 +1736,7 @@ class Sales extends Secure_Controller
{
$sale_id = $this->sale_lib->get_sale_id();
if ($sale_id != NEW_ENTRY && $sale_id != '') {
$sale_type = $this->sale_lib->get_sale_type();
$sale_type = $this->sale_lib->getSaleType();
if ($this->config['dinner_table_enable']) {
$dinner_table = $this->sale_lib->get_dinner_table();
@@ -1787,7 +1790,7 @@ class Sales extends Secure_Controller
$invoice_number = $this->sale_lib->get_invoice_number();
$work_order_number = $this->sale_lib->get_work_order_number();
$quote_number = $this->sale_lib->get_quote_number();
$sale_type = $this->sale_lib->get_sale_type();
$sale_type = $this->sale_lib->getSaleType();
if ($sale_type == '') {
$sale_type = SALE_TYPE_POS;
@@ -1837,15 +1840,22 @@ class Sales extends Secure_Controller
*/
public function postUnsuspend(): ResponseInterface|string
{
$sale_id = $this->request->getPost('suspended_sale_id', FILTER_SANITIZE_NUMBER_INT);
$this->sale_lib->clear_all();
$personId = $this->session->get('person_id');
if ($sale_id > 0) {
$this->sale_lib->copy_entire_sale($sale_id);
if (!$this->employee->has_grant('reports_sales', $personId)) {
return $this->response->setStatusCode(403)
->setJSON(['success' => false, 'message' => lang('Sales.not_authorized')]);
}
$saleId = $this->request->getPost('suspended_sale_id', FILTER_SANITIZE_NUMBER_INT);
if ($saleId > 0 && $this->sale->getSaleStatus($saleId) == SUSPENDED) {
$this->sale_lib->clear_all();
$this->sale_lib->copy_entire_sale($saleId);
}
// Set current register mode to reflect that of unsuspended order type
$this->change_register_mode($this->sale_lib->get_sale_type());
$this->change_register_mode($this->sale_lib->getSaleType());
return $this->reload();
}
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'يجب إدخال رقم للمبلغ الفعلى المدفوع.',
'must_enter_numeric_giftcard' => 'رقم بطاقة الهدية يجب أن يكون رقم.',
'must_enter_reference_code' => 'يجب إدخال رقم المرجع/الاسترداد.',
'negative_amount_invalid' => 'لا يمكن أن يكون المبلغ المدفوع سالبًا.',
'negative_amount_tendered' => 'لا يمكن أن يكون المبلغ المدفوع سالبًا.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'يجب إدخال رقم للمبلغ الفعلى المدفوع.',
'must_enter_numeric_giftcard' => 'رمز بطاقة الهدية يجب أن يكتون ارقام فقط.',
'must_enter_reference_code' => 'يجب إدخال رقم المرجع/الاسترداد.',
'negative_amount_invalid' => 'لا يمكن أن يكون المبلغ المدفوع سالبًا.',
'negative_amount_tendered' => 'لا يمكن أن يكون المبلغ المدفوع سالبًا.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Ödəniş məbləği rəqəm ilə olmalıdır.',
'must_enter_numeric_giftcard' => 'Hədiyyə Kartın nömrəsi rəqəmlə olmalıdır.',
'must_enter_reference_code' => 'İstinad/Axtarış nömrəsi daxil edilməlidir.',
'negative_amount_invalid' => 'Ödənilən məbləğ mənfi ola bilməz.',
'negative_amount_tendered' => 'Ödənilən məbləğ mənfi ola bilməz.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Сумата Предложена трябва да е число.',
'must_enter_numeric_giftcard' => 'Gift Card номера трябва да бъде число.',
'must_enter_reference_code' => 'Трябва да се въведе референтен/извличащ номер.',
'negative_amount_invalid' => 'Предоставената сума не може да бъде отрицателна.',
'negative_amount_tendered' => 'Предоставената сума не може да бъде отрицателна.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Ponuđeni iznos mora biti broj.',
'must_enter_numeric_giftcard' => 'Broj poklon kartice mora biti broj.',
'must_enter_reference_code' => 'Referentni/broj za preuzimanje mora biti unesen.',
'negative_amount_invalid' => 'Ponuđeni iznos ne može biti negativan.',
'negative_amount_tendered' => 'Ponuđeni iznos ne može biti negativan.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'بڕی پێشکەشکراو دەبێت ژمارەیەک بێت.',
'must_enter_numeric_giftcard' => 'ژمارەی کارتی دیاری دەبێت ژمارەیەک بێت.',
'must_enter_reference_code' => 'ژمارەی مەرجع/وەرگرتن دەبێت بنووسرێت.',
'negative_amount_invalid' => 'بڕی پێشکەشکراو ناتوانێت نەرێنی بێت.',
'negative_amount_tendered' => 'بڕی پێشکەشکراو ناتوانێت نەرێنی بێت.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => '',
'must_enter_numeric_giftcard' => 'Číslo dárkového poukazu musí být číslo.',
'must_enter_reference_code' => 'Je nutné zadat referenční/vyhledávací číslo.',
'negative_amount_invalid' => 'Uhrazená částka nemůže být záporná.',
'negative_amount_tendered' => 'Uhrazená částka nemůže být záporná.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => '',
'must_enter_numeric_giftcard' => '',
'must_enter_reference_code' => 'Reference-/hentningsnummer skal angives.',
'negative_amount_invalid' => 'Det modtagne beløb kan ikke være negativt.',
'negative_amount_tendered' => 'Det modtagne beløb kan ikke være negativt.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Eingabe muss eine Zahl sein',
'must_enter_numeric_giftcard' => 'Gutschein-Nr. muss eine Zahl sein',
'must_enter_reference_code' => 'Referenz-/Abrufnummer muss eingegeben werden.',
'negative_amount_invalid' => 'Der erhaltene Betrag darf nicht negativ sein.',
'negative_amount_tendered' => 'Der erhaltene Betrag darf nicht negativ sein.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Eingabe muss eine Zahl sein.',
'must_enter_numeric_giftcard' => 'Gutschein-Nr. muss eine Zahl sein.',
'must_enter_reference_code' => 'Referenz-/Abrufnummer muss eingegeben werden.',
'negative_amount_invalid' => 'Der erhaltene Betrag darf nicht negativ sein.',
'negative_amount_tendered' => 'Der erhaltene Betrag darf nicht negativ sein.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Το Ποσό Είσπραξης πρέπει να είναι Αριθμός.',
'must_enter_numeric_giftcard' => 'Ο Αριθμός της Δωροκάρτας πρέπει να είναι αριθμός.',
'must_enter_reference_code' => 'Πρέπει να εισαχθεί αριθμός αναφοράς/ανάκτησης.',
'negative_amount_invalid' => 'Το ποσό είσπραξης δεν μπορεί να είναι αρνητικό.',
'negative_amount_tendered' => 'Το ποσό είσπραξης δεν μπορεί να είναι αρνητικό.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -132,7 +132,7 @@ return [
'must_enter_numeric' => 'Amount Tendered must be a number.',
'must_enter_numeric_giftcard' => 'Gift Card Number must be a number.',
'must_enter_reference_code' => 'Reference/Retrieval Number must be entered.',
'negative_amount_invalid' => 'Amount Tendered cannot be negative.',
'negative_amount_tendered' => 'Amount Tendered cannot be negative.',
'negative_discount_invalid' => 'Discount cannot be negative.',
'negative_price_invalid' => 'Price cannot be negative.',
'negative_quantity_invalid' => 'Quantity cannot be negative.',
+1 -1
View File
@@ -132,7 +132,7 @@ return [
'must_enter_numeric' => 'Amount Tendered must be a number.',
'must_enter_numeric_giftcard' => 'Gift Card Number must be a number.',
'must_enter_reference_code' => 'Reference/Retrieval Number must be entered.',
'negative_amount_invalid' => 'Amount Tendered cannot be negative.',
'negative_amount_tendered' => 'Amount Tendered cannot be negative.',
'negative_discount_invalid' => 'Discount cannot be negative.',
'negative_price_invalid' => 'Price cannot be negative.',
'negative_quantity_invalid' => 'Quantity cannot be negative.',
+1 -1
View File
@@ -132,7 +132,7 @@ return [
'must_enter_numeric' => 'Cantidad Recibida debe ser número.',
'must_enter_numeric_giftcard' => 'Número de Tarjeta de Regalo debe ser número.',
'must_enter_reference_code' => 'Se debe ingresar el número de referencia/recuperación.',
'negative_amount_invalid' => 'La cantidad recibida no puede ser negativa.',
'negative_amount_tendered' => 'La cantidad recibida no puede ser negativa.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -132,7 +132,7 @@ return [
'must_enter_numeric' => 'Cantidad recibida debe ser un número.',
'must_enter_numeric_giftcard' => 'Número de Tarjeta de Regalo debe ser un número.',
'must_enter_reference_code' => 'Se debe ingresar el número de referencia/recuperación.',
'negative_amount_invalid' => 'La cantidad recibida no puede ser negativa.',
'negative_amount_tendered' => 'La cantidad recibida no puede ser negativa.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'مقدار پیشنهادی باید یک عدد باشد.',
'must_enter_numeric_giftcard' => 'شماره کارت هدیه باید یک عدد باشد.',
'must_enter_reference_code' => 'شماره مرجع/بازیابی باید وارد شود.',
'negative_amount_invalid' => 'مبلغ مناقصه نمی‌تواند منفی باشد.',
'negative_amount_tendered' => 'مبلغ مناقصه نمی‌تواند منفی باشد.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -132,7 +132,7 @@ return [
'must_enter_numeric' => 'Veuillez entrer une valeur numérique pour la somme.',
'must_enter_numeric_giftcard' => 'Veuillez entrer une valeur numérique pour le numéro de carte.',
'must_enter_reference_code' => 'Le numéro de référence/récupération doit être saisi.',
'negative_amount_invalid' => 'Le montant présenté ne peut pas être négatif.',
'negative_amount_tendered' => 'Le montant présenté ne peut pas être négatif.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'סכום ההצעה חייב להיות מספר.',
'must_enter_numeric_giftcard' => 'מספר כרטיס המתנה חייב להיות מספר.',
'must_enter_reference_code' => 'יש להזין מספר אסמכתא/אחזור.',
'negative_amount_invalid' => 'סכום ההצעה לא יכול להיות שלילי.',
'negative_amount_tendered' => 'סכום ההצעה לא יכול להיות שלילי.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Morate unijeti numeričku vrijednost za količinu',
'must_enter_numeric_giftcard' => 'Morate unijeti numeričku vrijednost za poklon bon',
'must_enter_reference_code' => 'Referentni/broj za preuzimanje mora biti unesen.',
'negative_amount_invalid' => 'Ponuđeni iznos ne može biti negativan.',
'negative_amount_tendered' => 'Ponuđeni iznos ne može biti negativan.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Számot kell megadnia a megrendelt mennyiséghez',
'must_enter_numeric_giftcard' => 'Vásárlási utalvány számát adja meg',
'must_enter_reference_code' => 'A hivatkozási/visszakeresési számot meg kell adni.',
'negative_amount_invalid' => 'Az összeg nem lehet negatív.',
'negative_amount_tendered' => 'Az összeg nem lehet negatív.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => '',
'must_enter_numeric_giftcard' => '',
'must_enter_reference_code' => 'Պետք է մուտքագրվի հղման/որոնման համարը։',
'negative_amount_invalid' => 'Վճարված գումարը չի կարող բացասական լինել.',
'negative_amount_tendered' => 'Վճարված գումարը չի կարող բացասական լինել.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Nilai yang dimasukkan harus berupa angka.',
'must_enter_numeric_giftcard' => 'Nomor Gift Card harus berupa angka.',
'must_enter_reference_code' => 'Nomor referensi/pengambilan harus dimasukkan.',
'negative_amount_invalid' => 'Nilai pembayaran tidak boleh negatif.',
'negative_amount_tendered' => 'Nilai pembayaran tidak boleh negatif.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Import Offerto deve essere un numero.',
'must_enter_numeric_giftcard' => 'Numero Carta Regalo deve essere un numero.',
'must_enter_reference_code' => 'Il numero di riferimento/recupero deve essere inserito.',
'negative_amount_invalid' => 'L\'importo offerto non può essere negativo.',
'negative_amount_tendered' => 'L\'importo offerto non può essere negativo.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -132,7 +132,7 @@ return [
'must_enter_numeric' => '',
'must_enter_numeric_giftcard' => '',
'must_enter_reference_code' => 'მიუთითეთ საცნობარო/მოძიების ნომერი.',
'negative_amount_invalid' => 'გადახდილი თანხა არ შეიძლება იყოს უარყოფითი.',
'negative_amount_tendered' => 'გადახდილი თანხა არ შეიძლება იყოს უარყოფითი.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => '',
'must_enter_numeric_giftcard' => '',
'must_enter_reference_code' => 'លេខយោង/ទាញយកត្រូវតែបញ្ចូល។',
'negative_amount_invalid' => 'ទឹកប្រាក់បានមកពីការបង់មិនអាចជាចំនួនអវិជ្ជមានបានទេ។',
'negative_amount_tendered' => 'ទឹកប្រាក់បានមកពីការបង់មិនអាចជាចំនួនអវិជ្ជមានបានទេ។',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Amount Tendered must be a number.',
'must_enter_numeric_giftcard' => 'Gift Card Number must be a number.',
'must_enter_reference_code' => 'ຕ້ອງປ້ອນໝາຍເລກອ້າງອີງ/ດຶງຂໍ້ມູນ.',
'negative_amount_invalid' => 'ຈຳນວນເງິນທີ່ຈ່າຍບໍ່ສາມາດເປັນຄ່າລົບໄດ້.',
'negative_amount_tendered' => 'ຈຳນວນເງິນທີ່ຈ່າຍບໍ່ສາມາດເປັນຄ່າລົບໄດ້.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => '',
'must_enter_numeric_giftcard' => '',
'must_enter_reference_code' => 'റഫറൻസ്/റിട്രീവൽ നമ്പർ നൽകണം.',
'negative_amount_invalid' => 'നൽകിയ തുക നെഗറ്റീവ് ആകാൻ പാടില്ല.',
'negative_amount_tendered' => 'നൽകിയ തുക നെഗറ്റീവ് ആകാൻ പാടില്ല.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => '',
'must_enter_numeric_giftcard' => '',
'must_enter_reference_code' => 'Referanse-/hentingsnummer må angis.',
'negative_amount_invalid' => 'Det mottatte beløpet kan ikke være negativt.',
'negative_amount_tendered' => 'Det mottatte beløpet kan ikke være negativt.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Het ontvangen bedrag moet een numerieke waarde zijn.',
'must_enter_numeric_giftcard' => 'Er moet een geldige code worden ingevuld voor de cadeaubon.',
'must_enter_reference_code' => 'Referentie-/ophaalnummer moet worden ingevoerd.',
'negative_amount_invalid' => 'Het ontvangen bedrag mag niet negatief zijn.',
'negative_amount_tendered' => 'Het ontvangen bedrag mag niet negatief zijn.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Betaald bedrag moet een getal zijn.',
'must_enter_numeric_giftcard' => 'Cadeaubonnummer moet een getal zijn.',
'must_enter_reference_code' => 'Referentie-/ophaalnummer moet worden ingevoerd.',
'negative_amount_invalid' => 'Het betaalde bedrag mag niet negatief zijn.',
'negative_amount_tendered' => 'Het betaalde bedrag mag niet negatief zijn.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => '',
'must_enter_numeric_giftcard' => '',
'must_enter_reference_code' => 'Należy podać numer referencyjny/pobierania.',
'negative_amount_invalid' => 'Otrzymana kwota nie może być ujemna.',
'negative_amount_tendered' => 'Otrzymana kwota nie może być ujemna.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Deve entrar valor numérico para montante da proposta apresentada.',
'must_enter_numeric_giftcard' => 'Deve entrar valor numérico para o número de cartão presente.',
'must_enter_reference_code' => 'O número de referência/recuperação deve ser informado.',
'negative_amount_invalid' => 'O valor pago não pode ser negativo.',
'negative_amount_tendered' => 'O valor pago não pode ser negativo.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Valoare ofertata trebuie sa fie numerica.',
'must_enter_numeric_giftcard' => 'Numar Card Cadou trebuie sa fie numeric.',
'must_enter_reference_code' => 'Numărul de referință/recuperare trebuie introdus.',
'negative_amount_invalid' => 'Suma oferită nu poate fi negativă.',
'negative_amount_tendered' => 'Suma oferită nu poate fi negativă.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Предложенная сумма должна быть числом.',
'must_enter_numeric_giftcard' => 'Номер подарочной карты должен быть числом.',
'must_enter_reference_code' => 'Необходимо ввести справочный/поисковый номер.',
'negative_amount_invalid' => 'Предложенная сумма не может быть отрицательной.',
'negative_amount_tendered' => 'Предложенная сумма не может быть отрицательной.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Belopp som anslås måste vara ett nummer.',
'must_enter_numeric_giftcard' => 'Presentkortets nummer måste vara ett nummer.',
'must_enter_reference_code' => 'Referens-/hämtningsnummer måste anges.',
'negative_amount_invalid' => 'Det upplagda beloppet kan inte vara negativt.',
'negative_amount_tendered' => 'Det upplagda beloppet kan inte vara negativt.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Kiasi Kilicholipwa lazima kiwe Namba.',
'must_enter_numeric_giftcard' => 'Namba ya Kadi ya Zawadi lazima iwe Namba.',
'must_enter_reference_code' => 'Nambari ya Kumbukumbu/Upatikanaji lazima iingizwe.',
'negative_amount_invalid' => 'Kiasi kilicholipwa hakiwezi kuwa hasi.',
'negative_amount_tendered' => 'Kiasi kilicholipwa hakiwezi kuwa hasi.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Kiasi Kilicholipwa lazima kiwe Namba.',
'must_enter_numeric_giftcard' => 'Namba ya Kadi ya Zawadi lazima iwe Namba.',
'must_enter_reference_code' => 'Nambari ya Kumbukumbu/Upatikanaji lazima iingizwe.',
'negative_amount_invalid' => 'Kiasi kilicholipwa hakiwezi kuwa hasi.',
'negative_amount_tendered' => 'Kiasi kilicholipwa hakiwezi kuwa hasi.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'தொகை ஒரு எண்ணாக இருக்க வேண்டும்.',
'must_enter_numeric_giftcard' => 'பரிசு அட்டை எண் ஒரு எண்ணாக இருக்க வேண்டும்.',
'must_enter_reference_code' => 'குறிப்பு/மீட்டெடுப்பு எண் உள்ளிட வேண்டும்.',
'negative_amount_invalid' => 'கொடுத்த தொகை எதிர்மறையாக இருக்கக்கூடாது.',
'negative_amount_tendered' => 'கொடுத்த தொகை எதிர்மறையாக இருக்கக்கூடாது.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'จำนวนที่ถุกประมูลต้องใส่ข้อมุลที่เปนตัวเลข',
'must_enter_numeric_giftcard' => 'เลขที่บัตรของขวัญ ต้องใส่ตัวเลขเท่านั้น',
'must_enter_reference_code' => 'ต้องระบุหมายเลขอ้างอิง/การดึงข้อมูล',
'negative_amount_invalid' => 'ชำระเข้ามาไม่สามารถเป็นค่าติดลบได้',
'negative_amount_tendered' => 'ชำระเข้ามาไม่สามารถเป็นค่าติดลบได้',
'negative_discount_invalid' => 'ส่วนลดไม่สามารถเป็นค่าติดลบได้',
'negative_price_invalid' => 'ราคาไม่สามารถเป็นค่าติดลบได้',
'negative_quantity_invalid' => 'จำนวนไม่สามารถเป็นค่าติดลบได้',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Amount Tendered must be a number.',
'must_enter_numeric_giftcard' => 'Gift Card Number must be a number.',
'must_enter_reference_code' => 'Ang Numero ng Sanggunian/Pagkuha ay dapat ipasok.',
'negative_amount_invalid' => 'Hindi maaaring negatibo ang halagang ibinayad.',
'negative_amount_tendered' => 'Hindi maaaring negatibo ang halagang ibinayad.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Ödenen Tutar sayı olmalıdır.',
'must_enter_numeric_giftcard' => 'Hediye Çeki Numarası sayı olmalıdır.',
'must_enter_reference_code' => 'Referans/Alım numarası girilmelidir.',
'negative_amount_invalid' => 'Ödenen tutar negatif olamaz.',
'negative_amount_tendered' => 'Ödenen tutar negatif olamaz.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Введена сума повинна бути числом.',
'must_enter_numeric_giftcard' => 'Номер подарункової картки повинен бути цифровим.',
'must_enter_reference_code' => 'Необхідно ввести довідковий/пошуковий номер.',
'negative_amount_invalid' => 'Запропонована сума не може бути від\'ємною.',
'negative_amount_tendered' => 'Запропонована сума не може бути від\'ємною.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => '',
'must_enter_numeric_giftcard' => '',
'must_enter_reference_code' => 'حوالہ/بازیابی نمبر درج کرنا ضروری ہے۔',
'negative_amount_invalid' => 'ادا کی گئی رقم منفی نہیں ہو سکتی۔',
'negative_amount_tendered' => 'ادا کی گئی رقم منفی نہیں ہو سکتی۔',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => 'Số tiền thanh toán phải là dạng số.',
'must_enter_numeric_giftcard' => 'Số Thẻ quà tặng phải là dạng số.',
'must_enter_reference_code' => 'Số tham chiếu/truy xuất phải được nhập.',
'negative_amount_invalid' => 'Số tiền thanh toán không được là số âm.',
'negative_amount_tendered' => 'Số tiền thanh toán không được là số âm.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => '已收帳款必須輸入數值',
'must_enter_numeric_giftcard' => '禮金券編號必須輸入數值',
'must_enter_reference_code' => '必须输入参考/检索编号。',
'negative_amount_invalid' => '已收帐款不能为负数。',
'negative_amount_tendered' => '已收帐款不能为负数。',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -131,7 +131,7 @@ return [
'must_enter_numeric' => '已收帳款必須輸入數值.',
'must_enter_numeric_giftcard' => '禮金券編號必須輸入數值.',
'must_enter_reference_code' => '必須輸入參考/檢索編號。',
'negative_amount_invalid' => '已收帳款不能為負數。',
'negative_amount_tendered' => '已收帳款不能為負數。',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
+1 -1
View File
@@ -147,7 +147,7 @@ class Barcode_lib
$barcode = $this->generate_barcode($item, $barcode_config);
$display_table = '<table>';
$display_table .= '<tr><td style="text-align: center;">' . $this->manageDisplayLayout($barcode_config['barcode_first_row'], $item, $barcode_config) . '</td></tr>';
$display_table .= '<tr><td style="text-align: center;"><div class="barcode">'.$barcode.'</div></td></tr>';
$display_table .= '<tr><td style="text-align: center;"><div class="barcode">' . $barcode . '</div></td></tr>';
$display_table .= '<tr><td style="text-align: center;">' . $this->manageDisplayLayout($barcode_config['barcode_second_row'], $item, $barcode_config) . '</td></tr>';
$display_table .= '<tr><td style="text-align: center;">' . $this->manageDisplayLayout($barcode_config['barcode_third_row'], $item, $barcode_config) . '</td></tr>';
$display_table .= '</table>';
+2 -2
View File
@@ -331,9 +331,9 @@ class Sale_lib
/**
* @return int|null
*/
public function get_sale_type(): ?int
public function getSaleType(): ?int
{
return $this->session->get('sale_type', 0);
return $this->session->get('sale_type') ?? 0;
}
/**
+1 -4
View File
@@ -311,12 +311,9 @@ class Giftcard extends Model
$builder->update(['value' => $value]);
}
/**
* Atomically decrements a gift card's value, failing if the balance is insufficient
*/
public function decrementGiftcardValue(string $giftcardNumber, float $amount): bool
{
if ($amount <= 0) {
if ($amount <= 0.0) {
return false;
}
+5 -3
View File
@@ -805,7 +805,7 @@ class Sale extends Model
// Start a transaction to assure data integrity
$this->db->transStart();
$sale_status = $this->get_sale_status($sale_id);
$sale_status = $this->getSaleStatus($sale_id);
if ($update_inventory && $sale_status == COMPLETED) {
// Defect, not all item deletions will be undone?
@@ -1225,12 +1225,14 @@ class Sale extends Model
/**
* Gets the sale status for the selected sale
*/
public function get_sale_status(int $sale_id): int
public function getSaleStatus(int $sale_id): ?int
{
$builder = $this->db->table('sales');
$builder->where('sale_id', $sale_id);
return $builder->get()->getRow()->sale_status;
$row = $builder->get()->getRow();
return $row === null ? null : $row->sale_status;
}
/**
+155 -58
View File
@@ -10,20 +10,26 @@ use CodeIgniter\Config\Services;
use App\Models\Employee;
use Config\Database;
use Tests\Support\ItemFixtureTrait;
use Tests\Support\SaleFixtureTrait;
/**
* Regression tests for GHSA-3xf6-8fmq-44wg.
*
* A cashier holding only the base "sales" grant (no "reports_sales") must
* not be able to reach the per-sale endpoints that getManage() gates
* behind reports_sales: getRow, getEdit, postSave, getReceipt, getInvoice,
* getSendPdf, getSendReceipt.
* behind reports_sales: getSearch, getRow, getEdit, postSave, getReceipt,
* getInvoice, getSendPdf, getSendReceipt.
*
* Also covers: getSearch() (the AJAX endpoint that
* supplies every row of the Sales Takings list) was the one sibling that
* returned the full ledger and was missing the reports_sales check.
*/
class SalesControllerTest extends CIUnitTestCase
{
use DatabaseTestTrait;
use FeatureTestTrait;
use ItemFixtureTrait;
use SaleFixtureTrait;
protected $migrate = true;
protected $migrateOnce = true;
@@ -214,49 +220,6 @@ class SalesControllerTest extends CIUnitTestCase
]);
}
/**
* Inserts a minimal completed sale row directly, bypassing Sale::save_value()
* (which requires a full cart/inventory/tax pipeline unrelated to this
* authorization check). Sale::get_info() inner-joins sales_items, so a
* matching item/sales_items row is required for the sale to be found.
*/
protected function createSale(int $employeeId): int
{
$unique = uniqid();
$db = Database::connect();
$db->table('items')->insert([
'name' => "Test Item $unique",
'category' => 'Test',
'description' => 'Test item',
'cost_price' => 1,
'unit_price' => 1,
'item_number' => "TEST-$unique",
]);
$itemId = (int) $db->insertID();
$db->table('sales')->insert([
'sale_time' => date('Y-m-d H:i:s'),
'customer_id' => null,
'employee_id' => $employeeId,
'comment' => 'test sale',
'invoice_number' => null,
]);
$saleId = (int) $db->insertID();
$db->table('sales_items')->insert([
'sale_id' => $saleId,
'item_id' => $itemId,
'line' => 1,
'quantity_purchased' => 1,
'item_cost_price' => 1,
'item_unit_price' => 1,
'item_location' => 1,
]);
return $saleId;
}
/**
* Seeds a single cart line directly in the session, mirroring the shape
* Sale_lib::add_item() produces, so tests can target postEditItem's
@@ -310,6 +273,20 @@ class SalesControllerTest extends CIUnitTestCase
$this->assertFalse($result['success']);
}
public function testCashierWithoutReportsSalesCannotGetSearch(): void
{
$cashierId = $this->createCashierEmployee();
$this->createSale($cashierId);
$this->loginAs($cashierId);
$response = $this->get('/sales/search');
$response->assertStatus(403);
$result = json_decode($response->getJSON(), true);
$this->assertFalse($result['success']);
$this->assertSame(lang('Sales.not_authorized'), $result['message']);
}
public function testCashierWithoutReportsSalesCannotGetEdit(): void
{
$cashierId = $this->createCashierEmployee();
@@ -395,19 +372,6 @@ class SalesControllerTest extends CIUnitTestCase
$this->assertSame(lang('Sales.not_authorized'), $result['message']);
}
public function testCashierWithoutReportsSalesCannotGetSearch(): void
{
$cashierId = $this->createCashierEmployee();
$this->createSale($cashierId);
$this->loginAs($cashierId);
$response = $this->get('/sales/search');
$response->assertStatus(403);
$result = json_decode($response->getJSON(), true);
$this->assertFalse($result['success']);
}
public function testEmployeeWithReportsSalesCanGetSearch(): void
{
$supervisorId = $this->createReportsSalesEmployee();
@@ -519,6 +483,101 @@ class SalesControllerTest extends CIUnitTestCase
$this->assertEquals('0.01', $cart[1]['price']);
}
public function testPostAddPaymentRejectsNegativeAmountTendered(): void
{
$cashierId = $this->createCashierWithoutChangePriceGrant();
$this->loginAs($cashierId);
$itemId = $this->createTestItem();
$this->seedCartLine(1, '1.00', $itemId);
$forgedPaymentType = lang('Sales.giftcard') . ':AUDIT-100';
$response = $this->post('/sales/addPayment', [
'payment_type' => $forgedPaymentType,
'amount_tendered' => '-500',
]);
$response->assertStatus(200);
$response->assertSee(lang('Sales.must_enter_numeric'));
// ... and the negative payment must NOT have been added to the cart.
$session = Services::session();
$payments = $session->get('sales_payments');
$this->assertArrayNotHasKey($forgedPaymentType, (array) $payments);
}
public function testPostAddPaymentRejectsMalformedAmountTendered(): void
{
$cashierId = $this->createCashierWithoutChangePriceGrant();
$this->loginAs($cashierId);
$itemId = $this->createTestItem();
$this->seedCartLine(1, '1.00', $itemId);
$response = $this->post('/sales/addPayment', [
'payment_type' => lang('Sales.cash'),
'amount_tendered' => 'ABC123',
]);
$response->assertStatus(200);
$response->assertSee(lang('Sales.must_enter_numeric'));
$session = Services::session();
$payments = $session->get('sales_payments');
$this->assertArrayNotHasKey(lang('Sales.cash'), (array) $payments);
}
public function testCashierWithoutReportsSalesCannotUnsuspend(): void
{
$victimId = $this->createReportsSalesEmployee();
$saleId = $this->createSuspendedSale($victimId);
$cashierId = $this->createCashierEmployee();
$this->loginAs($cashierId);
$response = $this->post('/sales/unsuspend', [
'suspended_sale_id' => $saleId,
]);
$response->assertStatus(403);
$result = json_decode($response->getJSON(), true);
$this->assertFalse($result['success']);
$session = Services::session();
$this->assertNotEquals($saleId, $session->get('sale_id'));
}
public function testEmployeeWithReportsSalesCannotUnsuspendCompletedSale(): void
{
$victimId = $this->createReportsSalesEmployee();
$saleId = $this->createSale($victimId);
$supervisorId = $this->createReportsSalesEmployee();
$this->loginAs($supervisorId);
$this->post('/sales/unsuspend', [
'suspended_sale_id' => $saleId,
]);
$session = Services::session();
$this->assertNotEquals($saleId, $session->get('sale_id'));
}
public function testEmployeeWithReportsSalesCanUnsuspendSale(): void
{
$ownerId = $this->createReportsSalesEmployee();
$saleId = $this->createSuspendedSale($ownerId);
$supervisorId = $this->createReportsSalesEmployee();
$this->loginAs($supervisorId);
$this->post('/sales/unsuspend', [
'suspended_sale_id' => $saleId,
]);
$session = Services::session();
$this->assertEquals($saleId, $session->get('sale_id'));
}
protected function createGiftcard(float $value): int
{
$giftcardNumber = random_int(1000000, 9999999);
@@ -719,4 +778,42 @@ class SalesControllerTest extends CIUnitTestCase
$this->assertStringNotContainsString('<svg onload', $body);
$this->assertStringContainsString('&lt;svg', $body);
}
public function testPostUnsuspendRejectsNonSuspendedSaleWithoutClearingActiveCart(): void
{
$employeeId = $this->createReportsSalesEmployee();
$itemId = $this->createTestItem(HAS_NO_STOCK);
$notSuspendedSaleId = $this->createSale($employeeId);
$this->loginAs($employeeId);
$this->seedCartLine(1, '1.00', $itemId);
$this->withSession(array_merge($this->session, ['sale_id' => NEW_ENTRY]));
$this->post('/sales/unsuspend', [
'suspended_sale_id' => (string) $notSuspendedSaleId,
]);
$cart = Services::session()->get('sales_cart');
$this->assertNotEmpty($cart);
$this->assertArrayHasKey(1, $cart);
$this->assertSame($itemId, $cart[1]['item_id']);
$this->assertSame(NEW_ENTRY, Services::session()->get('sale_id'));
}
public function testPostUnsuspendReplacesCartForValidSuspendedSale(): void
{
$employeeId = $this->createReportsSalesEmployee();
$itemId = $this->createTestItem(HAS_NO_STOCK);
$suspendedSaleId = $this->createSuspendedSale($employeeId);
$this->loginAs($employeeId);
$this->seedCartLine(1, '1.00', $itemId);
$this->post('/sales/unsuspend', [
'suspended_sale_id' => (string) $suspendedSaleId,
]);
$this->assertSame($suspendedSaleId, Services::session()->get('sale_id'));
$cart = Services::session()->get('sales_cart');
$this->assertNotEmpty($cart);
$this->assertNotSame($itemId, array_values($cart)[0]['item_id']);
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace Tests\Support;
use App\Models\Sale;
use Config\Database;
trait SaleFixtureTrait
{
/**
* Inserts a minimal completed sale row directly, bypassing Sale::save_value()
* (which requires a full cart/inventory/tax pipeline unrelated to authorization
* checks). Sale::get_info() inner-joins sales_items, so a matching item/
* sales_items row is required for the sale to be found.
*/
protected function createSale(int $employeeId): int
{
$unique = uniqid();
$db = Database::connect();
$db->table('items')->insert([
'name' => "Test Item $unique",
'category' => 'Test',
'description' => 'Test item',
'cost_price' => 1,
'unit_price' => 1,
'item_number' => "TEST-$unique",
]);
$itemId = (int) $db->insertID();
$db->table('sales')->insert([
'sale_time' => date('Y-m-d H:i:s'),
'customer_id' => null,
'employee_id' => $employeeId,
'comment' => 'test sale',
'invoice_number' => null,
]);
$saleId = (int) $db->insertID();
$db->table('sales_items')->insert([
'sale_id' => $saleId,
'item_id' => $itemId,
'line' => 1,
'quantity_purchased' => 1,
'item_cost_price' => 1,
'item_unit_price' => 1,
'item_location' => 1,
]);
return $saleId;
}
/**
* Same as createSale(), but flips the sale to SUSPENDED afterward so
* postUnsuspend's status check can be exercised.
*/
protected function createSuspendedSale(int $employeeId): int
{
$saleId = $this->createSale($employeeId);
model(Sale::class)->update_sale_status($saleId, SUSPENDED);
return $saleId;
}
}