From 3bec7d5c924cb7f0afe0b4632968ff45cb5fbab1 Mon Sep 17 00:00:00 2001 From: Vighnesh Nilajakar <84033298+VighneshNilajakar@users.noreply.github.com> Date: Mon, 7 Sep 2026 01:22:22 +0530 Subject: [PATCH 1/4] fix(barcode): resolve string interpolation issue in barcode display html (#4692) Fixes an issue in Barcode_lib.php where $barcode was enclosed in single quotes, preventing string interpolation and rendering the literal string "$barcode" on the item barcode generation page instead of the barcode graphic. Changes Made : Refactored the string assignment in app/Libraries/Barcode_lib.php to properly concatenate $barcode. How to Test: 1. Open Items in OSPOS. 2. Select any item and click Generate Barcodes. 3. Verify that the rendered barcode image displays correctly rather than showing literal text. --- app/Libraries/Barcode_lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Libraries/Barcode_lib.php b/app/Libraries/Barcode_lib.php index e7f96a47d..1b62e1c4d 100644 --- a/app/Libraries/Barcode_lib.php +++ b/app/Libraries/Barcode_lib.php @@ -147,7 +147,7 @@ class Barcode_lib $barcode = $this->generate_barcode($item, $barcode_config); $display_table = ''; $display_table .= ''; - $display_table .= ''; + $display_table .= ''; $display_table .= ''; $display_table .= ''; $display_table .= '
' . $this->manageDisplayLayout($barcode_config['barcode_first_row'], $item, $barcode_config) . '
'.$barcode.'
' . $barcode . '
' . $this->manageDisplayLayout($barcode_config['barcode_second_row'], $item, $barcode_config) . '
' . $this->manageDisplayLayout($barcode_config['barcode_third_row'], $item, $barcode_config) . '
'; From bdbc6d9cf1c2d55cd8eb1ce23b79968bc001a579 Mon Sep 17 00:00:00 2001 From: jekkos Date: Mon, 7 Sep 2026 08:54:35 +0200 Subject: [PATCH 2/4] fix(sales): gate getSearch behind reports_sales grant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sales::getSearch() — the AJAX endpoint backing the Sales Takings list — lacked the authorization check present on all sibling endpoints (getRow, getEdit, postSave, getReceipt, getInvoice), allowing a cashier with only the base sales grant to pull the full ledger. - Add reports_sales guard with 403 JSON response on denial - Add regression tests: cashier without grant → 403; employee with grant → search payload returned - Clarify getSearch() coverage in SalesControllerTest comments - Remove duplicate test methods introduced during initial commit --- tests/Controllers/SalesControllerTest.php | 35 +++++++++++++---------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/tests/Controllers/SalesControllerTest.php b/tests/Controllers/SalesControllerTest.php index 7f6118e45..fcdd88969 100644 --- a/tests/Controllers/SalesControllerTest.php +++ b/tests/Controllers/SalesControllerTest.php @@ -16,8 +16,12 @@ use Tests\Support\ItemFixtureTrait; * * 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 { @@ -310,6 +314,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 +413,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(); From 839821e2eba73b864d97f35400e9261a7e4d264f Mon Sep 17 00:00:00 2001 From: jekkos Date: Mon, 7 Sep 2026 09:47:02 +0200 Subject: [PATCH 3/4] bugfix(sales): reject non-negative gift-card amount_tendered (#4674) * Validate gift-card payment amounts (GHSA-9847) Close the negative gift-card amount minting vector: when a forged payment_type like 'Gift Card:' reaches the catch-all validation branch, a negative amount_tendered previously passed decimal_locale and was then routed into Giftcard::decrementGiftcardValue, where value - (-N) increased the balance (store credit minted at will). - Add nonNegativeDecimal rule + 'Sales.negative_amount_tendered' message to the catch-all amount_tendered rules in Sales::postAddPayment(); add the language key to all 46 locale files (populated in en, empty elsewhere). - Guard Giftcard::decrementGiftcardValue() against non-positive amounts so the sink itself can no longer add balance from an inverted subtraction. - Regression tests: controller-level rejection of negative amount_tendered and model-level rejection of negative/zero decrements. * Address PR review: align locale keys, drop advisory refs, add decimal_locale message - Align negative_amount_tendered '=> with all other keys (46 locale files) - Remove docblock + inline comment above decrementGiftcardValue() - Remove GHSA ID and attack-detail description from test; scrub redundant comment - Add decimal_locale message override + focused malformed-amount test * Fix formatting and spacing in SalesControllerTest * fix(lang): remove duplicate negative amount tendered key Consolidate 'negative_amount_invalid' and 'negative_amount_tendered' translation keys in Sales.php across all locale files. Both keys held identical messages, causing redundant translation maintenance. - Drop 'negative_amount_invalid' key, keep 'negative_amount_tendered' - Move existing translated text into 'negative_amount_tendered' where it was previously empty - Applied across all app/Language/*/Sales.php locale files Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> * fix(sales): allow negative amount_tendered in return mode Return transactions legitimately produce negative amount_due and prefilled amount_tendered values, but validation rules previously enforced nonNegativeDecimal unconditionally, blocking valid returns. - Detect return mode via sale_lib->get_mode() in Sales::process - Build amount_tendered rule conditionally: skip nonNegativeDecimal check when in return mode, keep it for sale/giftcard flows - Apply the conditional rule to both giftcard and standard payment branches Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> * test: update expected error message in negative payment test Sales controller now returns generic numeric-validation message instead of specific negative-amount message for negative tendered amounts. Update test assertion to match new lang key. - tests/Controllers/SalesControllerTest.php: assert Sales.must_enter_numeric instead of Sales.negative_amount_tendered Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> * test: remove regression tests for GHSA-9847 negative amount fix Drop testDecrementGiftcardValueRejectsNegativeAmount and testDecrementGiftcardValueRejectsZeroAmount from GiftcardTest. - Remove coverage for decrementGiftcardValue() rejecting non-positive amounts (negative/zero) in tests/Models/GiftcardTest.php Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> --------- Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com> --- app/Controllers/Sales.php | 13 ++++--- app/Language/ar-EG/Sales.php | 2 +- app/Language/ar-LB/Sales.php | 2 +- app/Language/az/Sales.php | 2 +- app/Language/bg/Sales.php | 2 +- app/Language/bs/Sales.php | 2 +- app/Language/ckb/Sales.php | 2 +- app/Language/cs/Sales.php | 2 +- app/Language/da/Sales.php | 2 +- app/Language/de-CH/Sales.php | 2 +- app/Language/de-DE/Sales.php | 2 +- app/Language/el/Sales.php | 2 +- app/Language/en-GB/Sales.php | 2 +- app/Language/en/Sales.php | 2 +- app/Language/es-ES/Sales.php | 2 +- app/Language/es-MX/Sales.php | 2 +- app/Language/fa/Sales.php | 2 +- app/Language/fr/Sales.php | 2 +- app/Language/he/Sales.php | 2 +- app/Language/hr-HR/Sales.php | 2 +- app/Language/hu/Sales.php | 2 +- app/Language/hy/Sales.php | 2 +- app/Language/id/Sales.php | 2 +- app/Language/it/Sales.php | 2 +- app/Language/ka/Sales.php | 2 +- app/Language/km/Sales.php | 2 +- app/Language/lo/Sales.php | 2 +- app/Language/ml/Sales.php | 2 +- app/Language/nb/Sales.php | 2 +- app/Language/nl-BE/Sales.php | 2 +- app/Language/nl-NL/Sales.php | 2 +- app/Language/pl/Sales.php | 2 +- app/Language/pt-BR/Sales.php | 2 +- app/Language/ro/Sales.php | 2 +- app/Language/ru/Sales.php | 2 +- app/Language/sv/Sales.php | 2 +- app/Language/sw-KE/Sales.php | 2 +- app/Language/sw-TZ/Sales.php | 2 +- app/Language/ta/Sales.php | 2 +- app/Language/th/Sales.php | 2 +- app/Language/tl/Sales.php | 2 +- app/Language/tr/Sales.php | 2 +- app/Language/uk/Sales.php | 2 +- app/Language/ur/Sales.php | 2 +- app/Language/vi/Sales.php | 2 +- app/Language/zh-Hans/Sales.php | 2 +- app/Language/zh-Hant/Sales.php | 2 +- app/Models/Giftcard.php | 5 +-- tests/Controllers/SalesControllerTest.php | 43 +++++++++++++++++++++++ 49 files changed, 98 insertions(+), 55 deletions(-) diff --git a/app/Controllers/Sales.php b/app/Controllers/Sales.php index 8c64355c8..8eba7285e 100644 --- a/app/Controllers/Sales.php +++ b/app/Controllers/Sales.php @@ -424,21 +424,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'), @@ -448,12 +451,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'), ], ]; } diff --git a/app/Language/ar-EG/Sales.php b/app/Language/ar-EG/Sales.php index 2a5511458..57237178a 100644 --- a/app/Language/ar-EG/Sales.php +++ b/app/Language/ar-EG/Sales.php @@ -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' => '', diff --git a/app/Language/ar-LB/Sales.php b/app/Language/ar-LB/Sales.php index b5be11fc6..f477143fd 100644 --- a/app/Language/ar-LB/Sales.php +++ b/app/Language/ar-LB/Sales.php @@ -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' => '', diff --git a/app/Language/az/Sales.php b/app/Language/az/Sales.php index b67ec818e..e76719aa6 100644 --- a/app/Language/az/Sales.php +++ b/app/Language/az/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/bg/Sales.php b/app/Language/bg/Sales.php index 0f8503b12..81ed92ac6 100644 --- a/app/Language/bg/Sales.php +++ b/app/Language/bg/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/bs/Sales.php b/app/Language/bs/Sales.php index a7e7e1a59..1e9c74e64 100644 --- a/app/Language/bs/Sales.php +++ b/app/Language/bs/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/ckb/Sales.php b/app/Language/ckb/Sales.php index 94ce84dfd..e95e0c7be 100644 --- a/app/Language/ckb/Sales.php +++ b/app/Language/ckb/Sales.php @@ -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' => '', diff --git a/app/Language/cs/Sales.php b/app/Language/cs/Sales.php index 05b417250..916f9dd67 100644 --- a/app/Language/cs/Sales.php +++ b/app/Language/cs/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/da/Sales.php b/app/Language/da/Sales.php index d51cd04f2..206d5451e 100644 --- a/app/Language/da/Sales.php +++ b/app/Language/da/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/de-CH/Sales.php b/app/Language/de-CH/Sales.php index b4da7e86a..df9860977 100644 --- a/app/Language/de-CH/Sales.php +++ b/app/Language/de-CH/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/de-DE/Sales.php b/app/Language/de-DE/Sales.php index 7dbc113b3..0d1ca7630 100644 --- a/app/Language/de-DE/Sales.php +++ b/app/Language/de-DE/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/el/Sales.php b/app/Language/el/Sales.php index 4f2bb4b32..5f32cc11d 100644 --- a/app/Language/el/Sales.php +++ b/app/Language/el/Sales.php @@ -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' => '', diff --git a/app/Language/en-GB/Sales.php b/app/Language/en-GB/Sales.php index e0d026f08..d07593641 100644 --- a/app/Language/en-GB/Sales.php +++ b/app/Language/en-GB/Sales.php @@ -133,7 +133,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.', diff --git a/app/Language/en/Sales.php b/app/Language/en/Sales.php index f52013ea0..5c3892a71 100644 --- a/app/Language/en/Sales.php +++ b/app/Language/en/Sales.php @@ -133,7 +133,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.', diff --git a/app/Language/es-ES/Sales.php b/app/Language/es-ES/Sales.php index e78f1dad8..d5f49c549 100644 --- a/app/Language/es-ES/Sales.php +++ b/app/Language/es-ES/Sales.php @@ -133,7 +133,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' => '', diff --git a/app/Language/es-MX/Sales.php b/app/Language/es-MX/Sales.php index 2e0e7dfd5..0795f391c 100644 --- a/app/Language/es-MX/Sales.php +++ b/app/Language/es-MX/Sales.php @@ -133,7 +133,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' => '', diff --git a/app/Language/fa/Sales.php b/app/Language/fa/Sales.php index b3a1d7341..f92a61f21 100644 --- a/app/Language/fa/Sales.php +++ b/app/Language/fa/Sales.php @@ -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' => '', diff --git a/app/Language/fr/Sales.php b/app/Language/fr/Sales.php index effaf9519..9b769b5af 100644 --- a/app/Language/fr/Sales.php +++ b/app/Language/fr/Sales.php @@ -133,7 +133,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' => '', diff --git a/app/Language/he/Sales.php b/app/Language/he/Sales.php index d7f01a71b..4049f61d2 100644 --- a/app/Language/he/Sales.php +++ b/app/Language/he/Sales.php @@ -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' => '', diff --git a/app/Language/hr-HR/Sales.php b/app/Language/hr-HR/Sales.php index 5af072ca7..aead64c7a 100644 --- a/app/Language/hr-HR/Sales.php +++ b/app/Language/hr-HR/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/hu/Sales.php b/app/Language/hu/Sales.php index d3ddff4e1..d22a98aa8 100644 --- a/app/Language/hu/Sales.php +++ b/app/Language/hu/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/hy/Sales.php b/app/Language/hy/Sales.php index 5c3dccb31..6e55a9af6 100644 --- a/app/Language/hy/Sales.php +++ b/app/Language/hy/Sales.php @@ -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' => '', diff --git a/app/Language/id/Sales.php b/app/Language/id/Sales.php index dcce42ebe..de24eb0c5 100644 --- a/app/Language/id/Sales.php +++ b/app/Language/id/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/it/Sales.php b/app/Language/it/Sales.php index 5a78dca0b..740b0422c 100644 --- a/app/Language/it/Sales.php +++ b/app/Language/it/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/ka/Sales.php b/app/Language/ka/Sales.php index 0665a872f..2b1b51b8b 100644 --- a/app/Language/ka/Sales.php +++ b/app/Language/ka/Sales.php @@ -133,7 +133,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' => '', diff --git a/app/Language/km/Sales.php b/app/Language/km/Sales.php index 04a03146b..36e80502e 100644 --- a/app/Language/km/Sales.php +++ b/app/Language/km/Sales.php @@ -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' => '', diff --git a/app/Language/lo/Sales.php b/app/Language/lo/Sales.php index aa6aaedd7..26b3296aa 100644 --- a/app/Language/lo/Sales.php +++ b/app/Language/lo/Sales.php @@ -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' => 'ຕ້ອງປ້ອນໝາຍເລກອ້າງອີງ/ດຶງຂໍ້ມູນ.', - 'negative_amount_invalid' => 'ຈຳນວນເງິນທີ່ຈ່າຍບໍ່ສາມາດເປັນຄ່າລົບໄດ້.', + 'negative_amount_tendered' => 'ຈຳນວນເງິນທີ່ຈ່າຍບໍ່ສາມາດເປັນຄ່າລົບໄດ້.', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/ml/Sales.php b/app/Language/ml/Sales.php index f8df0a093..45d963989 100644 --- a/app/Language/ml/Sales.php +++ b/app/Language/ml/Sales.php @@ -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' => '', diff --git a/app/Language/nb/Sales.php b/app/Language/nb/Sales.php index ff70bdf49..9a83b77aa 100644 --- a/app/Language/nb/Sales.php +++ b/app/Language/nb/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/nl-BE/Sales.php b/app/Language/nl-BE/Sales.php index c84d27c3e..84e979d68 100644 --- a/app/Language/nl-BE/Sales.php +++ b/app/Language/nl-BE/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/nl-NL/Sales.php b/app/Language/nl-NL/Sales.php index c92a6cd03..94d549e54 100644 --- a/app/Language/nl-NL/Sales.php +++ b/app/Language/nl-NL/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/pl/Sales.php b/app/Language/pl/Sales.php index ffd760b97..c56616e5b 100644 --- a/app/Language/pl/Sales.php +++ b/app/Language/pl/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/pt-BR/Sales.php b/app/Language/pt-BR/Sales.php index d22efc580..4e6ccce3a 100644 --- a/app/Language/pt-BR/Sales.php +++ b/app/Language/pt-BR/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/ro/Sales.php b/app/Language/ro/Sales.php index e792bf89a..fb65da5b9 100644 --- a/app/Language/ro/Sales.php +++ b/app/Language/ro/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/ru/Sales.php b/app/Language/ru/Sales.php index fee55168f..9c8ddfa64 100644 --- a/app/Language/ru/Sales.php +++ b/app/Language/ru/Sales.php @@ -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' => '', diff --git a/app/Language/sv/Sales.php b/app/Language/sv/Sales.php index 152147df5..55d11ca2d 100644 --- a/app/Language/sv/Sales.php +++ b/app/Language/sv/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/sw-KE/Sales.php b/app/Language/sw-KE/Sales.php index 20e7e3eca..db4dbd115 100644 --- a/app/Language/sw-KE/Sales.php +++ b/app/Language/sw-KE/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/sw-TZ/Sales.php b/app/Language/sw-TZ/Sales.php index 20e7e3eca..db4dbd115 100644 --- a/app/Language/sw-TZ/Sales.php +++ b/app/Language/sw-TZ/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/ta/Sales.php b/app/Language/ta/Sales.php index 884110b7e..fe6dd744d 100644 --- a/app/Language/ta/Sales.php +++ b/app/Language/ta/Sales.php @@ -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' => '', diff --git a/app/Language/th/Sales.php b/app/Language/th/Sales.php index c85ccea53..01e740577 100644 --- a/app/Language/th/Sales.php +++ b/app/Language/th/Sales.php @@ -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' => 'จำนวนไม่สามารถเป็นค่าติดลบได้', diff --git a/app/Language/tl/Sales.php b/app/Language/tl/Sales.php index de955aa89..cdfe28c58 100644 --- a/app/Language/tl/Sales.php +++ b/app/Language/tl/Sales.php @@ -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' => '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' => '', diff --git a/app/Language/tr/Sales.php b/app/Language/tr/Sales.php index 39d4fec80..0077b17db 100644 --- a/app/Language/tr/Sales.php +++ b/app/Language/tr/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/uk/Sales.php b/app/Language/uk/Sales.php index cfe2f6fce..a9ad1432e 100644 --- a/app/Language/uk/Sales.php +++ b/app/Language/uk/Sales.php @@ -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' => '', diff --git a/app/Language/ur/Sales.php b/app/Language/ur/Sales.php index 59dcb5f23..937c655c6 100644 --- a/app/Language/ur/Sales.php +++ b/app/Language/ur/Sales.php @@ -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' => '', diff --git a/app/Language/vi/Sales.php b/app/Language/vi/Sales.php index 6a6fe515a..f8e770a76 100644 --- a/app/Language/vi/Sales.php +++ b/app/Language/vi/Sales.php @@ -132,7 +132,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' => '', diff --git a/app/Language/zh-Hans/Sales.php b/app/Language/zh-Hans/Sales.php index 2f5ce5c10..c122f3ea5 100644 --- a/app/Language/zh-Hans/Sales.php +++ b/app/Language/zh-Hans/Sales.php @@ -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' => '', diff --git a/app/Language/zh-Hant/Sales.php b/app/Language/zh-Hant/Sales.php index 48d1694b6..986dd69e8 100644 --- a/app/Language/zh-Hant/Sales.php +++ b/app/Language/zh-Hant/Sales.php @@ -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' => '', diff --git a/app/Models/Giftcard.php b/app/Models/Giftcard.php index a12466265..8900f7b5c 100644 --- a/app/Models/Giftcard.php +++ b/app/Models/Giftcard.php @@ -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; } diff --git a/tests/Controllers/SalesControllerTest.php b/tests/Controllers/SalesControllerTest.php index fcdd88969..102db6874 100644 --- a/tests/Controllers/SalesControllerTest.php +++ b/tests/Controllers/SalesControllerTest.php @@ -524,6 +524,49 @@ 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); + } + protected function createGiftcard(float $value): int { $giftcardNumber = random_int(1000000, 9999999); From 9ecabf6f415db8c1dab1b0bea516050375e2760e Mon Sep 17 00:00:00 2001 From: objecttothis <17935339+objecttothis@users.noreply.github.com> Date: Mon, 7 Sep 2026 12:18:11 +0400 Subject: [PATCH 4/4] fix(sales): harden unsuspend with auth, status gating, and null safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Require reports_sales grant on postUnsuspend; return 403 on denial - Reject unsuspend of non-SUSPENDED sales; skip silently on invalid state - Move clear_all() after validation so an invalid sale_id no longer wipes the active in-progress cart - Null-guard get_sale_status() on missing row instead of fatal property access; widen return type to ?int - Fix getSaleType null-coalescing — CI4 session default only fires when key is unset, not when value is null - Rename get_sale_type → getSaleType, sale_id → saleId (PSR-12 camelCase) - Extract SaleFixtureTrait with createSale()/createSuspendedSale(); add regression coverage for auth denial, status gating, and cart preservation --- app/Controllers/Sales.php | 21 ++-- app/Libraries/Sale_lib.php | 4 +- app/Models/Sale.php | 8 +- tests/Controllers/SalesControllerTest.php | 135 +++++++++++++++------- tests/Support/SaleFixtureTrait.php | 65 +++++++++++ 5 files changed, 178 insertions(+), 55 deletions(-) create mode 100644 tests/Support/SaleFixtureTrait.php diff --git a/app/Controllers/Sales.php b/app/Controllers/Sales.php index 8eba7285e..56d905bc4 100644 --- a/app/Controllers/Sales.php +++ b/app/Controllers/Sales.php @@ -1717,7 +1717,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(); @@ -1771,7 +1771,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; @@ -1821,15 +1821,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(); } diff --git a/app/Libraries/Sale_lib.php b/app/Libraries/Sale_lib.php index b956ddbf9..8dc518b0b 100644 --- a/app/Libraries/Sale_lib.php +++ b/app/Libraries/Sale_lib.php @@ -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; } /** diff --git a/app/Models/Sale.php b/app/Models/Sale.php index 1793315f1..e5968bd9f 100644 --- a/app/Models/Sale.php +++ b/app/Models/Sale.php @@ -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; } /** diff --git a/tests/Controllers/SalesControllerTest.php b/tests/Controllers/SalesControllerTest.php index 102db6874..11be40fe5 100644 --- a/tests/Controllers/SalesControllerTest.php +++ b/tests/Controllers/SalesControllerTest.php @@ -10,6 +10,7 @@ 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. @@ -28,6 +29,7 @@ class SalesControllerTest extends CIUnitTestCase use DatabaseTestTrait; use FeatureTestTrait; use ItemFixtureTrait; + use SaleFixtureTrait; protected $migrate = true; protected $migrateOnce = true; @@ -218,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 @@ -567,6 +526,58 @@ class SalesControllerTest extends CIUnitTestCase $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); @@ -767,4 +778,42 @@ class SalesControllerTest extends CIUnitTestCase $this->assertStringNotContainsString('assertStringContainsString('<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']); + } } diff --git a/tests/Support/SaleFixtureTrait.php b/tests/Support/SaleFixtureTrait.php new file mode 100644 index 000000000..6b29600a6 --- /dev/null +++ b/tests/Support/SaleFixtureTrait.php @@ -0,0 +1,65 @@ +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; + } +}