From fdc1c38b43fc9680c253b77c087d72ee0e575f2b Mon Sep 17 00:00:00 2001 From: objecttothis <17935339+objecttothis@users.noreply.github.com> Date: Tue, 1 Sep 2026 19:41:41 +0400 Subject: [PATCH 1/2] feat(validation, tests): add `valid_path_strict` rule and integrate into mailpath validation (#4684) - Introduce `valid_path_strict` rule in `OSPOSRules` to enforce stricter path validation, preventing security issues like injection attempts with newline or special characters. - Update mail configuration validation in `Config` controller to use the new rule for the `mailpath` field. - Add unit tests in `OSPOSRulesTest` to cover edge cases for `valid_path_strict`. Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com> --- app/Config/Validation/OSPOSRules.php | 20 +++++++++++++++++ app/Controllers/Config.php | 23 +++++++++++-------- tests/Config/Validation/OSPOSRulesTest.php | 26 ++++++++++++++++++++++ 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/app/Config/Validation/OSPOSRules.php b/app/Config/Validation/OSPOSRules.php index 05670463e..5b0638e31 100644 --- a/app/Config/Validation/OSPOSRules.php +++ b/app/Config/Validation/OSPOSRules.php @@ -225,4 +225,24 @@ class OSPOSRules return true; } + + /** + * Validates that the candidate is a plain filesystem path: only letters, digits, + * underscore, dash, dot and forward slash. Uses \A...\z (not ^...$) because PCRE's $ + * also matches immediately before a single trailing newline, which would let a + * value like "/usr/bin/php\n" slip through — the bug behind GHSA-jc56-j8m6-q627. + * + * @param string $candidate + * @param string|null $error + * @return bool + * @noinspection PhpUnused + */ + public function valid_path_strict(string $candidate, ?string &$error = null): bool + { + if ($candidate === '') { + return false; + } + + return (bool) preg_match('/\A[a-zA-Z0-9_\-\/.]+\z/', $candidate); + } } diff --git a/app/Controllers/Config.php b/app/Controllers/Config.php index fdf4f1daf..82f68e57c 100644 --- a/app/Controllers/Config.php +++ b/app/Controllers/Config.php @@ -540,16 +540,21 @@ class Config extends Secure_Controller $protocol = $this->request->getPost('protocol'); $mailpath = $this->request->getPost('mailpath'); - // Validate mailpath: required for sendmail, optional for others but must be safe if provided - $isMailpathRequired = ($protocol === 'sendmail'); - $isMailpathProvided = !empty($mailpath); - $isMailpathValid = $isMailpathProvided && preg_match('/^[a-zA-Z0-9_\-\/.]+$/', $mailpath); + $rules = [ + 'mailpath' => [ + 'label' => lang('Config.email_mailpath'), + 'rules' => ($protocol === 'sendmail' ? 'required' : 'permit_empty') . '|valid_path_strict' + ] + ]; + $messages = [ + 'mailpath' => [ + 'required' => lang('Config.mailpath_invalid'), + 'valid_path_strict' => lang('Config.mailpath_invalid') + ] + ]; - if (($isMailpathRequired && !$isMailpathProvided) || ($isMailpathProvided && !$isMailpathValid)) { - return $this->response->setJSON([ - 'success' => false, - 'message' => lang('Config.mailpath_invalid') - ]); + if ($error = $this->validateFields($rules, $messages)) { + return $error; } $batch_save_data = [ diff --git a/tests/Config/Validation/OSPOSRulesTest.php b/tests/Config/Validation/OSPOSRulesTest.php index 4fa827ce2..6ef1daa50 100644 --- a/tests/Config/Validation/OSPOSRulesTest.php +++ b/tests/Config/Validation/OSPOSRulesTest.php @@ -100,6 +100,32 @@ class OSPOSRulesTest extends CIUnitTestCase $this->assertNull($error); } + /** + * @dataProvider validPathStrictProvider + */ + public function testValidPathStrict(string $candidate, bool $expected): void + { + $rules = new OSPOSRules(); + + $this->assertSame($expected, $rules->valid_path_strict($candidate)); + } + + public static function validPathStrictProvider(): array + { + return [ + 'plain sendmail path' => ['/usr/sbin/sendmail', true], + 'plain php path' => ['/usr/bin/php', true], + 'path with dash and underscore' => ['/opt/my-mail_bin/sendmail.exe', true], + 'empty string' => ['', false], + 'trailing newline bypass payload' => ["/usr/bin/php\n", false], + 'trailing newline plus injected command' => ["/usr/bin/php\nid", false], + 'embedded newline mid-string' => ["/usr/bin/php\n/bin/sh", false], + 'semicolon injection' => ['/usr/bin/php;id', false], + 'pipe injection' => ['/usr/bin/php|id', false], + 'space separated args' => ['/usr/bin/php -r "phpinfo();"', false], + ]; + } + private function injectSettings(array $settings): void { $ospos = new OSPOS(); From f5f9052de1b4a5006e88a7cc6175e0da6434fac5 Mon Sep 17 00:00:00 2001 From: objecttothis <17935339+objecttothis@users.noreply.github.com> Date: Wed, 2 Sep 2026 01:21:46 +0400 Subject: [PATCH 2/2] fix(sales): harden payment validation and gift card handling - Validate paymentType is a non-empty string before processing - Reject negative or zero amounts for all payment types - Enforce full payment coverage before completing a sale - Bypass coverage check for invoice and quote mode sales - Require a valid gift card number before decrementing value; rollback and return insufficient balance error on missing input - Add "amount_due_not_covered" and "negative_amount_invalid" translations across 40+ locales - Add test coverage for gift card validation, negative amounts, and quote/invoice zero-payment completion - Rename snake_case locals to camelCase in postComplete (no behavior change) --- app/Controllers/Sales.php | 147 +++++++++++------- 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 | 23 +-- app/Language/sw-TZ/Sales.php | 23 +-- 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 | 4 + app/Models/Sale.php | 125 +++++++-------- tests/Controllers/SalesControllerTest.php | 181 +++++++++++++++++++++- tests/Models/GiftcardTest.php | 22 +++ tests/Models/SaleTest.php | 58 ++++++- 52 files changed, 526 insertions(+), 145 deletions(-) diff --git a/app/Controllers/Sales.php b/app/Controllers/Sales.php index 28627379c..8c64355c8 100644 --- a/app/Controllers/Sales.php +++ b/app/Controllers/Sales.php @@ -408,6 +408,22 @@ class Sales extends Secure_Controller $giftcard = model(Giftcard::class); $paymentType = $this->request->getPost('payment_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS); + if (!is_string($paymentType) || $paymentType === '') { + $data['error'] = lang('Sales.must_enter_numeric'); + + return $this->reload($data); + } + + if ( + $paymentType !== lang('Sales.giftcard') + && $paymentType !== lang('Sales.rewards') + && (str_contains($paymentType, lang('Sales.giftcard')) || str_contains($paymentType, lang('Sales.rewards'))) + ) { + $data['error'] = lang('Sales.must_enter_numeric'); + + return $this->reload($data); + } + if ($paymentType === lang('Sales.giftcard')) { $rules = ['amount_tendered' => 'trim|required|integer']; //For giftcards, amount_tendered becomes the giftcard number which must be an integer $messages = ['amount_tendered' => lang('Sales.must_enter_numeric_giftcard')]; @@ -415,12 +431,14 @@ class Sales extends Secure_Controller $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', + 'amount_tendered' => 'trim|required|decimal_locale|nonNegativeDecimal', 'reference_code' => "trim|required|alpha_numeric|min_length[$min]|max_length[$max]", ]; $messages = [ 'amount_tendered' => [ - 'required' => lang('Sales.must_enter_numeric'), + 'required' => lang('Sales.must_enter_numeric'), + 'decimal_locale' => lang('Sales.must_enter_numeric'), + 'nonNegativeDecimal' => lang('Sales.negative_amount_invalid'), ], 'reference_code' => [ 'required' => lang('Sales.must_enter_reference_code'), @@ -430,8 +448,14 @@ class Sales extends Secure_Controller ], ]; } else { - $rules = ['amount_tendered' => 'trim|required|decimal_locale']; - $messages = ['amount_tendered' => lang('Sales.must_enter_numeric')]; + $rules = ['amount_tendered' => 'trim|required|decimal_locale|nonNegativeDecimal']; + $messages = [ + 'amount_tendered' => [ + 'required' => lang('Sales.must_enter_numeric'), + 'decimal_locale' => lang('Sales.must_enter_numeric'), + 'nonNegativeDecimal' => lang('Sales.negative_amount_invalid'), + ], + ]; } if (!$this->validate($rules, $messages)) { @@ -730,21 +754,21 @@ class Sales extends Secure_Controller */ public function postComplete(): string // TODO: this function is huge. Probably should be refactored. { - $sale_id = $this->sale_lib->get_sale_id(); + $saleId = $this->sale_lib->get_sale_id(); $data = []; $data['dinner_table'] = $this->sale_lib->get_dinner_table(); $data['cart'] = $this->sale_lib->get_cart(); $data['include_hsn'] = (bool)$this->config['include_hsn']; - $__time = time(); - $data['transaction_time'] = to_datetime($__time); - $data['transaction_date'] = to_date($__time); + $time = time(); + $data['transaction_time'] = to_datetime($time); + $data['transaction_date'] = to_date($time); $data['show_stock_locations'] = $this->stock_location->show_locations('sales'); $data['comments'] = $this->sale_lib->get_comment(); - $employee_id = $this->employee->get_logged_in_employee_info()->person_id; - $employee_info = $this->employee->get_info($employee_id); - $data['employee'] = $employee_info->first_name . ' ' . mb_substr($employee_info->last_name, 0, 1); + $employeeId = $this->employee->get_logged_in_employee_info()->person_id; + $employeeInfo = $this->employee->get_info($employeeId); + $data['employee'] = $employeeInfo->first_name . ' ' . mb_substr($employeeInfo->last_name, 0, 1); $data['company_info'] = implode("\n", [$this->config['address'], $this->config['phone']]); @@ -762,26 +786,26 @@ class Sales extends Secure_Controller $data['print_after_sale'] = $this->session->get('sales_print_after_sale'); $data['price_work_orders'] = $this->sale_lib->is_price_work_orders(); $data['email_receipt'] = $this->sale_lib->is_email_receipt(); - $customer_id = $this->sale_lib->get_customer(); - $invoice_number = $this->sale_lib->get_invoice_number(); - $data["invoice_number"] = $invoice_number; - $work_order_number = $this->sale_lib->get_work_order_number(); - $data["work_order_number"] = $work_order_number; - $quote_number = $this->sale_lib->get_quote_number(); - $data["quote_number"] = $quote_number; - $customer_info = $this->_load_customer_data($customer_id, $data); + $customerId = $this->sale_lib->get_customer(); + $invoiceNumber = $this->sale_lib->get_invoice_number(); + $data["invoice_number"] = $invoiceNumber; + $workOrderNumber = $this->sale_lib->get_work_order_number(); + $data["work_order_number"] = $workOrderNumber; + $quoteNumber = $this->sale_lib->get_quote_number(); + $data["quote_number"] = $quoteNumber; + $customerInfo = $this->_load_customer_data($customerId, $data); - if ($customer_info != null) { - $data["customer_comments"] = $customer_info->comments; - $data['tax_id'] = $customer_info->tax_id; + if ($customerInfo != null) { + $data["customer_comments"] = $customerInfo->comments; + $data['tax_id'] = $customerInfo->tax_id; } - $tax_details = $this->tax_lib->get_taxes($data['cart']); // TODO: Duplicated code - $data['taxes'] = $tax_details[0]; + $taxDetails = $this->tax_lib->get_taxes($data['cart']); // TODO: Duplicated code + $data['taxes'] = $taxDetails[0]; $data['discount'] = $this->sale_lib->get_discount(); $data['payments'] = $this->sale_lib->getPayments(); // Returns 'subtotal', 'total', 'cash_total', 'payment_total', 'amount_due', 'cash_amount_due', 'payments_cover_total' - $totals = $this->sale_lib->get_totals($tax_details[0]); + $totals = $this->sale_lib->get_totals($taxDetails[0]); $data['subtotal'] = $totals['subtotal']; $data['total'] = $totals['total']; $data['payments_total'] = $totals['payment_total']; @@ -800,6 +824,11 @@ class Sales extends Secure_Controller return $this->reload($data); } + if (!$totals['payments_cover_total'] && !$this->sale_lib->is_invoice_mode() && !$this->sale_lib->is_quote_mode()) { + $data['error'] = lang('Sales.amount_due_not_covered'); + return $this->reload($data); + } + if ($data['cash_mode']) { // TODO: Convert this to ternary notation $data['amount_due'] = $totals['cash_amount_due']; } else { @@ -829,31 +858,31 @@ class Sales extends Secure_Controller $data['print_price_info'] = true; if ($this->sale_lib->is_invoice_mode()) { - $invoice_format = $this->config['sales_invoice_format']; + $invoiceFormat = $this->config['sales_invoice_format']; // Generate final invoice number (if using the invoice in sales by receipt mode then the invoice number can be manually entered or altered in some way - if (!empty($invoice_format) && $invoice_number == null) { + if (!empty($invoiceFormat) && $invoiceNumber == null) { // The user can retain the default encoded format or can manually override it. It still passes through the rendering step. - $invoice_number = $this->token_lib->render($invoice_format); + $invoiceNumber = $this->token_lib->render($invoiceFormat); } - if ($sale_id == NEW_ENTRY && $this->sale->check_invoice_number_exists($invoice_number)) { - $data['error'] = lang('Sales.invoice_number_duplicate', [$invoice_number]); + if ($saleId == NEW_ENTRY && $this->sale->check_invoice_number_exists($invoiceNumber)) { + $data['error'] = lang('Sales.invoice_number_duplicate', [$invoiceNumber]); return $this->reload($data); } else { - $data['invoice_number'] = $invoice_number; + $data['invoice_number'] = $invoiceNumber; $data['sale_status'] = COMPLETED; - $sale_type = SALE_TYPE_INVOICE; + $saleType = SALE_TYPE_INVOICE; - $invoice_type = $this->config['invoice_type']; - if (!Sale_lib::isValidInvoiceType($invoice_type)) { - $invoice_type = 'invoice'; + $invoiceType = $this->config['invoice_type']; + if (!Sale_lib::isValidInvoiceType($invoiceType)) { + $invoiceType = 'invoice'; } - $invoice_view = $invoice_type; + $invoiceView = $invoiceType; // Save the data to the sales table - $data['sale_id_num'] = $this->sale->save_value($sale_id, $data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $work_order_number, $quote_number, $sale_type, $data['payments'], $data['dinner_table'], $tax_details); + $data['sale_id_num'] = $this->sale->save_value($saleId, $data['sale_status'], $data['cart'], $customerId, $employeeId, $data['comments'], $invoiceNumber, $workOrderNumber, $quoteNumber, $saleType, $data['payments'], $data['dinner_table'], $taxDetails); $data['sale_id'] = 'POS ' . $data['sale_id_num']; // Resort and filter cart lines for printing @@ -871,7 +900,7 @@ class Sales extends Secure_Controller } else { $data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']); $this->sale_lib->clear_all(); - return view('sales/' . $invoice_view, $data); + return view('sales/' . $invoiceView, $data); } } } elseif ($this->sale_lib->is_work_order_mode()) { @@ -883,21 +912,21 @@ class Sales extends Secure_Controller $data['sales_work_order'] = lang('Sales.work_order'); $data['work_order_number_label'] = lang('Sales.work_order_number'); - if ($work_order_number == null) { + if ($workOrderNumber == null) { // Generate work order number - $work_order_format = $this->config['work_order_format']; - $work_order_number = $this->token_lib->render($work_order_format); + $workOrderFormat = $this->config['work_order_format']; + $workOrderNumber = $this->token_lib->render($workOrderFormat); } - if ($sale_id == NEW_ENTRY && $this->sale->check_work_order_number_exists($work_order_number)) { + if ($saleId == NEW_ENTRY && $this->sale->check_work_order_number_exists($workOrderNumber)) { $data['error'] = lang('Sales.work_order_number_duplicate'); return $this->reload($data); } else { - $data['work_order_number'] = $work_order_number; + $data['work_order_number'] = $workOrderNumber; $data['sale_status'] = SUSPENDED; - $sale_type = SALE_TYPE_WORK_ORDER; + $saleType = SALE_TYPE_WORK_ORDER; - $data['sale_id_num'] = $this->sale->save_value($sale_id, $data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $work_order_number, $quote_number, $sale_type, $data['payments'], $data['dinner_table'], $tax_details); + $data['sale_id_num'] = $this->sale->save_value($saleId, $data['sale_status'], $data['cart'], $customerId, $employeeId, $data['comments'], $invoiceNumber, $workOrderNumber, $quoteNumber, $saleType, $data['payments'], $data['dinner_table'], $taxDetails); if ($data['sale_id_num'] === INSUFFICIENT_GIFTCARD_BALANCE) { $data['error_message'] = lang('Sales.insufficient_giftcard_balance'); @@ -923,21 +952,21 @@ class Sales extends Secure_Controller $data['sales_quote'] = lang('Sales.quote'); $data['quote_number_label'] = lang('Sales.quote_number'); - if ($quote_number == null) { + if ($quoteNumber == null) { // Generate quote number - $quote_format = $this->config['sales_quote_format']; - $quote_number = $this->token_lib->render($quote_format); + $quoteFormat = $this->config['sales_quote_format']; + $quoteNumber = $this->token_lib->render($quoteFormat); } - if ($sale_id == NEW_ENTRY && $this->sale->check_quote_number_exists($quote_number)) { + if ($saleId == NEW_ENTRY && $this->sale->check_quote_number_exists($quoteNumber)) { $data['error'] = lang('Sales.quote_number_duplicate'); return $this->reload($data); } else { - $data['quote_number'] = $quote_number; + $data['quote_number'] = $quoteNumber; $data['sale_status'] = SUSPENDED; - $sale_type = SALE_TYPE_QUOTE; + $saleType = SALE_TYPE_QUOTE; - $data['sale_id_num'] = $this->sale->save_value($sale_id, $data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $work_order_number, $quote_number, $sale_type, $data['payments'], $data['dinner_table'], $tax_details); + $data['sale_id_num'] = $this->sale->save_value($saleId, $data['sale_status'], $data['cart'], $customerId, $employeeId, $data['comments'], $invoiceNumber, $workOrderNumber, $quoteNumber, $saleType, $data['payments'], $data['dinner_table'], $taxDetails); if ($data['sale_id_num'] === INSUFFICIENT_GIFTCARD_BALANCE) { $data['error_message'] = lang('Sales.insufficient_giftcard_balance'); @@ -962,12 +991,12 @@ class Sales extends Secure_Controller // Save the data to the sales table $data['sale_status'] = COMPLETED; if ($this->sale_lib->is_return_mode()) { - $sale_type = SALE_TYPE_RETURN; + $saleType = SALE_TYPE_RETURN; } else { - $sale_type = SALE_TYPE_POS; + $saleType = SALE_TYPE_POS; } - $data['sale_id_num'] = $this->sale->save_value($sale_id, $data['sale_status'], $data['cart'], $customer_id, $employee_id, $data['comments'], $invoice_number, $work_order_number, $quote_number, $sale_type, $data['payments'], $data['dinner_table'], $tax_details); + $data['sale_id_num'] = $this->sale->save_value($saleId, $data['sale_status'], $data['cart'], $customerId, $employeeId, $data['comments'], $invoiceNumber, $workOrderNumber, $quoteNumber, $saleType, $data['payments'], $data['dinner_table'], $taxDetails); $data['sale_id'] = 'POS ' . $data['sale_id_num']; @@ -986,11 +1015,11 @@ class Sales extends Secure_Controller $data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']); // Validate receipt template to prevent path traversal - $receipt_template = $this->config['receipt_template'] ?? ''; - if (!Sale_lib::isValidReceiptTemplate($receipt_template)) { - $receipt_template = 'receipt_default'; + $receiptTemplate = $this->config['receipt_template'] ?? ''; + if (!Sale_lib::isValidReceiptTemplate($receiptTemplate)) { + $receiptTemplate = 'receipt_default'; } - $data['receipt_template_view'] = $receipt_template; + $data['receipt_template_view'] = $receiptTemplate; $this->sale_lib->clear_all(); return view('sales/receipt', $data); diff --git a/app/Language/ar-EG/Sales.php b/app/Language/ar-EG/Sales.php index 36299cc5a..2a5511458 100644 --- a/app/Language/ar-EG/Sales.php +++ b/app/Language/ar-EG/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'حساب', 'add_payment' => 'إضافة دفع', 'amount_due' => 'المبلغ المطلوب', + 'amount_due_not_covered' => 'المدفوعات لا تغطي المبلغ المستحق.', 'amount_tendered' => 'المبلغ المدفوع', 'authorized_signature' => 'توقيع معتمد', 'cancel_sale' => 'الغاء عملية البيع', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => 'يجب إدخال رقم للمبلغ الفعلى المدفوع.', 'must_enter_numeric_giftcard' => 'رقم بطاقة الهدية يجب أن يكون رقم.', 'must_enter_reference_code' => 'يجب إدخال رقم المرجع/الاسترداد.', + 'negative_amount_invalid' => 'لا يمكن أن يكون المبلغ المدفوع سالبًا.', '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 1e8cc5f4d..b5be11fc6 100644 --- a/app/Language/ar-LB/Sales.php +++ b/app/Language/ar-LB/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'حساب', 'add_payment' => 'إضافة دفع', 'amount_due' => 'المبلغ المطلوب', + 'amount_due_not_covered' => 'المدفوعات لا تغطي المبلغ المستحق.', 'amount_tendered' => 'المبلغ المدفوع', 'authorized_signature' => 'توقيع معتمد', 'cancel_sale' => 'الغاء عملية البيع', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => 'يجب إدخال رقم للمبلغ الفعلى المدفوع.', 'must_enter_numeric_giftcard' => 'رمز بطاقة الهدية يجب أن يكتون ارقام فقط.', 'must_enter_reference_code' => 'يجب إدخال رقم المرجع/الاسترداد.', + 'negative_amount_invalid' => 'لا يمكن أن يكون المبلغ المدفوع سالبًا.', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/az/Sales.php b/app/Language/az/Sales.php index e3b16ac71..b67ec818e 100644 --- a/app/Language/az/Sales.php +++ b/app/Language/az/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Hesab #', 'add_payment' => 'Ödəniş Əlavə Etmək', 'amount_due' => 'Qalıq', + 'amount_due_not_covered' => 'Ödənişlər ödəniləcək məbləği əhatə etmir.', 'amount_tendered' => 'Ödənilən məbləğ', 'authorized_signature' => 'Səlahiyyətli İmza', 'cancel_sale' => 'İmtina', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/bg/Sales.php b/app/Language/bg/Sales.php index 1edcb8b09..0f8503b12 100644 --- a/app/Language/bg/Sales.php +++ b/app/Language/bg/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Номер на акаунт', 'add_payment' => 'Добавяне на плащане', 'amount_due' => 'Дължима сума', + 'amount_due_not_covered' => 'Плащанията не покриват дължимата сума.', 'amount_tendered' => 'Предоставена сума', 'authorized_signature' => 'Оторизиран подпис', 'cancel_sale' => 'Отказ', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => 'Сумата Предложена трябва да е число.', 'must_enter_numeric_giftcard' => 'Gift Card номера трябва да бъде число.', 'must_enter_reference_code' => 'Трябва да се въведе референтен/извличащ номер.', + 'negative_amount_invalid' => 'Предоставената сума не може да бъде отрицателна.', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/bs/Sales.php b/app/Language/bs/Sales.php index 6f2d6e709..a7e7e1a59 100644 --- a/app/Language/bs/Sales.php +++ b/app/Language/bs/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Broj računa', 'add_payment' => 'Plaćanje', 'amount_due' => 'Iznos duga', + 'amount_due_not_covered' => 'Uplate ne pokrivaju dospjeli iznos.', 'amount_tendered' => 'Ponuđeni iznos', 'authorized_signature' => 'Ovlašćeni potpis', 'cancel_sale' => 'Otkaži', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/ckb/Sales.php b/app/Language/ckb/Sales.php index e84492f16..94ce84dfd 100644 --- a/app/Language/ckb/Sales.php +++ b/app/Language/ckb/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'هەژمار #', 'add_payment' => 'زیادکردنی پارەدان', 'amount_due' => 'بڕی پارە کە دەبێ بدرێت', + 'amount_due_not_covered' => 'پارەدانەکان بڕی پێویست دانپۆشین نەکردووە.', 'amount_tendered' => 'بڕی پێشکەشکراو', 'authorized_signature' => 'واژۆی ڕێگەپێدراو', 'cancel_sale' => 'هەڵوەشاندنەوە', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => 'بڕی پێشکەشکراو دەبێت ژمارەیەک بێت.', 'must_enter_numeric_giftcard' => 'ژمارەی کارتی دیاری دەبێت ژمارەیەک بێت.', 'must_enter_reference_code' => 'ژمارەی مەرجع/وەرگرتن دەبێت بنووسرێت.', + 'negative_amount_invalid' => 'بڕی پێشکەشکراو ناتوانێت نەرێنی بێت.', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/cs/Sales.php b/app/Language/cs/Sales.php index fc7efd2fe..05b417250 100644 --- a/app/Language/cs/Sales.php +++ b/app/Language/cs/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '', 'add_payment' => 'Přidat platbu', 'amount_due' => 'K úhradě', + 'amount_due_not_covered' => 'Platby nepokrývají splatnou částku.', 'amount_tendered' => 'Uhrazeno', 'authorized_signature' => '', 'cancel_sale' => 'Zrušit', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/da/Sales.php b/app/Language/da/Sales.php index a750dcabe..d51cd04f2 100644 --- a/app/Language/da/Sales.php +++ b/app/Language/da/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '', 'add_payment' => '', 'amount_due' => '', + 'amount_due_not_covered' => 'Betalingerne dækker ikke det skyldige beløb.', 'amount_tendered' => '', 'authorized_signature' => '', 'cancel_sale' => '', @@ -131,6 +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_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 829fe3896..b4da7e86a 100644 --- a/app/Language/de-CH/Sales.php +++ b/app/Language/de-CH/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '', 'add_payment' => 'Zahlung', 'amount_due' => 'fälliger Betrag', + 'amount_due_not_covered' => 'Die Zahlungen decken den fälligen Betrag nicht.', 'amount_tendered' => 'Erhalten', 'authorized_signature' => '', 'cancel_sale' => 'Annullieren', @@ -131,6 +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_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 b13ba0fb5..7dbc113b3 100644 --- a/app/Language/de-DE/Sales.php +++ b/app/Language/de-DE/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Kundennummer', 'add_payment' => 'Zahlung', 'amount_due' => 'fälliger Betrag', + 'amount_due_not_covered' => 'Die Zahlungen decken den fälligen Betrag nicht.', 'amount_tendered' => 'Erhalten', 'authorized_signature' => 'Unterschrift', 'cancel_sale' => 'Annullieren', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/el/Sales.php b/app/Language/el/Sales.php index e565d2940..4f2bb4b32 100644 --- a/app/Language/el/Sales.php +++ b/app/Language/el/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Λογαριασμός #', 'add_payment' => 'Προσθήκη Πληρωμής', 'amount_due' => 'Ποσό επιστροφής', + 'amount_due_not_covered' => 'Οι πληρωμές δεν καλύπτουν το οφειλόμενο ποσό.', 'amount_tendered' => 'Ποσό Είσπραξης', 'authorized_signature' => 'Εγκεκριμένη Υπογραφή', 'cancel_sale' => 'Ακύρωση', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => 'Το Ποσό Είσπραξης πρέπει να είναι Αριθμός.', 'must_enter_numeric_giftcard' => 'Ο Αριθμός της Δωροκάρτας πρέπει να είναι αριθμός.', 'must_enter_reference_code' => 'Πρέπει να εισαχθεί αριθμός αναφοράς/ανάκτησης.', + 'negative_amount_invalid' => 'Το ποσό είσπραξης δεν μπορεί να είναι αρνητικό.', '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 cfa866750..e0d026f08 100644 --- a/app/Language/en-GB/Sales.php +++ b/app/Language/en-GB/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Account #', 'add_payment' => 'Add Payment', 'amount_due' => 'Amount Due', + 'amount_due_not_covered' => 'Payments do not cover the amount due.', 'amount_tendered' => 'Amount Tendered', 'authorized_signature' => 'Authorised Signature', 'bank_transfer' => 'Bank Transfer', @@ -132,6 +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_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 6cbf2a045..f52013ea0 100644 --- a/app/Language/en/Sales.php +++ b/app/Language/en/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Account #', 'add_payment' => 'Add Payment', 'amount_due' => 'Amount Due', + 'amount_due_not_covered' => 'Payments do not cover the amount due.', 'amount_tendered' => 'Amount Tendered', 'authorized_signature' => 'Authorized Signature', 'bank_transfer' => 'Bank Transfer', @@ -132,6 +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_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 7d7d9481d..e78f1dad8 100644 --- a/app/Language/es-ES/Sales.php +++ b/app/Language/es-ES/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Cuenta #', 'add_payment' => 'Agregar Pago', 'amount_due' => 'Monto Adeudado', + 'amount_due_not_covered' => 'Los pagos no cubren el importe adeudado.', 'amount_tendered' => 'Cantidad Recibida', 'authorized_signature' => 'Firma Autorizada', 'bank_transfer' => 'Transferencia Bancaria', @@ -132,6 +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_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 236ac227c..2e0e7dfd5 100644 --- a/app/Language/es-MX/Sales.php +++ b/app/Language/es-MX/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Cuenta #', 'add_payment' => 'Agregar Pago', 'amount_due' => 'Monto de adeudo', + 'amount_due_not_covered' => 'Los pagos no cubren el monto adeudado.', 'amount_tendered' => 'Cantidad Recibida', 'authorized_signature' => 'Firma Autorizada', 'bank_transfer' => 'Transferencia Bancaria', @@ -132,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/fa/Sales.php b/app/Language/fa/Sales.php index 436ee0c70..b3a1d7341 100644 --- a/app/Language/fa/Sales.php +++ b/app/Language/fa/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'حساب #', 'add_payment' => 'افزودن پرداخت', 'amount_due' => 'مبلغ پرداختی', + 'amount_due_not_covered' => 'پرداخت‌ها مبلغ بدهی را پوشش نمی‌دهند.', 'amount_tendered' => 'مبلغ مناقصه', 'authorized_signature' => 'امضای مجاز', 'cancel_sale' => 'لغو', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => 'مقدار پیشنهادی باید یک عدد باشد.', 'must_enter_numeric_giftcard' => 'شماره کارت هدیه باید یک عدد باشد.', 'must_enter_reference_code' => 'شماره مرجع/بازیابی باید وارد شود.', + 'negative_amount_invalid' => 'مبلغ مناقصه نمی‌تواند منفی باشد.', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/fr/Sales.php b/app/Language/fr/Sales.php index a8bab15b2..effaf9519 100644 --- a/app/Language/fr/Sales.php +++ b/app/Language/fr/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '# Compte', 'add_payment' => 'Ajout Paiement', 'amount_due' => 'Montant à Payer', + 'amount_due_not_covered' => 'Les paiements ne couvrent pas le montant dû.', 'amount_tendered' => 'Montant Présenté', 'authorized_signature' => 'Signature autorisée', 'bank_transfer' => 'Virement Bancaire', @@ -132,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/he/Sales.php b/app/Language/he/Sales.php index 45f2ed995..d7f01a71b 100644 --- a/app/Language/he/Sales.php +++ b/app/Language/he/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'חשבון מס #', 'add_payment' => 'הוסף תשלום', 'amount_due' => 'סכום לתשלום', + 'amount_due_not_covered' => 'התשלומים אינם מכסים את הסכום לתשלום.', 'amount_tendered' => 'סכום ההצעה', 'authorized_signature' => 'חתימת מורשה', 'cancel_sale' => 'בטל', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => 'סכום ההצעה חייב להיות מספר.', 'must_enter_numeric_giftcard' => 'מספר כרטיס המתנה חייב להיות מספר.', 'must_enter_reference_code' => 'יש להזין מספר אסמכתא/אחזור.', + 'negative_amount_invalid' => 'סכום ההצעה לא יכול להיות שלילי.', '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 60ec03a2a..5af072ca7 100644 --- a/app/Language/hr-HR/Sales.php +++ b/app/Language/hr-HR/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '', 'add_payment' => 'Dodaj plaćanje', 'amount_due' => 'Iznos duga', + 'amount_due_not_covered' => 'Uplate ne pokrivaju dospjeli iznos.', 'amount_tendered' => 'Ponuđeni iznos', 'authorized_signature' => '', 'cancel_sale' => 'Otkaži', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/hu/Sales.php b/app/Language/hu/Sales.php index 802f55037..d3ddff4e1 100644 --- a/app/Language/hu/Sales.php +++ b/app/Language/hu/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '', 'add_payment' => 'Fiz. hozzáad.', 'amount_due' => 'Fennmaradó', + 'amount_due_not_covered' => 'A befizetések nem fedezik az esedékes összeget.', 'amount_tendered' => 'Összeg', 'authorized_signature' => '', 'cancel_sale' => 'Mégsem', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/hy/Sales.php b/app/Language/hy/Sales.php index d8b1024a2..5c3dccb31 100644 --- a/app/Language/hy/Sales.php +++ b/app/Language/hy/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '', 'add_payment' => '', 'amount_due' => '', + 'amount_due_not_covered' => 'Վճարումները չեն ծածկում վճարման ենթակա գումարը.', 'amount_tendered' => '', 'authorized_signature' => '', 'cancel_sale' => '', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => '', 'must_enter_numeric_giftcard' => '', 'must_enter_reference_code' => 'Պետք է մուտքագրվի հղման/որոնման համարը։', + 'negative_amount_invalid' => 'Վճարված գումարը չի կարող բացասական լինել.', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/id/Sales.php b/app/Language/id/Sales.php index 8ecfb96bd..dcce42ebe 100644 --- a/app/Language/id/Sales.php +++ b/app/Language/id/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Akun #', 'add_payment' => 'Terima', 'amount_due' => 'Uang Kembalian', + 'amount_due_not_covered' => 'Pembayaran tidak menutupi jumlah yang harus dibayar.', 'amount_tendered' => 'Nilai Pembayaran', 'authorized_signature' => 'Tanda tangan', 'cancel_sale' => 'Batal Jual', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/it/Sales.php b/app/Language/it/Sales.php index cf51cb444..5a78dca0b 100644 --- a/app/Language/it/Sales.php +++ b/app/Language/it/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Account #', 'add_payment' => 'Aggiungi Pagamento', 'amount_due' => 'Importo Dovuto', + 'amount_due_not_covered' => 'I pagamenti non coprono l\'importo dovuto.', 'amount_tendered' => 'Importo Offerto', 'authorized_signature' => 'Firma Autorizzata', 'cancel_sale' => 'Annulla', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/ka/Sales.php b/app/Language/ka/Sales.php index 97142c44b..0665a872f 100644 --- a/app/Language/ka/Sales.php +++ b/app/Language/ka/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '', 'add_payment' => '', 'amount_due' => '', + 'amount_due_not_covered' => 'გადახდები არ ფარავს გადასახდელ თანხას.', 'amount_tendered' => '', 'authorized_signature' => '', 'bank_transfer' => '', @@ -132,6 +133,7 @@ return [ 'must_enter_numeric' => '', 'must_enter_numeric_giftcard' => '', 'must_enter_reference_code' => 'მიუთითეთ საცნობარო/მოძიების ნომერი.', + 'negative_amount_invalid' => 'გადახდილი თანხა არ შეიძლება იყოს უარყოფითი.', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/km/Sales.php b/app/Language/km/Sales.php index dc16323d2..04a03146b 100644 --- a/app/Language/km/Sales.php +++ b/app/Language/km/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'លេខគណនី', 'add_payment' => 'បញ្ជូលរបៀបបង់ប្រាក់', 'amount_due' => 'ទឹកប្រាក់ដែលត្រូវបង់', + 'amount_due_not_covered' => 'ការទូទាត់មិនគ្របដណ្តប់ចំនួនទឹកប្រាក់ដែលត្រូវបង់ទេ។', 'amount_tendered' => 'ទឹកប្រាក់បានមកពីការបង់ទាំងផ្សេងៗ', 'authorized_signature' => 'ហត្ថលេខា', 'cancel_sale' => 'បោះបង់', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => '', 'must_enter_numeric_giftcard' => '', 'must_enter_reference_code' => 'លេខយោង/ទាញយកត្រូវតែបញ្ចូល។', + 'negative_amount_invalid' => 'ទឹកប្រាក់បានមកពីការបង់មិនអាចជាចំនួនអវិជ្ជមានបានទេ។', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/lo/Sales.php b/app/Language/lo/Sales.php index 2e306c192..aa6aaedd7 100644 --- a/app/Language/lo/Sales.php +++ b/app/Language/lo/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '', 'add_payment' => 'Add Payment', 'amount_due' => 'Amount Due', + 'amount_due_not_covered' => 'ການຊຳລະເງິນບໍ່ຄຸ້ມຈຳນວນທີ່ຕ້ອງຈ່າຍ.', 'amount_tendered' => 'Amount Tendered', 'authorized_signature' => '', 'cancel_sale' => 'Cancel', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/ml/Sales.php b/app/Language/ml/Sales.php index 7fc6b326f..f8df0a093 100644 --- a/app/Language/ml/Sales.php +++ b/app/Language/ml/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '', 'add_payment' => '', 'amount_due' => '', + 'amount_due_not_covered' => 'പേയ്‌മെന്റുകൾ അടയ്‌ക്കേണ്ട തുക ഉൾക്കൊള്ളുന്നില്ല.', 'amount_tendered' => '', 'authorized_signature' => '', 'cancel_sale' => '', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => '', 'must_enter_numeric_giftcard' => '', 'must_enter_reference_code' => 'റഫറൻസ്/റിട്രീവൽ നമ്പർ നൽകണം.', + 'negative_amount_invalid' => 'നൽകിയ തുക നെഗറ്റീവ് ആകാൻ പാടില്ല.', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/nb/Sales.php b/app/Language/nb/Sales.php index 22de8d58a..ff70bdf49 100644 --- a/app/Language/nb/Sales.php +++ b/app/Language/nb/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '', 'add_payment' => '', 'amount_due' => '', + 'amount_due_not_covered' => 'Betalingene dekker ikke det skyldige beløpet.', 'amount_tendered' => '', 'authorized_signature' => '', 'cancel_sale' => '', @@ -131,6 +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_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 f614253b2..c84d27c3e 100644 --- a/app/Language/nl-BE/Sales.php +++ b/app/Language/nl-BE/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Btw-nummer', 'add_payment' => 'Betaal', 'amount_due' => 'Te betalen', + 'amount_due_not_covered' => 'De betalingen dekken het verschuldigde bedrag niet.', 'amount_tendered' => 'Ontvangen bedrag', 'authorized_signature' => 'Handtekening', 'cancel_sale' => 'Annuleer', @@ -131,6 +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_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 51066b456..c92a6cd03 100644 --- a/app/Language/nl-NL/Sales.php +++ b/app/Language/nl-NL/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Account #', 'add_payment' => 'Betaling toevoegen', 'amount_due' => 'Te betalen bedrag', + 'amount_due_not_covered' => 'De betalingen dekken het verschuldigde bedrag niet.', 'amount_tendered' => 'Betaald bedrag', 'authorized_signature' => 'Geautoriseerde handtekening', 'cancel_sale' => 'Annuleren', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/pl/Sales.php b/app/Language/pl/Sales.php index e0d3afe07..ffd760b97 100644 --- a/app/Language/pl/Sales.php +++ b/app/Language/pl/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '', 'add_payment' => '', 'amount_due' => '', + 'amount_due_not_covered' => 'Płatności nie pokrywają należnej kwoty.', 'amount_tendered' => '', 'authorized_signature' => '', 'cancel_sale' => '', @@ -131,6 +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_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 a0301fea3..d22efc580 100644 --- a/app/Language/pt-BR/Sales.php +++ b/app/Language/pt-BR/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Montante º', 'add_payment' => 'Pagar', 'amount_due' => 'Diferença', + 'amount_due_not_covered' => 'Os pagamentos não cobrem o valor devido.', 'amount_tendered' => 'A Pagar', 'authorized_signature' => 'Assinatura autorizada', 'cancel_sale' => 'Cancelar', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/ro/Sales.php b/app/Language/ro/Sales.php index cc532bf8c..e792bf89a 100644 --- a/app/Language/ro/Sales.php +++ b/app/Language/ro/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Cont #', 'add_payment' => 'Adauga Plata', 'amount_due' => 'Suma datorata', + 'amount_due_not_covered' => 'Plățile nu acoperă suma datorată.', 'amount_tendered' => 'Suma Oferita', 'authorized_signature' => 'Semnatura Autorizata', 'cancel_sale' => 'Anuleaza', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/ru/Sales.php b/app/Language/ru/Sales.php index deae24e7d..fee55168f 100644 --- a/app/Language/ru/Sales.php +++ b/app/Language/ru/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Счет #', 'add_payment' => 'Добавить оплату', 'amount_due' => 'Сумма задолженности', + 'amount_due_not_covered' => 'Платежи не покрывают причитающуюся сумму.', 'amount_tendered' => 'Предложенная сумма', 'authorized_signature' => 'Подпись уполномоченного лица', 'cancel_sale' => 'Отменить продажу', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => 'Предложенная сумма должна быть числом.', 'must_enter_numeric_giftcard' => 'Номер подарочной карты должен быть числом.', 'must_enter_reference_code' => 'Необходимо ввести справочный/поисковый номер.', + 'negative_amount_invalid' => 'Предложенная сумма не может быть отрицательной.', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/sv/Sales.php b/app/Language/sv/Sales.php index 219babcb5..152147df5 100644 --- a/app/Language/sv/Sales.php +++ b/app/Language/sv/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Konto #', 'add_payment' => 'Lägg till betalning', 'amount_due' => 'Belopp', + 'amount_due_not_covered' => 'Betalningarna täcker inte det förfallna beloppet.', 'amount_tendered' => 'Upplagt belopp', 'authorized_signature' => 'Auktoriserad signatur', 'cancel_sale' => 'Avbryt', @@ -131,6 +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_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 f071f40c7..20e7e3eca 100644 --- a/app/Language/sw-KE/Sales.php +++ b/app/Language/sw-KE/Sales.php @@ -1,10 +1,10 @@ "Hatua (Action)", 'account_number' => 'Nambari ya Akaunti', 'add_payment' => 'Ongeza Malipo', 'amount_due' => 'Kiasi Kinachodaiwa', + 'amount_due_not_covered' => 'Malipo hayafikii kiwango kinachodaiwa.', 'amount_tendered' => 'Kiasi Kilicholipwa', 'authorized_signature' => 'Sahihi Iliyothibitishwa', 'cancel_sale' => 'Ghairi', @@ -132,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', @@ -226,14 +227,14 @@ return [ 'unsuccessfully_suspended_sale' => 'Imeshindikana kusitisha Mauzo.', 'unsuccessfully_updated' => 'Kusasisha mauzo kimeshindikana.', 'unsuspend' => 'Ondoa Kusitishwa', - 'unsuspend_and_delete' => 'Hatua', - 'update' => 'Sasisha', - 'upi' => 'UPI', - 'visa' => '', - 'wholesale' => '', - 'work_order' => 'Agizo la Kazi (Work Order)', - 'work_order_number' => 'Nambari ya Agizo la Kazi', - 'work_order_number_duplicate' => 'Nambari ya Agizo la Kazi lazima iwe ya kipekee.', - 'work_order_sent' => 'Agizo la Kazi limetumwa kwa', - 'work_order_unsent' => 'Imeshindikana kutuma Agizo la Kazi kwa', + 'unsuspend_and_delete' => 'Hatua', + 'update' => 'Sasisha', + 'upi' => 'UPI', + 'visa' => '', + 'wholesale' => '', + 'work_order' => 'Agizo la Kazi (Work Order)', + 'work_order_number' => 'Nambari ya Agizo la Kazi', + 'work_order_number_duplicate' => 'Nambari ya Agizo la Kazi lazima iwe ya kipekee.', + 'work_order_sent' => 'Agizo la Kazi limetumwa kwa', + 'work_order_unsent' => 'Imeshindikana kutuma Agizo la Kazi kwa', ]; diff --git a/app/Language/sw-TZ/Sales.php b/app/Language/sw-TZ/Sales.php index f071f40c7..20e7e3eca 100644 --- a/app/Language/sw-TZ/Sales.php +++ b/app/Language/sw-TZ/Sales.php @@ -1,10 +1,10 @@ "Hatua (Action)", 'account_number' => 'Nambari ya Akaunti', 'add_payment' => 'Ongeza Malipo', 'amount_due' => 'Kiasi Kinachodaiwa', + 'amount_due_not_covered' => 'Malipo hayafikii kiwango kinachodaiwa.', 'amount_tendered' => 'Kiasi Kilicholipwa', 'authorized_signature' => 'Sahihi Iliyothibitishwa', 'cancel_sale' => 'Ghairi', @@ -132,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', @@ -226,14 +227,14 @@ return [ 'unsuccessfully_suspended_sale' => 'Imeshindikana kusitisha Mauzo.', 'unsuccessfully_updated' => 'Kusasisha mauzo kimeshindikana.', 'unsuspend' => 'Ondoa Kusitishwa', - 'unsuspend_and_delete' => 'Hatua', - 'update' => 'Sasisha', - 'upi' => 'UPI', - 'visa' => '', - 'wholesale' => '', - 'work_order' => 'Agizo la Kazi (Work Order)', - 'work_order_number' => 'Nambari ya Agizo la Kazi', - 'work_order_number_duplicate' => 'Nambari ya Agizo la Kazi lazima iwe ya kipekee.', - 'work_order_sent' => 'Agizo la Kazi limetumwa kwa', - 'work_order_unsent' => 'Imeshindikana kutuma Agizo la Kazi kwa', + 'unsuspend_and_delete' => 'Hatua', + 'update' => 'Sasisha', + 'upi' => 'UPI', + 'visa' => '', + 'wholesale' => '', + 'work_order' => 'Agizo la Kazi (Work Order)', + 'work_order_number' => 'Nambari ya Agizo la Kazi', + 'work_order_number_duplicate' => 'Nambari ya Agizo la Kazi lazima iwe ya kipekee.', + 'work_order_sent' => 'Agizo la Kazi limetumwa kwa', + 'work_order_unsent' => 'Imeshindikana kutuma Agizo la Kazi kwa', ]; diff --git a/app/Language/ta/Sales.php b/app/Language/ta/Sales.php index 3597b359f..884110b7e 100644 --- a/app/Language/ta/Sales.php +++ b/app/Language/ta/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'கணக்கு #', 'add_payment' => 'கட்டணத்தைச் சேர்க்கவும்', 'amount_due' => 'செலுத்த வேண்டிய தொகை', + 'amount_due_not_covered' => 'செலுத்தப்பட்ட தொகை நிலுவைத் தொகையை ஈடுசெய்யவில்லை.', 'amount_tendered' => 'கொடுத்த தொகை', 'authorized_signature' => 'அங்கீகரிக்கப்பட்ட கையெழுத்து', 'cancel_sale' => 'ரத்துசெய்', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => 'தொகை ஒரு எண்ணாக இருக்க வேண்டும்.', 'must_enter_numeric_giftcard' => 'பரிசு அட்டை எண் ஒரு எண்ணாக இருக்க வேண்டும்.', 'must_enter_reference_code' => 'குறிப்பு/மீட்டெடுப்பு எண் உள்ளிட வேண்டும்.', + 'negative_amount_invalid' => 'கொடுத்த தொகை எதிர்மறையாக இருக்கக்கூடாது.', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/th/Sales.php b/app/Language/th/Sales.php index de5b395a3..c85ccea53 100644 --- a/app/Language/th/Sales.php +++ b/app/Language/th/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'บัญชี #', 'add_payment' => 'เพิ่มบิล', 'amount_due' => 'ยอดค้างชำระ', + 'amount_due_not_covered' => 'การชำระเงินไม่ครอบคลุมจำนวนที่ต้องชำระ', 'amount_tendered' => 'ชำระเข้ามา', 'authorized_signature' => 'ลายเซ็นผู้มีอำนาจ', 'cancel_sale' => 'ยกเลิกการขาย', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => 'จำนวนที่ถุกประมูลต้องใส่ข้อมุลที่เปนตัวเลข', 'must_enter_numeric_giftcard' => 'เลขที่บัตรของขวัญ ต้องใส่ตัวเลขเท่านั้น', 'must_enter_reference_code' => 'ต้องระบุหมายเลขอ้างอิง/การดึงข้อมูล', + 'negative_amount_invalid' => 'ชำระเข้ามาไม่สามารถเป็นค่าติดลบได้', 'negative_discount_invalid' => 'ส่วนลดไม่สามารถเป็นค่าติดลบได้', 'negative_price_invalid' => 'ราคาไม่สามารถเป็นค่าติดลบได้', 'negative_quantity_invalid' => 'จำนวนไม่สามารถเป็นค่าติดลบได้', diff --git a/app/Language/tl/Sales.php b/app/Language/tl/Sales.php index e55ad435c..de955aa89 100644 --- a/app/Language/tl/Sales.php +++ b/app/Language/tl/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Account #', 'add_payment' => 'Add Payment', 'amount_due' => 'Amount Due', + 'amount_due_not_covered' => 'Hindi sapat ang mga bayad para sa kabuuang dapat bayaran.', 'amount_tendered' => 'Amount Tendered', 'authorized_signature' => 'Authorized Signature', 'cancel_sale' => 'Cancel', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/tr/Sales.php b/app/Language/tr/Sales.php index ae53f0a68..39d4fec80 100644 --- a/app/Language/tr/Sales.php +++ b/app/Language/tr/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Hesap Numarası', 'add_payment' => 'Ödeme Ekle', 'amount_due' => 'Kalan Ödeme', + 'amount_due_not_covered' => 'Ödemeler, ödenmesi gereken tutarı karşılamıyor.', 'amount_tendered' => 'Ödenen Tutar', 'authorized_signature' => 'Yetkili İmza', 'cancel_sale' => 'İptal Et', @@ -131,6 +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_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/uk/Sales.php b/app/Language/uk/Sales.php index a3282d7d9..cfe2f6fce 100644 --- a/app/Language/uk/Sales.php +++ b/app/Language/uk/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Номер рахунку', 'add_payment' => 'Додати платіж', 'amount_due' => 'Сума заборгованості', + 'amount_due_not_covered' => 'Платежі не покривають суму до сплати.', 'amount_tendered' => 'Запропонована сума', 'authorized_signature' => 'Підпис уповноваженої особи', 'cancel_sale' => 'Відмінити', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => 'Введена сума повинна бути числом.', 'must_enter_numeric_giftcard' => 'Номер подарункової картки повинен бути цифровим.', 'must_enter_reference_code' => 'Необхідно ввести довідковий/пошуковий номер.', + 'negative_amount_invalid' => 'Запропонована сума не може бути від\'ємною.', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/ur/Sales.php b/app/Language/ur/Sales.php index d1110de40..59dcb5f23 100644 --- a/app/Language/ur/Sales.php +++ b/app/Language/ur/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'اکاوٗنٹ', 'add_payment' => '', 'amount_due' => '', + 'amount_due_not_covered' => 'ادائیگیاں واجب الادا رقم کو پورا نہیں کرتیں۔', 'amount_tendered' => '', 'authorized_signature' => '', 'cancel_sale' => '', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => '', 'must_enter_numeric_giftcard' => '', 'must_enter_reference_code' => 'حوالہ/بازیابی نمبر درج کرنا ضروری ہے۔', + 'negative_amount_invalid' => 'ادا کی گئی رقم منفی نہیں ہو سکتی۔', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Language/vi/Sales.php b/app/Language/vi/Sales.php index 1092d59f9..6a6fe515a 100644 --- a/app/Language/vi/Sales.php +++ b/app/Language/vi/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => 'Tài khoản #', 'add_payment' => 'Thêm thanh toán', 'amount_due' => 'Số còn lại phải thanh toán', + 'amount_due_not_covered' => 'Các khoản thanh toán không đủ để trang trải số tiền phải trả.', 'amount_tendered' => 'Số tiền thanh toán', 'authorized_signature' => 'Chữ ký ủy quyền', 'cancel_sale' => 'Thôi', @@ -131,6 +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_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 b59fcf423..2f5ce5c10 100644 --- a/app/Language/zh-Hans/Sales.php +++ b/app/Language/zh-Hans/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '', 'add_payment' => '新增付款', 'amount_due' => '商品金額', + 'amount_due_not_covered' => '付款金额未覆盖应付金额。', 'amount_tendered' => '已收帳款', 'authorized_signature' => '', 'cancel_sale' => '取消銷售', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => '已收帳款必須輸入數值', 'must_enter_numeric_giftcard' => '禮金券編號必須輸入數值', 'must_enter_reference_code' => '必须输入参考/检索编号。', + 'negative_amount_invalid' => '已收帐款不能为负数。', '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 3c8c4beba..48d1694b6 100644 --- a/app/Language/zh-Hant/Sales.php +++ b/app/Language/zh-Hant/Sales.php @@ -4,6 +4,7 @@ return [ 'account_number' => '帳戶 #', 'add_payment' => '新增付款', 'amount_due' => '商品金額', + 'amount_due_not_covered' => '付款金額未涵蓋應付金額。', 'amount_tendered' => '已收帳款', 'authorized_signature' => '授權簽名', 'cancel_sale' => '取消銷售', @@ -131,6 +132,7 @@ return [ 'must_enter_numeric' => '已收帳款必須輸入數值.', 'must_enter_numeric_giftcard' => '禮金券編號必須輸入數值.', 'must_enter_reference_code' => '必須輸入參考/檢索編號。', + 'negative_amount_invalid' => '已收帳款不能為負數。', 'negative_discount_invalid' => '', 'negative_price_invalid' => '', 'negative_quantity_invalid' => '', diff --git a/app/Models/Giftcard.php b/app/Models/Giftcard.php index 347a6d832..a12466265 100644 --- a/app/Models/Giftcard.php +++ b/app/Models/Giftcard.php @@ -316,6 +316,10 @@ class Giftcard extends Model */ public function decrementGiftcardValue(string $giftcardNumber, float $amount): bool { + if ($amount <= 0) { + return false; + } + $builder = $this->db->table('giftcards'); $builder->where('giftcard_number', $giftcardNumber); $builder->where('deleted', 0); diff --git a/app/Models/Sale.php b/app/Models/Sale.php index c0dfa4348..1793315f1 100644 --- a/app/Models/Sale.php +++ b/app/Models/Sale.php @@ -524,13 +524,13 @@ class Sale extends Model int $customerId, int $employeeId, string $comment, - ?string $invoice_number, - ?string $work_order_number, - ?string $quote_number, - int $sale_type, + ?string $invoiceNumber, + ?string $workOrderNumber, + ?string $quoteNumber, + int $saleType, ?array $payments, - ?int $dinner_table_id, - ?array &$sales_taxes + ?int $dinnerTableId, + ?array &$salesTaxes ): int { // TODO: this method returns the sale_id but the override is expecting it to return a bool. The signature needs to be reworked. Generally when there are more than 3 maybe 4 parameters, there's a good chance that an object needs to be passed rather than so many params. $config = config(OSPOS::class)->settings; $attribute = model(Attribute::class); @@ -539,23 +539,23 @@ class Sale extends Model $inventory = model('Inventory'); $item = model(Item::class); - $item_quantity = model(Item_quantity::class); + $itemQuantity = model(Item_quantity::class); if (count($items) == 0) { // TODO: === return -1; // TODO: Replace -1 with a constant } - $sales_data = [ + $salesData = [ 'sale_time' => date('Y-m-d H:i:s'), 'customer_id' => $customer->exists($customerId) ? $customerId : null, 'employee_id' => $employeeId, 'comment' => $comment, 'sale_status' => $saleStatus, - 'invoice_number' => $invoice_number, - 'quote_number' => $quote_number, - 'work_order_number' => $work_order_number, - 'dinner_table_id' => $dinner_table_id, - 'sale_type' => $sale_type + 'invoice_number' => $invoiceNumber, + 'quote_number' => $quoteNumber, + 'work_order_number' => $workOrderNumber, + 'dinner_table_id' => $dinnerTableId, + 'sale_type' => $saleType ]; // Run these queries as a transaction, we want to make sure we do all or nothing @@ -567,26 +567,27 @@ class Sale extends Model $builder = $this->db->table('sales'); if ($saleId == NEW_ENTRY) { - $builder->insert($sales_data); + $builder->insert($salesData); $saleId = $this->db->insertID(); } else { $builder->where('sale_id', $saleId); - $builder->update($sales_data); + $builder->update($salesData); } - $total_amount = 0; + $totalAmount = 0; $totalAmountUsed = 0; - foreach ($payments as $payment_id => $payment) { - if (!empty(strstr($payment['payment_type'], lang('Sales.giftcard')))) { - $splitPayment = explode(':', $payment['payment_type']); + foreach ($payments as $paymentId => $payment) { + $splitPayment = explode(':', $payment['payment_type'], 2); + $paymentPrefix = $splitPayment[0]; - if (! $giftcard->decrementGiftcardValue($splitPayment[1], (float) $payment['payment_amount'])) { + if ($paymentPrefix === lang('Sales.giftcard')) { + if (empty($splitPayment[1]) || ! $giftcard->decrementGiftcardValue($splitPayment[1], (float) $payment['payment_amount'])) { $this->db->transRollback(); return INSUFFICIENT_GIFTCARD_BALANCE; } - } elseif (!empty(strstr($payment['payment_type'], lang('Sales.rewards')))) { + } elseif ($paymentPrefix === lang('Sales.rewards')) { if (! $customer->adjustRewardPoints($customerId, -(float) $payment['payment_amount'])) { $this->db->transRollback(); @@ -596,7 +597,7 @@ class Sale extends Model $totalAmountUsed = floatval($totalAmountUsed) + floatval($payment['payment_amount']); } - $sales_payments_data = [ + $salesPaymentsData = [ 'sale_id' => $saleId, 'payment_type' => $payment['payment_type'], 'payment_amount' => $payment['payment_amount'], @@ -607,46 +608,46 @@ class Sale extends Model ]; $builder = $this->db->table('sales_payments'); - $builder->insert($sales_payments_data); + $builder->insert($salesPaymentsData); - $total_amount = floatval($total_amount) + floatval($payment['payment_amount']) - floatval($payment['cash_refund']); + $totalAmount = floatval($totalAmount) + floatval($payment['payment_amount']) - floatval($payment['cash_refund']); } - $this->save_customer_rewards($customerId, $saleId, $total_amount, $totalAmountUsed); + $this->save_customer_rewards($customerId, $saleId, $totalAmount, $totalAmountUsed); $customer = $customer->get_info($customerId); - foreach ($items as $line => $item_data) { - $cur_item_info = $item->get_info($item_data['item_id']); + foreach ($items as $line => $itemData) { + $curItemInfo = $item->get_info($itemData['item_id']); - if ($item_data['price'] == 0.00) { - $item_data['discount'] = 0.00; + if ($itemData['price'] == 0.00) { + $itemData['discount'] = 0.00; } - $sales_items_data = [ + $salesItemsData = [ 'sale_id' => $saleId, - 'item_id' => $item_data['item_id'], - 'line' => $item_data['line'], - 'description' => character_limiter($item_data['description'], 255), - 'serialnumber' => character_limiter($item_data['serialnumber'], 30), - 'quantity_purchased' => $item_data['quantity'], - 'discount' => $item_data['discount'], - 'discount_type' => $item_data['discount_type'], - 'item_cost_price' => $item_data['cost_price'], - 'item_unit_price' => $item_data['price'], - 'item_location' => $item_data['item_location'], - 'print_option' => $item_data['print_option'] + 'item_id' => $itemData['item_id'], + 'line' => $itemData['line'], + 'description' => character_limiter($itemData['description'], 255), + 'serialnumber' => character_limiter($itemData['serialnumber'], 30), + 'quantity_purchased' => $itemData['quantity'], + 'discount' => $itemData['discount'], + 'discount_type' => $itemData['discount_type'], + 'item_cost_price' => $itemData['cost_price'], + 'item_unit_price' => $itemData['price'], + 'item_location' => $itemData['item_location'], + 'print_option' => $itemData['print_option'] ]; $builder = $this->db->table('sales_items'); - $builder->insert($sales_items_data); + $builder->insert($salesItemsData); - if ($cur_item_info->stock_type == HAS_STOCK && $saleStatus == COMPLETED) { // TODO: === ? + if ($curItemInfo->stock_type == HAS_STOCK && $saleStatus == COMPLETED) { // TODO: === ? // Update stock quantity if item type is a standard stock item and the sale is a standard sale - if (! $item_quantity->changeQuantity( - $item_data['item_id'], - $item_data['item_location'], - -(float) $item_data['quantity'], + if (! $itemQuantity->changeQuantity( + $itemData['item_id'], + $itemData['item_location'], + -(float) $itemData['quantity'], )) { $this->db->transRollback(); @@ -654,38 +655,38 @@ class Sale extends Model } // If an items was deleted but later returned it's restored with this rule - if ($item_data['quantity'] < 0) { - $item->undelete($item_data['item_id']); + if ($itemData['quantity'] < 0) { + $item->undelete($itemData['item_id']); } // Inventory Count Details - $sale_remarks = 'POS ' . $saleId; // TODO: Use string interpolation here. - $inv_data = [ + $saleRemarks = 'POS ' . $saleId; // TODO: Use string interpolation here. + $invData = [ 'trans_date' => date('Y-m-d H:i:s'), - 'trans_items' => $item_data['item_id'], + 'trans_items' => $itemData['item_id'], 'trans_user' => $employeeId, - 'trans_location' => $item_data['item_location'], - 'trans_comment' => $sale_remarks, - 'trans_inventory' => -$item_data['quantity'] + 'trans_location' => $itemData['item_location'], + 'trans_comment' => $saleRemarks, + 'trans_inventory' => -$itemData['quantity'] ]; - $inventory->insert($inv_data, false); + $inventory->insert($invData, false); } - $attribute->copy_attribute_links($item_data['item_id'], 'sale_id', $saleId); + $attribute->copy_attribute_links($itemData['item_id'], 'sale_id', $saleId); } if ($customerId == NEW_ENTRY || $customer->taxable) { - $this->save_sales_tax($saleId, $sales_taxes[0]); - $this->save_sales_items_taxes($saleId, $sales_taxes[1]); + $this->save_sales_tax($saleId, $salesTaxes[0]); + $this->save_sales_items_taxes($saleId, $salesTaxes[1]); } if ($config['dinner_table_enable']) { - $dinner_table = model(Dinner_table::class); + $dinnerTable = model(Dinner_table::class); if ($saleStatus == COMPLETED) { // TODO: === ? - $dinner_table->release($dinner_table_id); + $dinnerTable->release($dinnerTableId); } else { - $dinner_table->occupy($dinner_table_id); + $dinnerTable->occupy($dinnerTableId); } } diff --git a/tests/Controllers/SalesControllerTest.php b/tests/Controllers/SalesControllerTest.php index 5faad4d94..7f6118e45 100644 --- a/tests/Controllers/SalesControllerTest.php +++ b/tests/Controllers/SalesControllerTest.php @@ -287,7 +287,7 @@ class SalesControllerTest extends CIUnitTestCase 'cost_price' => '1.00', 'total' => $price, 'discounted_total' => $price, - 'print_option' => 1, + 'print_option' => PRINT_YES, 'stock_type' => HAS_NO_STOCK, 'item_type' => ITEM, 'hsn_code' => null, @@ -519,6 +519,185 @@ class SalesControllerTest extends CIUnitTestCase $this->assertEquals('0.01', $cart[1]['price']); } + protected function createGiftcard(float $value): int + { + $giftcardNumber = random_int(1000000, 9999999); + + Database::connect()->table('giftcards')->insert([ + 'giftcard_number' => $giftcardNumber, + 'value' => $value, + 'deleted' => 0, + 'person_id' => null, + ]); + + return $giftcardNumber; + } + + protected function getGiftcardValue(int $giftcardNumber): float + { + $row = Database::connect()->table('giftcards') + ->where('giftcard_number', $giftcardNumber) + ->get() + ->getRow(); + + return (float) $row->value; + } + + public function testCashierCannotForgeGiftcardPaymentTypeWithNegativeAmount(): void + { + $cashierId = $this->createCashierEmployee(); + $itemId = $this->createTestItem(HAS_NO_STOCK); + $giftcardNumber = $this->createGiftcard(100.00); + $this->loginAs($cashierId); + $this->seedCartLine(1, '1.00', $itemId); + + $this->post('/sales/addPayment', [ + 'payment_type' => lang('Sales.giftcard') . ':' . $giftcardNumber, + 'amount_tendered' => '-500', + ]); + + $payments = Services::session()->get('sales_payments'); + $this->assertEmpty($payments); + $this->assertEqualsWithDelta(100.00, $this->getGiftcardValue($giftcardNumber), 0.001); + } + + public function testCashierCanUseLegitimateGiftcardPayment(): void + { + $cashierId = $this->createCashierEmployee(); + $itemId = $this->createTestItem(HAS_NO_STOCK); + $giftcardNumber = $this->createGiftcard(100.00); + $this->loginAs($cashierId); + $this->seedCartLine(1, '1.00', $itemId); + + $this->post('/sales/addPayment', [ + 'payment_type' => lang('Sales.giftcard'), + 'amount_tendered' => (string) $giftcardNumber, + ]); + + $payments = Services::session()->get('sales_payments'); + $this->assertNotEmpty($payments); + $this->assertArrayHasKey(lang('Sales.giftcard') . ':' . $giftcardNumber, $payments); + } + + public function testCashierCannotSubmitNegativeAmountForReferenceCodePayment(): void + { + $cashierId = $this->createCashierEmployee(); + $itemId = $this->createTestItem(HAS_NO_STOCK); + $this->loginAs($cashierId); + $this->seedCartLine(1, '1.00', $itemId); + + $this->post('/sales/addPayment', [ + 'payment_type' => lang('Sales.debit'), + 'amount_tendered' => '-25', + 'reference_code' => 'ABC123', + ]); + + $payments = Services::session()->get('sales_payments'); + $this->assertEmpty($payments); + } + + public function testCashierCanUseLegitimateReferenceCodePayment(): void + { + $cashierId = $this->createCashierEmployee(); + $itemId = $this->createTestItem(HAS_NO_STOCK); + $this->loginAs($cashierId); + $this->seedCartLine(1, '1.00', $itemId); + + $this->post('/sales/addPayment', [ + 'payment_type' => lang('Sales.debit'), + 'amount_tendered' => '25.00', + 'reference_code' => 'ABC123', + ]); + + $payments = Services::session()->get('sales_payments'); + $this->assertNotEmpty($payments); + $this->assertArrayHasKey(lang('Sales.debit'), $payments); + $this->assertSame('ABC123', $payments[lang('Sales.debit')]['reference_code']); + } + + public function testCashierCannotSubmitNegativeAmountForCashPayment(): void + { + $cashierId = $this->createCashierEmployee(); + $itemId = $this->createTestItem(HAS_NO_STOCK); + $this->loginAs($cashierId); + $this->seedCartLine(1, '1.00', $itemId); + + $this->post('/sales/addPayment', [ + 'payment_type' => lang('Sales.cash'), + 'amount_tendered' => '-10', + ]); + + $payments = Services::session()->get('sales_payments'); + $this->assertEmpty($payments); + } + + public function testCashierCanUseLegitimateCashPayment(): void + { + $cashierId = $this->createCashierEmployee(); + $itemId = $this->createTestItem(HAS_NO_STOCK); + $this->loginAs($cashierId); + $this->seedCartLine(1, '1.00', $itemId); + + $this->post('/sales/addPayment', [ + 'payment_type' => lang('Sales.cash'), + 'amount_tendered' => '10.00', + ]); + + $payments = Services::session()->get('sales_payments'); + $this->assertNotEmpty($payments); + $this->assertArrayHasKey(lang('Sales.cash'), $payments); + } + + public function testPostCompleteRejectsSaleWithInsufficientPayments(): void + { + $cashierId = $this->createCashierEmployee(); + $itemId = $this->createTestItem(HAS_NO_STOCK); + $this->loginAs($cashierId); + $this->seedCartLine(1, '100.00', $itemId); + $this->withSession(array_merge($this->session, ['sale_id' => NEW_ENTRY])); + + $salesCountBefore = Database::connect()->table('sales')->countAllResults(); + + $this->post('/sales/complete'); + + $salesCountAfter = Database::connect()->table('sales')->countAllResults(); + $this->assertSame($salesCountBefore, $salesCountAfter); + } + + public function testPostCompleteAllowsZeroPaymentQuoteCompletion(): void + { + $cashierId = $this->createCashierEmployee(); + $itemId = $this->createTestItem(HAS_NO_STOCK); + $this->loginAs($cashierId); + $this->seedCartLine(1, '100.00', $itemId); + $this->withSession(array_merge($this->session, ['sale_id' => NEW_ENTRY, 'sales_mode' => 'sale_quote'])); + + $salesCountBefore = Database::connect()->table('sales')->countAllResults(); + + $response = $this->post('/sales/complete'); + + $salesCountAfter = Database::connect()->table('sales')->countAllResults(); + $this->assertSame($salesCountBefore + 1, $salesCountAfter); + $response->assertDontSee(lang('Sales.amount_due_not_covered')); + } + + public function testPostCompleteAllowsZeroPaymentInvoiceCompletion(): void + { + $cashierId = $this->createCashierEmployee(); + $itemId = $this->createTestItem(HAS_NO_STOCK); + $this->loginAs($cashierId); + $this->seedCartLine(1, '100.00', $itemId); + $this->withSession(array_merge($this->session, ['sale_id' => NEW_ENTRY, 'sales_mode' => 'sale_invoice'])); + + $salesCountBefore = Database::connect()->table('sales')->countAllResults(); + + $response = $this->post('/sales/complete'); + + $salesCountAfter = Database::connect()->table('sales')->countAllResults(); + $this->assertSame($salesCountBefore + 1, $salesCountAfter); + $response->assertDontSee(lang('Sales.amount_due_not_covered')); + } + public function testRegisterEscapesMaliciousTaxName(): void { $cashierId = $this->createCashierEmployee(); diff --git a/tests/Models/GiftcardTest.php b/tests/Models/GiftcardTest.php index 9e6198ebc..e57b88cd9 100644 --- a/tests/Models/GiftcardTest.php +++ b/tests/Models/GiftcardTest.php @@ -81,6 +81,28 @@ class GiftcardTest extends CIUnitTestCase $this->assertEqualsWithDelta(50.00, $this->getGiftcardValue($giftcardNumber), 0.001); } + public function testDecrementGiftcardValueRejectsNegativeAmount(): void + { + $giftcardNumber = $this->createGiftcard(100.00); + $giftcardModel = model(Giftcard::class); + + $result = $giftcardModel->decrementGiftcardValue((string) $giftcardNumber, -500.00); + + $this->assertFalse($result); + $this->assertEqualsWithDelta(100.00, $this->getGiftcardValue($giftcardNumber), 0.001); + } + + public function testDecrementGiftcardValueRejectsZeroAmount(): void + { + $giftcardNumber = $this->createGiftcard(100.00); + $giftcardModel = model(Giftcard::class); + + $result = $giftcardModel->decrementGiftcardValue((string) $giftcardNumber, 0.00); + + $this->assertFalse($result); + $this->assertEqualsWithDelta(100.00, $this->getGiftcardValue($giftcardNumber), 0.001); + } + public function testDecrementGiftcardValueExactBalanceSucceeds(): void { $giftcardNumber = $this->createGiftcard(60.00); diff --git a/tests/Models/SaleTest.php b/tests/Models/SaleTest.php index d6348c8b9..4dffad438 100644 --- a/tests/Models/SaleTest.php +++ b/tests/Models/SaleTest.php @@ -3,6 +3,7 @@ namespace Tests\Models; use App\Models\Sale; +use CodeIgniter\Database\Config; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; use Tests\Support\EmployeeFixtureTrait; @@ -21,16 +22,31 @@ class SaleTest extends CIUnitTestCase protected $migrate = true; protected $migrateOnce = true; - protected $refresh = true; + protected $seedOnce = true; + protected $refresh = false; protected $namespace = null; + private static bool $doneBootstrap = false; + private const LOCATION_ID = 1; protected function setUp(): void { + if (self::$doneBootstrap === false) { + Config::seeder($this->DBGroup)->call('App\Database\Seeds\TestDatabaseBootstrapSeeder'); + Config::connect($this->DBGroup)->close(); + + self::$doneBootstrap = true; + } + parent::setUp(); } + protected function tearDown(): void + { + parent::tearDown(); + } + protected function createGiftcard(float $value): int { $giftcardNumber = random_int(1000000, 9999999); @@ -157,6 +173,46 @@ class SaleTest extends CIUnitTestCase $this->assertEqualsWithDelta(40.00, $this->getGiftcardValue($giftcardNumber), 0.001); } + public function testSaveValueDoesNotMatchGiftcardSinkOnSubstringOnly(): void + { + $employeeId = $this->createEmployee(); + $giftcardNumber = $this->createGiftcard(100.00); + $itemId = $this->createTestItem(HAS_NO_STOCK); + + $saleModel = model(Sale::class); + $saleStatus = COMPLETED; + $items = $this->buildCartLine($itemId, 1, 60.00); + $payments = [ + 0 => [ + 'payment_type' => 'Foo ' . lang('Sales.giftcard') . ':' . $giftcardNumber, + 'payment_amount' => 60.00, + 'cash_refund' => 0, + 'cash_adjustment' => 0, + 'reference_code' => null, + ], + ]; + $salesTaxes = [[], []]; + + $result = $saleModel->save_value( + NEW_ENTRY, + $saleStatus, + $items, + NEW_ENTRY, + $employeeId, + 'test sale', + null, + null, + null, + SALE_TYPE_POS, + $payments, + null, + $salesTaxes + ); + + $this->assertGreaterThan(0, $result); + $this->assertEqualsWithDelta(100.00, $this->getGiftcardValue($giftcardNumber), 0.001); + } + public function testSaveValueReturnsInsufficientGiftcardBalanceSentinel(): void { $employeeId = $this->createEmployee();