Merge branch 'master' into feature-optimize-items-view-queries

This commit is contained in:
objecttothis authored and GitHub committed 2026-09-02 01:31:53 +04:00
commit fab6fcf2a8
55 files changed
+586 -154

No files matched your search

+20
View File
@@ -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);
}
}
+14 -9
View File
@@ -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 = [
+88 -59
View File
@@ -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);
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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.',
+2
View File
@@ -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.',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+12 -11
View File
@@ -1,10 +1,10 @@
<?php
return [
// "unsuspend_and_delete" => "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',
];
+12 -11
View File
@@ -1,10 +1,10 @@
<?php
return [
// "unsuspend_and_delete" => "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',
];
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => 'จำนวนไม่สามารถเป็นค่าติดลบได้',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+2
View File
@@ -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' => '',
+4
View File
@@ -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);
+63 -62
View File
@@ -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);
}
}
@@ -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();
+180 -1
View File
@@ -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();
+22
View File
@@ -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);
+57 -1
View File
@@ -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();