From 905b58ca6ed2bfb82af3084c73dacf0f8119a79f Mon Sep 17 00:00:00 2001 From: Ollama Date: Fri, 10 Apr 2026 19:08:45 +0000 Subject: [PATCH 01/15] [Fix]: Add missing return statements to Sales Controller functions - Fix postComplete(): Add return keyword for error redirect paths (lines 799, 843, 871) when duplicate invoice/work_order/quote numbers - Fix postChangeItemNumber(): Add return statement returning JSON response - Fix postChangeItemName(): Add return statement returning JSON response - Fix postChangeItemDescription(): Add return statement returning JSON response All 4 functions declared return types but were missing return statements, causing potential runtime errors in certain code paths. Resolves #4492 --- app/Controllers/Sales.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/Controllers/Sales.php b/app/Controllers/Sales.php index d44d61fe1..f9868ee69 100644 --- a/app/Controllers/Sales.php +++ b/app/Controllers/Sales.php @@ -796,7 +796,7 @@ class Sales extends Secure_Controller if ($sale_id == NEW_ENTRY && $this->sale->check_invoice_number_exists($invoice_number)) { $data['error'] = lang('Sales.invoice_number_duplicate', [$invoice_number]); - $this->_reload($data); + return $this->_reload($data); } else { $data['invoice_number'] = $invoice_number; $data['sale_status'] = COMPLETED; @@ -817,6 +817,7 @@ class Sales extends Secure_Controller if ($data['sale_id_num'] == NEW_ENTRY) { $data['error_message'] = lang('Sales.transaction_failed'); + return $this->_reload($data); } else { $data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']); $this->sale_lib->clear_all(); @@ -840,7 +841,7 @@ class Sales extends Secure_Controller if ($sale_id == NEW_ENTRY && $this->sale->check_work_order_number_exists($work_order_number)) { $data['error'] = lang('Sales.work_order_number_duplicate'); - $this->_reload($data); + return $this->_reload($data); } else { $data['work_order_number'] = $work_order_number; $data['sale_status'] = SUSPENDED; @@ -868,7 +869,7 @@ class Sales extends Secure_Controller if ($sale_id == NEW_ENTRY && $this->sale->check_quote_number_exists($quote_number)) { $data['error'] = lang('Sales.quote_number_duplicate'); - $this->_reload($data); + return $this->_reload($data); } else { $data['quote_number'] = $quote_number; $data['sale_status'] = SUSPENDED; @@ -900,6 +901,7 @@ class Sales extends Secure_Controller if ($data['sale_id_num'] == NEW_ENTRY) { $data['error_message'] = lang('Sales.transaction_failed'); + return $this->_reload($data); } else { $data['barcode'] = $this->barcode_lib->generate_receipt_barcode($data['sale_id']); $this->sale_lib->clear_all(); @@ -1693,10 +1695,11 @@ class Sales extends Secure_Controller $this->item->update_item_number($item_id, $item_number); $cart = $this->sale_lib->get_cart(); $x = $this->search_cart_for_item_id($item_id, $cart); - if ($x != null) { + if ($x !== null) { $cart[$x]['item_number'] = $item_number; } $this->sale_lib->set_cart($cart); + return $this->response->setJSON(['success' => true]); } /** @@ -1715,11 +1718,12 @@ class Sales extends Secure_Controller $cart = $this->sale_lib->get_cart(); $x = $this->search_cart_for_item_id($item_id, $cart); - if ($x != null) { + if ($x !== null) { $cart[$x]['name'] = $name; } $this->sale_lib->set_cart($cart); + return $this->response->setJSON(['success' => true]); } /** @@ -1738,11 +1742,12 @@ class Sales extends Secure_Controller $cart = $this->sale_lib->get_cart(); $x = $this->search_cart_for_item_id($item_id, $cart); - if ($x != null) { + if ($x !== null) { $cart[$x]['description'] = $description; } $this->sale_lib->set_cart($cart); + return $this->response->setJSON(['success' => true]); } /** From 165c3351eb8bed4e32863d8da71a7f718dfbf9be Mon Sep 17 00:00:00 2001 From: objecttothis <17935339+objecttothis@users.noreply.github.com> Date: Wed, 15 Apr 2026 16:25:06 +0400 Subject: [PATCH 02/15] Encourage users to star the project Added a request to star the project for support. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 56c47dd17..ecd695b8f 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ NOTE: If you're running non-release code, please make sure you always run the la ## 🏃 Keep the Machine Running -If you like our project, please consider buying us a coffee through the button below so we can keep adding features. +If you like our project, please consider buying us a coffee through the button below so we can keep adding features. Please star the project if you like it! [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MUN6AEG7NY6H8)\ Or refer to the [FUNDING.yml](.github/FUNDING.yml) file. From b6f28da0583eaca4b075e7a7369054a50d66a421 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 14:14:29 +0400 Subject: [PATCH 03/15] Bump dompurify from 3.3.2 to 3.4.0 (#4512) Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.3.2 to 3.4.0. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/3.3.2...3.4.0) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.4.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 11 ++++------- package.json | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index e50d0ca52..c968b01e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "chartist-plugin-tooltips": "^0.0.17", "clipboard": "^2.0.11", "coffeescript": "^2.7.0", - "dompurify": "^3.3.2", + "dompurify": "^3.4.0", "elegant-circles": "github:opensourcepos/elegant-circles#minified", "es6-promise": "^4.2.8", "file-saver": "^2.0.5", @@ -1483,13 +1483,10 @@ "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==" }, "node_modules/dompurify": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", - "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.0.tgz", + "integrity": "sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==", "license": "(MPL-2.0 OR Apache-2.0)", - "engines": { - "node": ">=20" - }, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } diff --git a/package.json b/package.json index f4acd77e0..805d2a735 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "chartist-plugin-tooltips": "^0.0.17", "clipboard": "^2.0.11", "coffeescript": "^2.7.0", - "dompurify": "^3.3.2", + "dompurify": "^3.4.0", "elegant-circles": "github:opensourcepos/elegant-circles#minified", "es6-promise": "^4.2.8", "file-saver": "^2.0.5", From c714dd6f68f59157feff0b20264237768f3664ea Mon Sep 17 00:00:00 2001 From: jekkos Date: Thu, 16 Apr 2026 19:14:50 +0000 Subject: [PATCH 04/15] fix: propagate attribute definition failures in postSaveGeneral() (#4509) - Wrap attribute definition and appconfig save in single transaction - Capture return values from saveDefinition() and deleteDefinition() - Only call batch_save() if attribute operation succeeds - Combine success status with transStatus() for atomic result - Prevents category_dropdown config persistence when attribute fails Fixes #4461 Co-authored-by: Ollama --- app/Controllers/Config.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/Controllers/Config.php b/app/Controllers/Config.php index beb0ba19e..e018e8def 100644 --- a/app/Controllers/Config.php +++ b/app/Controllers/Config.php @@ -398,6 +398,9 @@ class Config extends Secure_Controller $this->module->set_show_office_group($this->request->getPost('show_office_group') != null); + $this->db->transStart(); + + $attributeSuccess = true; if ($batchSaveData['category_dropdown']) { $definitionData['definition_name'] = 'ospos_category'; $definitionData['definition_flags'] = 0; @@ -405,12 +408,16 @@ class Config extends Secure_Controller $definitionData['definition_id'] = CATEGORY_DEFINITION_ID; $definitionData['deleted'] = 0; - $this->attribute->saveDefinition($definitionData, CATEGORY_DEFINITION_ID); + $attributeSuccess = $this->attribute->saveDefinition($definitionData, CATEGORY_DEFINITION_ID); } elseif ($batchSaveData['category_dropdown'] == NO_DEFINITION_ID) { - $this->attribute->deleteDefinition(CATEGORY_DEFINITION_ID); + $attributeSuccess = $this->attribute->deleteDefinition(CATEGORY_DEFINITION_ID); } - $success = $this->appconfig->batch_save($batchSaveData); + $success = $attributeSuccess && $this->appconfig->batch_save($batchSaveData); + + $this->db->transComplete(); + + $success = $success && $this->db->transStatus(); return $this->response->setJSON(['success' => $success, 'message' => lang('Config.saved_' . ($success ? '' : 'un') . 'successfully')]); } From 97ca738b2da2f77f4eae9d4184038157b26d4038 Mon Sep 17 00:00:00 2001 From: jekkos Date: Thu, 16 Apr 2026 19:37:06 +0000 Subject: [PATCH 05/15] fix: Escape dynamic output and fix CSS property in barcode_sheet.php (#4501) - Add esc() for dynamic output in HTML attributes and URLs - Cast numeric values to int for CSS properties - Fix invalid 'borderspacing' CSS property to 'border-spacing' - Add quotes around class attribute Closes #4487 Co-authored-by: Ollama --- app/Views/barcodes/barcode_sheet.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Views/barcodes/barcode_sheet.php b/app/Views/barcodes/barcode_sheet.php index c6a13b831..3e27880f0 100644 --- a/app/Views/barcodes/barcode_sheet.php +++ b/app/Views/barcodes/barcode_sheet.php @@ -13,17 +13,17 @@ $barcode_lib = new Barcode_lib(); - <?= lang('Items.generate_barcodes') ?> - + <?= esc(lang('Items.generate_barcodes')) ?> + - get_font_name($barcode_config['barcode_font']) ?> style="font-size: px;"> - + +
Date: Fri, 17 Apr 2026 17:53:46 +0000 Subject: [PATCH 06/15] Fix CRC currency reverting to EUR/LAK in locale config (#4511) Root cause: In postCheckNumberLocale(), when number_locale differed from save_number_locale (which happens during form typing/validation), the code ignored user-provided currency values and always used locale defaults. For example: - User sets currency_code to "CRC" (Costa Rica Colon) - checkNumberLocale is called with save_number_locale from hidden field - If locale values don't match, original code overwrites with locale defaults - This caused CRC to revert to the default currency for that locale (EUR, LAK, etc.) Fix: Always respect user-provided currency_symbol and currency_code values when they are non-empty, regardless of whether locale changed or not. Fixes #4494 Co-authored-by: Ollama --- app/Controllers/Config.php | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/app/Controllers/Config.php b/app/Controllers/Config.php index e018e8def..38f2ca899 100644 --- a/app/Controllers/Config.php +++ b/app/Controllers/Config.php @@ -430,32 +430,35 @@ class Config extends Secure_Controller */ public function postCheckNumberLocale(): ResponseInterface { - $number_locale = $this->request->getPost('number_locale'); - $save_number_locale = $this->request->getPost('save_number_locale'); + $numberLocale = $this->request->getPost('number_locale'); + $saveNumberLocale = $this->request->getPost('save_number_locale'); + $postedCurrencySymbol = $this->request->getPost('currency_symbol'); + $postedCurrencyCode = $this->request->getPost('currency_code'); - $fmt = new NumberFormatter($number_locale, NumberFormatter::CURRENCY); - if ($number_locale != $save_number_locale) { - $currency_symbol = $fmt->getSymbol(NumberFormatter::CURRENCY_SYMBOL); - $currency_code = $fmt->getTextAttribute(NumberFormatter::CURRENCY_CODE); - $save_number_locale = $number_locale; - } else { - $currency_symbol = empty($this->request->getPost('currency_symbol')) ? $fmt->getSymbol(NumberFormatter::CURRENCY_SYMBOL) : $this->request->getPost('currency_symbol'); - $currency_code = empty($this->request->getPost('currency_code')) ? $fmt->getTextAttribute(NumberFormatter::CURRENCY_CODE) : $this->request->getPost('currency_code'); + $fmt = new NumberFormatter($numberLocale, NumberFormatter::CURRENCY); + + // Use posted values if provided, otherwise fall back to locale defaults + $currencySymbol = $postedCurrencySymbol !== '' ? $postedCurrencySymbol : $fmt->getSymbol(NumberFormatter::CURRENCY_SYMBOL); + $currencyCode = $postedCurrencyCode !== '' ? $postedCurrencyCode : $fmt->getTextAttribute(NumberFormatter::CURRENCY_CODE); + + // Update saved locale if it changed + if ($numberLocale !== $saveNumberLocale) { + $saveNumberLocale = $numberLocale; } if ($this->request->getPost('thousands_separator') == 'false') { $fmt->setTextAttribute(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, ''); } - $fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $currency_symbol); - $number_local_example = $fmt->format(1234567890.12300); + $fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $currencySymbol); + $numberLocaleExample = $fmt->format(1234567890.12300); return $this->response->setJSON([ - 'success' => $number_local_example != false, - 'save_number_locale' => $save_number_locale, - 'number_locale_example' => $number_local_example, - 'currency_symbol' => $currency_symbol, - 'currency_code' => $currency_code, + 'success' => $numberLocaleExample != false, + 'save_number_locale' => $saveNumberLocale, + 'number_locale_example' => $numberLocaleExample, + 'currency_symbol' => $currencySymbol, + 'currency_code' => $currencyCode, ]); } From 12e3c7e31fe971d58cb298ac4c87829cfc11bc23 Mon Sep 17 00:00:00 2001 From: jekkos Date: Fri, 17 Apr 2026 21:02:45 +0000 Subject: [PATCH 07/15] fix: Add missing $img_tag variable in Sales::getSendPdf() (#4515) * fix: Add missing $img_tag variable in Sales::getSendPdf() The receipt_email.php view expects $img_tag but getSendPdf() wasn't passing it. This caused 'Undefined variable $img_tag' error when sending receipt emails. Closes #4514 * refactor: Extract img_tag building into helper method Refactored duplicate img_tag building code into _build_img_tag helper method. Both getSendPdf and getSendReceipt now use this shared method. * refactor: Move logo-related methods to Email_lib Moved buildLogoImgTag and getLogoMimeType methods to Email_lib library where they logically belong alongside email-related functionality. This removes duplicate code and centralizes email-related helpers. Sales controller now uses email_lib->buildLogoImgTag() and email_lib->getLogoMimeType() instead of private methods. * fix: Address CodeRabbit review comments - buildLogoImgTag now uses getLogoMimeType for actual MIME type instead of hardcoding image/png - getLogoMimeType returns empty string instead of false for consistency - Consolidated logo path/exists check logic between both methods --------- Co-authored-by: Ollama --- app/Controllers/Sales.php | 13 +++++-------- app/Libraries/Email_lib.php | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/app/Controllers/Sales.php b/app/Controllers/Sales.php index f9868ee69..76b3a8bfe 100644 --- a/app/Controllers/Sales.php +++ b/app/Controllers/Sales.php @@ -937,7 +937,10 @@ class Sales extends Secure_Controller new Token_customer((array)$sale_data) ]; $text = $this->token_lib->render($text, $tokens); - $sale_data['mimetype'] = mime_content_type(FCPATH . 'uploads/' . $this->config['company_logo']); + $sale_data['mimetype'] = $this->email_lib->getLogoMimeType(); + + // Build img_tag for email views that need it (receipt_email.php) + $sale_data['img_tag'] = $this->email_lib->buildLogoImgTag(); // Generate email attachment: invoice in PDF format $view = Services::renderer(); @@ -974,13 +977,7 @@ class Sales extends Secure_Controller if (!empty($sale_data['customer_email'])) { $sale_data['barcode'] = $this->barcode_lib->generate_receipt_barcode($sale_data['sale_id']); - $sale_data['img_tag'] = ''; - - $logo_path = FCPATH . 'uploads/' . $this->config['company_logo']; - if (!empty($this->config['company_logo']) && file_exists($logo_path)) { - $logo_data = base64_encode(file_get_contents($logo_path)); - $sale_data['img_tag'] = 'company_logo'; - } + $sale_data['img_tag'] = $this->email_lib->buildLogoImgTag(); $to = $sale_data['customer_email']; $subject = lang('Sales.receipt'); diff --git a/app/Libraries/Email_lib.php b/app/Libraries/Email_lib.php index 914344567..eb8a9c24c 100644 --- a/app/Libraries/Email_lib.php +++ b/app/Libraries/Email_lib.php @@ -82,4 +82,40 @@ class Email_lib return $result; } + + /** + * Gets the mime type of the company logo file. + * + * @return string Mime type or empty string if logo doesn't exist + */ + public function getLogoMimeType(): string + { + $logo_path = FCPATH . 'uploads/' . $this->config['company_logo']; + + if (!empty($this->config['company_logo']) && file_exists($logo_path)) { + $mimeType = mime_content_type($logo_path); + return $mimeType !== false ? $mimeType : ''; + } + + return ''; + } + + /** + * Builds an img tag for the company logo to use in email templates. + * + * @return string HTML img tag with base64-encoded logo, or empty string if no logo + */ + public function buildLogoImgTag(): string + { + $mimeType = $this->getLogoMimeType(); + + if ($mimeType === '') { + return ''; + } + + $logo_path = FCPATH . 'uploads/' . $this->config['company_logo']; + $logo_data = base64_encode(file_get_contents($logo_path)); + + return 'company_logo'; + } } From 0a313aa09d4efa671ad1c0a2c4eb706563642bba Mon Sep 17 00:00:00 2001 From: jekkos Date: Sun, 19 Apr 2026 20:06:11 +0000 Subject: [PATCH 08/15] fix: Language dropdown not displaying saved language correctly (#4518) Root cause: In commit 7f9321eca, the refactoring incorrectly used object notation ($config->language_code) on an array instead of array notation ($config['language_code']). The settings property in OSPOS config is an array, so: - $config->language_code returns null (object access on array) - $config['language_code'] returns the actual value This caused both functions to always fall back to defaults, making the language dropdown show incorrect values. Fix: Change both functions to use array notation: - Line 25: $config['language_code'] (returns saved language code) - Line 46: $config['language'] (returns saved language name) Also fixed the wrong DEFAULT_LANGUAGE_CODE fallback on line 46 - should be DEFAULT_LANGUAGE since current_language() returns a name not a code. Fixes #4517 Co-authored-by: Ollama --- app/Helpers/locale_helper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Helpers/locale_helper.php b/app/Helpers/locale_helper.php index ed21c9fa9..fb6024bc2 100644 --- a/app/Helpers/locale_helper.php +++ b/app/Helpers/locale_helper.php @@ -22,7 +22,7 @@ function current_language_code(bool $load_system_language = false): string } } - return $config->language_code ?? DEFAULT_LANGUAGE_CODE; + return $config['language_code'] ?? DEFAULT_LANGUAGE_CODE; } /** @@ -43,7 +43,7 @@ function current_language(bool $load_system_language = false): string } } - return $config->language ?? DEFAULT_LANGUAGE_CODE; + return $config['language'] ?? DEFAULT_LANGUAGE; } /** From e602eddb47e334089ceeb2e4f441123108b126dc Mon Sep 17 00:00:00 2001 From: jekkos Date: Mon, 20 Apr 2026 06:22:42 +0000 Subject: [PATCH 09/15] fix: Scope orWhere clauses in Item::exists() and Item::get_item_id() (#4520) In PR #4250 (commit 29c3c55), orWhere was added to match items by either item_id or item_number, but the OR condition was not wrapped in groupStart()/groupEnd(). This causes: 1. Wrong SQL semantics: generates WHERE item_id = ? OR item_number = ? AND deleted = 0 instead of WHERE (item_id = ? OR item_number = ?) AND deleted = 0 Due to AND binding tighter than OR, the deleted filter only applies to the item_number branch, allowing deleted items to match via item_id. 2. Performance: the unscoped OR causes MySQL to bypass the item_id primary key index and fall back to full table scans when item_number is a string column compared against a numeric parameter. Both exists() and get_item_id() are fixed by wrapping the OR conditions in groupStart()/groupEnd() for proper parenthesization. Co-authored-by: Ollama --- app/Models/Item.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/Models/Item.php b/app/Models/Item.php index 3759810a0..7ca16379d 100644 --- a/app/Models/Item.php +++ b/app/Models/Item.php @@ -65,8 +65,10 @@ class Item extends Model public function exists(string $item_id, bool $ignore_deleted = false, bool $deleted = false): bool { $builder = $this->db->table('items'); + $builder->groupStart(); $builder->where('item_id', $item_id); $builder->orWhere('item_number', $item_id); + $builder->groupEnd(); if (!$ignore_deleted) { $builder->where('deleted', $deleted); @@ -390,8 +392,10 @@ class Item extends Model { $builder = $this->db->table('items'); $builder->join('suppliers', 'suppliers.person_id = items.supplier_id', 'left'); + $builder->groupStart(); $builder->where('item_number', $item_number); $builder->orWhere('item_id', $item_number); + $builder->groupEnd(); if (!$ignore_deleted) { $builder->where('items.deleted', $deleted); From ff7a8d2e88a27d2b544bbe6335fc9d45a22c80ce Mon Sep 17 00:00:00 2001 From: jekkos Date: Mon, 20 Apr 2026 06:48:57 +0000 Subject: [PATCH 10/15] fix: Update calendar translations (#4498) - Fix typo 'mayl' to 'may' in Calendar.php for lo, ka, ml, nb locales - Improve Spanish translation in Items.php for csv_import_invalid_location - Add trailing newlines to Calendar.php files (ka, ml, nb, lo) per PSR-12 Co-authored-by: Ollama --- app/Language/es-ES/Items.php | 2 +- app/Language/ka/Calendar.php | 4 ++-- app/Language/lo/Calendar.php | 4 ++-- app/Language/ml/Calendar.php | 4 ++-- app/Language/nb/Calendar.php | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/Language/es-ES/Items.php b/app/Language/es-ES/Items.php index 47d087105..783686ef8 100644 --- a/app/Language/es-ES/Items.php +++ b/app/Language/es-ES/Items.php @@ -26,7 +26,7 @@ return [ "cost_price_required" => "Precio al Por Mayor es un campo requerido.", "count" => "Actualizar Inventario", "csv_import_failed" => "Falló la importación de Hoja de Cálculo", - "csv_import_invalid_location" => "Ubicación(es) de stock inválida(s) encontrada(s): {0}. Solo ubicaciones de stock válidas son permitidas.", + "csv_import_invalid_location" => "Se encontraron ubicaciones de stock no válidas: {0}. Solo se permiten ubicaciones de stock válidas.", "csv_import_nodata_wrongformat" => "El archivo subido no tiene datos o el formato es incorrecto.", "csv_import_partially_failed" => "Hubo {0} falla(s) en la importación de producto(s) en la(s) línea(s): {1}. Ninguna fila ha sido importada.", "csv_import_success" => "Se importaron los articulos exitosamente.", diff --git a/app/Language/ka/Calendar.php b/app/Language/ka/Calendar.php index b3c9c5d15..3ca3c3825 100644 --- a/app/Language/ka/Calendar.php +++ b/app/Language/ka/Calendar.php @@ -38,7 +38,7 @@ return [ "february" => "", "march" => "", "april" => "", - "mayl" => "", + "may" => "", "june" => "", "july" => "", "august" => "", @@ -46,4 +46,4 @@ return [ "october" => "", "november" => "", "december" => "", -]; \ No newline at end of file +]; diff --git a/app/Language/lo/Calendar.php b/app/Language/lo/Calendar.php index b3c9c5d15..3ca3c3825 100644 --- a/app/Language/lo/Calendar.php +++ b/app/Language/lo/Calendar.php @@ -38,7 +38,7 @@ return [ "february" => "", "march" => "", "april" => "", - "mayl" => "", + "may" => "", "june" => "", "july" => "", "august" => "", @@ -46,4 +46,4 @@ return [ "october" => "", "november" => "", "december" => "", -]; \ No newline at end of file +]; diff --git a/app/Language/ml/Calendar.php b/app/Language/ml/Calendar.php index 1da89214d..34ac990a2 100644 --- a/app/Language/ml/Calendar.php +++ b/app/Language/ml/Calendar.php @@ -38,7 +38,7 @@ return [ "february" => "ഫെബ്രുവരി", "march" => "മാർച്ച്", "april" => "ഏപ്രിൽ", - "mayl" => "മേയ്", + "may" => "മേയ്", "june" => "ജൂൺ", "july" => "ജൂലൈ", "august" => "ആഗസ്റ്റ്", @@ -46,4 +46,4 @@ return [ "october" => "ഒക്ടോബർ", "november" => "നവംബർ", "december" => "ഡിസംബർ", -]; \ No newline at end of file +]; diff --git a/app/Language/nb/Calendar.php b/app/Language/nb/Calendar.php index bb7e706b2..5e9e48c7c 100644 --- a/app/Language/nb/Calendar.php +++ b/app/Language/nb/Calendar.php @@ -38,7 +38,7 @@ return [ "february" => "Februar", "march" => "Mars", "april" => "April", - "mayl" => "Mai", + "may" => "Mai", "june" => "Juni", "july" => "Juli", "august" => "August", @@ -46,4 +46,4 @@ return [ "october" => "Oktober", "november" => "November", "december" => "Desember", -]; \ No newline at end of file +]; From f1c6fe2981b4093673ebd447a687e82c6e2b45f8 Mon Sep 17 00:00:00 2001 From: jekkos Date: Wed, 22 Apr 2026 19:13:52 +0000 Subject: [PATCH 11/15] fix: Catch mysqli_sql_exception in DB fallback handlers for fresh Docker installs (#4525) * fix: Catch mysqli_sql_exception in DB fallback handlers for fresh Docker installs On a fresh Docker install with an empty database, the ospos_sessions table doesn't exist yet. The CSRF filter triggers session initialization before the login/migration page can be reached. The existing code in Session.php, OSPOS.php, and MY_Migration.php catches DatabaseException, but the MySQLi driver throws mysqli_sql_exception (which extends RuntimeException, not DatabaseException) when the table doesn't exist. This causes an unhandled exception resulting in HTTP 500. Fix: Change all three catch blocks from to so that mysqli_sql_exception and any other unexpected database errors are caught, allowing the app to fall back gracefully: - Session.php: Falls back to FileHandler so sessions work without DB - OSPOS.php: Falls back to empty settings so config loads work - MY_Migration.php: Falls back to version 0 / false so the migration check passes gracefully This allows the login page with migration UI to be served on first access, so the initial schema migration can run. Fixes #4524 --------- Co-authored-by: Ollama --- app/Config/OSPOS.php | 7 ++++--- app/Config/Session.php | 7 +++++-- app/Libraries/MY_Migration.php | 10 ++++++---- app/Models/Item.php | 1 - 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/Config/OSPOS.php b/app/Config/OSPOS.php index d8b5b99d4..1f411394d 100644 --- a/app/Config/OSPOS.php +++ b/app/Config/OSPOS.php @@ -5,7 +5,6 @@ namespace Config; use App\Models\Appconfig; use CodeIgniter\Cache\CacheInterface; use CodeIgniter\Config\BaseConfig; -use CodeIgniter\Database\Exceptions\DatabaseException; /** * This class holds the configuration options stored from the database so that on launch those settings can be cached @@ -41,9 +40,11 @@ class OSPOS extends BaseConfig $this->settings[$app_config->key] = $app_config->value; } $this->cache->save('settings', encode_array($this->settings)); - } catch (DatabaseException $e) { + } catch (\Exception $e) { // Database table doesn't exist yet (migrations haven't run) - // Return empty settings to allow migration page to display + // or database connection failed. Return empty settings to + // allow migration page to display. Catches mysqli_sql_exception + // which is not a subclass of DatabaseException. $this->settings = [ 'language' => 'english', 'language_code' => 'en', diff --git a/app/Config/Session.php b/app/Config/Session.php index 7c83fc94f..3d78928f4 100644 --- a/app/Config/Session.php +++ b/app/Config/Session.php @@ -3,7 +3,6 @@ namespace Config; use CodeIgniter\Config\BaseConfig; -use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Session\Handlers\BaseHandler; use CodeIgniter\Session\Handlers\DatabaseHandler; use CodeIgniter\Session\Handlers\FileHandler; @@ -139,7 +138,11 @@ class Session extends BaseConfig $this->driver = FileHandler::class; $this->savePath = WRITEPATH . 'session'; } - } catch (DatabaseException $e) { + } catch (\Exception $e) { + // Database not available yet (e.g. fresh install before migrations). + // Fall back to file-based sessions so the login/migration page + // can still be served. Catches mysqli_sql_exception which is + // not a subclass of DatabaseException but is a RuntimeException. $this->driver = FileHandler::class; $this->savePath = WRITEPATH . 'session'; } diff --git a/app/Libraries/MY_Migration.php b/app/Libraries/MY_Migration.php index b0187eb0a..c27954145 100644 --- a/app/Libraries/MY_Migration.php +++ b/app/Libraries/MY_Migration.php @@ -2,7 +2,6 @@ namespace App\Libraries; -use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\MigrationRunner; use Config\Database; use stdClass; @@ -44,7 +43,9 @@ class MY_Migration extends MigrationRunner $result = $builder->get()->getRow(); return $result ? $result->version : 0; } - } catch (DatabaseException $e) { + } catch (\Exception $e) { + // Database not available yet (e.g. fresh install before schema). + // Catches mysqli_sql_exception which is not a DatabaseException. return 0; } @@ -76,8 +77,9 @@ class MY_Migration extends MigrationRunner $result = $builder->get()->getRow(); return $result ? $result->version : false; } - } catch (DatabaseException $e) { - // Database doesn't exist yet or connection failed + } catch (\Exception $e) { + // Database not available yet (e.g. fresh install before schema). + // Catches mysqli_sql_exception which is not a DatabaseException. } return false; diff --git a/app/Models/Item.php b/app/Models/Item.php index 7ca16379d..efb8531d0 100644 --- a/app/Models/Item.php +++ b/app/Models/Item.php @@ -391,7 +391,6 @@ class Item extends Model public function get_item_id(string $item_number, bool $ignore_deleted = false, bool $deleted = false): bool|int { $builder = $this->db->table('items'); - $builder->join('suppliers', 'suppliers.person_id = items.supplier_id', 'left'); $builder->groupStart(); $builder->where('item_number', $item_number); $builder->orWhere('item_id', $item_number); From 058e12244e04d68380e5944e41073880085d43e6 Mon Sep 17 00:00:00 2001 From: enricodelarosa <79148590+enricodelarosa@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:56:56 +0800 Subject: [PATCH 12/15] fix(home): improve internal data type handling for user identification in auth process --- app/Controllers/Home.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/Controllers/Home.php b/app/Controllers/Home.php index d98d8e3d5..9ae68e2df 100644 --- a/app/Controllers/Home.php +++ b/app/Controllers/Home.php @@ -43,7 +43,7 @@ class Home extends Secure_Controller public function getChangePassword(int $employeeId = NEW_ENTRY): ResponseInterface|string { $loggedInEmployee = $this->employee->get_logged_in_employee_info(); - $currentPersonId = $loggedInEmployee->person_id; + $currentPersonId = (int) $loggedInEmployee->person_id; $employeeId = $employeeId === NEW_ENTRY ? $currentPersonId : $employeeId; @@ -68,10 +68,11 @@ class Home extends Secure_Controller public function postSave(int $employeeId = NEW_ENTRY): ResponseInterface { $currentUser = $this->employee->get_logged_in_employee_info(); + $currentPersonId = (int) $currentUser->person_id; - $employeeId = $employeeId === NEW_ENTRY ? $currentUser->person_id : $employeeId; + $employeeId = $employeeId === NEW_ENTRY ? $currentPersonId : $employeeId; - if (!$this->employee->isAdmin($currentUser->person_id) && $employeeId !== $currentUser->person_id) { + if (!$this->employee->isAdmin($currentPersonId) && $employeeId !== $currentPersonId) { return $this->response->setStatusCode(403)->setJSON([ 'success' => false, 'message' => lang('Employees.unauthorized_modify') From a381c3ca547e39cedff98da954c1d729befb358f Mon Sep 17 00:00:00 2001 From: khao_lek Date: Mon, 13 Apr 2026 05:06:22 +0000 Subject: [PATCH 13/15] Translated using Weblate (Thai) Currently translated at 99.5% (227 of 228 strings) Translation: opensourcepos/sales Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/sales/th/ --- app/Language/th/Sales.php | 456 +++++++++++++++++++------------------- 1 file changed, 228 insertions(+), 228 deletions(-) diff --git a/app/Language/th/Sales.php b/app/Language/th/Sales.php index 764f949d8..7a41fe767 100644 --- a/app/Language/th/Sales.php +++ b/app/Language/th/Sales.php @@ -1,232 +1,232 @@ "คะแนนที่มี", - "rewards_package" => "คะแนนสะสม", - "rewards_remaining_balance" => "คะแนนสะสมคงเหลือ ", - "account_number" => "บัญชี #", - "add_payment" => "เพิ่มบิล", - "amount_due" => "ยอดค้างชำระ", - "amount_tendered" => "ชำระเข้ามา", - "authorized_signature" => "ลายเซ็นผู้มีอำนาจ", - "cancel_sale" => "ยกเลิกการขาย", - "cash" => "เงินสด", - "cash_1" => "", - "cash_2" => "", - "cash_3" => "", - "cash_4" => "", - "cash_adjustment" => "การปรับเงินสดขาย", - "cash_deposit" => "ฝากเงินสด", - "cash_filter" => "เงินสด", - "change_due" => "เงินทอน", - "change_price" => "เปลี่ยนราคาขาย", - "check" => "โอนเงิน/พร้อมเพย์/เช็ค", - "check_balance" => "เช็คยอดคงเหลือ", - "check_filter" => "ตรวจสอบ", - "close" => "", - "comment" => "หมายเหตุ", - "comments" => "หมายเหตุ", - "company_name" => "", - "complete" => "", - "complete_sale" => "จบการขาย", - "confirm_cancel_sale" => "แน่ใจหรือไม่ที่จะล้างการขายนี้? ทุกรายการจะถูกลบทั้งหมด", - "confirm_delete" => "โปรดยืนยันการลบรายการขายที่เลือกไว้ ?", - "confirm_restore" => "คุณแน่ใจหรือไม่ว่าต้องการยกเลิกการขายที่เลือกไว้?", - "credit" => "เครดิตการ์ด", - "credit_deposit" => "เงินฝากเครดิต", - "credit_filter" => "บัตรเครติด", - "current_table" => "", - "customer" => "ลูกค้า", - "customer_address" => "Customer Address", - "customer_discount" => "ส่วนลด", - "customer_email" => "Customer Email", - "customer_location" => "Customer Location", - "customer_mailchimp_status" => "สถานะของระบบส่งเมล์เมล์ชิม", - "customer_optional" => "(ต้องระบุวันที่ชำระเงิน)", - "customer_required" => "(ต้องระบุ)", - "customer_total" => "Total", - "customer_total_spent" => "", - "daily_sales" => "", - "date" => "วันที่ขาย", - "date_range" => "ระหว่างวันที่", - "date_required" => "กรุณากรอกวันที่ให้ถูกต้อง", - "date_type" => "กรุณากรอกข้อมูลในช่องวันที่", - "debit" => "บัตรประชารัฐ/เดบิตการ์ด", - "debit_filter" => "", - "delete" => "อนุญาตให้ลบ", - "delete_confirmation" => "แน่ใจหรือไม่ที่จะลบรายการขายนี้, ลบแล้วไม่สามารถเรียกกลับคืนใด้", - "delete_entire_sale" => "ลบการขายทั้งหมด", - "delete_successful" => "คุณลบการขายสำเร็จ", - "delete_unsuccessful" => "คุณลบการขายไม่สำเร็จ", - "description_abbrv" => "รายละเอียด", - "discard" => "ยกเลิก", - "discard_quote" => "", - "discount" => "ส่วนลด %", - "discount_included" => "% ส่วนลด", - "discount_short" => "%", - "due" => "วันครบกำหนด", - "due_filter" => "วันที่ครบกำหนด", - "edit" => "แก้ไข", - "edit_item" => "แก้ไขสินค้า", - "edit_sale" => "แก้ไขการขาย", - "email_receipt" => "อีเมลบิล", - "employee" => "พนักงาน", - "entry" => "การนำเข้า", - "error_editing_item" => "แก้ไขสินค้าล้มเหลว", - "negative_price_invalid" => "", - "negative_quantity_invalid" => "", - "negative_discount_invalid" => "", - "discount_percent_exceeds_100" => "", - "discount_exceeds_item_total" => "", - "negative_total_invalid" => "", - "find_or_scan_item" => "ค้นหาสินค้า", - "find_or_scan_item_or_receipt" => "ค้นหา หรือ แสกนรายการ หรือ ใบเสร็จ", - "giftcard" => "บัตรของขวัญ", - "giftcard_balance" => "ยอดคงเหลือบัตรของขวัญ", - "giftcard_filter" => "", - "giftcard_number" => "เลขที่บัตรของขวัญ", - "group_by_category" => "กลุ่มตามหมวดหมู่", - "group_by_type" => "กลุ่มตามประเภท", - "hsn" => "HSN", - "id" => "เลขที่ขาย", - "include_prices" => "รวมในราคา?", - "invoice" => "ใบแจ้งหนี้", - "invoice_confirm" => "ใบแจ้งหนี้นี้จะถูกส่งไปที่", - "invoice_enable" => "เลขที่ใบแจ้งหนี้", - "invoice_filter" => "ใบแจ้งหนี้", - "invoice_no_email" => "ลูกค้ารายนี้ไม่มีที่อยู่อีเมล", - "invoice_number" => "เลขใบแจ้งหนี้ #", - "invoice_number_duplicate" => "ใบแจ้งหนี้หมายเลข {0} จะต้องไม่ซ้ำกัน", - "invoice_sent" => "ส่งใบแจ้งหนี้ไปที่", - "invoice_total" => "ยอดรวมในใบแจ้งหนี้", - "invoice_type_custom_invoice" => "ใบแจ้งหนี้ที่กำหนดเอง (custom_invoice.php)", - "invoice_type_custom_tax_invoice" => "ใบกำกับภาษีที่กำหนดเอง (custom_tax_invoice.php)", - "invoice_type_invoice" => "ใบแจ้งหนี้ (invoice.php)", - "invoice_type_tax_invoice" => "ใบกำกับภาษี (tax_invoice.php)", - "invoice_unsent" => "ไม่สามารถส่งใบแจ้งหนี้ถึง", - "invoice_update" => "คำนวณใหม่", - "item_insufficient_of_stock" => "จำนวนสินค้าไม่เพียงพอ", - "item_name" => "ชื่อสินค้า", - "item_number" => "สินค้า #", - "item_out_of_stock" => "สินค้าจำหน่ายหมด", - "key_browser" => "ความช่วยเหลือ", - "key_cancel" => "ยกเลิกใบเสนอราคา/ใบแจ้งหนี้ /ใบการขาย นี้", - "key_customer_search" => "ค้นหาลูกค้า", - "key_finish_quote" => "จบใบเสนอราคา/ใบแจ้งหนี้โดยไม่ต้องชำระเงิน", - "key_finish_sale" => "เพิ่มการชำระเงินและใบแจ้งหนี้ /ใบรายการขาย", - "key_full" => "เปิดแบบเต็มหน้าจอ", - "key_function" => "ฟังก์ชั่น", - "key_help" => "คำสั่งลัดงานขาย", - "key_help_modal" => "เปิดหน้าต่างคำสั่งลัดงานขาย", - "key_in" => "ขยายเข้า", - "key_item_search" => "ค้นหารายการขาย", - "key_out" => "ขยายออก", - "key_payment" => "เพิ่มการชำระเงิน", - "key_print" => "พิมพ์หน้านี้", - "key_restore" => "คืนการแสดงผลแบบดั้งเดิม/ขยาย", - "key_search" => "ค้นหาตารางรายงาน", - "key_suspend" => "พักรายการขายปัจจุบัน", - "key_suspended" => "แสดงรายการขายที่พักไว้", - "key_system" => "ทางลัดระบบ", - "key_tendered" => "แก้ไขจำนวนเงินรับมา", - "key_title" => "ทางลัดคียบอร์ดงานขาย", - "mc" => "", - "mode" => "รูปแบบการลงทะเบียน", - "must_enter_numeric" => "จำนวนที่ถุกประมูลต้องใส่ข้อมุลที่เปนตัวเลข", - "must_enter_numeric_giftcard" => "เลขที่บัตรของขวัญ ต้องใส่ตัวเลขเท่านั้น", - "new_customer" => "ลูกค้าใหม่", - "new_item" => "สินค้าใหม่", - "no_description" => "ไม่ระบุรายละเอียด", - "no_filter" => "ทั้งหมด", - "no_items_in_cart" => "ไม่พบสินค้าในตระกร้า", - "no_sales_to_display" => "ไม่มีการขายที่จะแสดง", - "none_selected" => "คุณยังไม่ได้เลือกการขายที่จะลบ", - "nontaxed_ind" => " . ", - "not_authorized" => "การกระทำนี้ไม่ได้รับอนุญาต", - "one_or_multiple" => "การขาย", - "payment" => "รูปแบบชำระเงิน", - "payment_amount" => "จำนวน", - "payment_not_cover_total" => "จำนวนเงินที่ชำระต้องมากกว่าหรือเท่ากับยอดรวม", - "payment_type" => "ชำระโดย", - "payments" => "", - "payments_total" => "ยอดชำระแล้ว", - "price" => "ราคา", - "print_after_sale" => "พิมพ์บิลหลังการขาย", - "quantity" => "จำนวน", - "quantity_less_than_reorder_level" => "คำเตือน ถ้าจำนวนของไม่เพียงพอกับความต้องการหรือไม่ตรงกับยอดในบันชี ก็สามารถทำการขายได้ แต่ต้องเชคปริมานสินค้าคงคลัง", - "quantity_less_than_zero" => "คำเตือน: ถ้าจำนวนของไม่เพียงพอกับความต้องการหรือไม่ตรงกับยอดในบัญชี ก็สามารถทำการขายได้ แต่ต้องตรวจสอบปริมาญสินค้าคงคลังก่อน", - "quantity_of_items" => "ปริมาณของ {0} รายการ", - "quote" => "ใบเสนอราคา", - "quote_number" => "หมายเลขอ้างอิง", - "quote_number_duplicate" => "หมายเลขอ้างอิงต้องไม่ซ้ำกัน", - "quote_sent" => "ส่งการอ้างอิงถึง", - "quote_unsent" => "ส่งการอ้างอิงถึงผิดพลาด", - "receipt" => "บิลขาย", - "receipt_no_email" => "ลูกค้านี้ไม่มีที่อยู่อีเมล์", - "receipt_number" => "จุดขาย#", - "receipt_sent" => "ส่งใบเสร็จไปที่", - "receipt_unsent" => "ไม่สามารถส่งใบเสร็จไปที่", - "refund" => "ประเภทการยกเลิกการขาย", - "register" => "ลงทะเบียนขาย", - "remove_customer" => "ลบลูกค้า", - "remove_discount" => "", - "return" => "คืน", - "rewards" => "คะแนนสะสม", - "rewards_balance" => "คะแนนสะสมคงเหลือ", - "sale" => "ขาย", - "sale_by_invoice" => "การขายโดยใบแจ้งหนี้", - "sale_for_customer" => "ลูกค้า:", - "sale_time" => "เวลา", - "sales_tax" => "ภาษีการขาย", - "sales_total" => "", - "select_customer" => "เลือกลูกค้า (Optional)", - "send_invoice" => "ส่งใบแจ้งหนี้", - "send_quote" => "ส่งใบเสนอราคา", - "send_receipt" => "ส่งใบเสร็จ", - "send_work_order" => "ส่งคำสั่งงาน", - "serial" => "หมายเลขซีเรียล", - "service_charge" => "", - "show_due" => "", - "show_invoice" => "ใบแจ้งหนี้", - "show_receipt" => "ใบเสร็จ", - "start_typing_customer_name" => "เริ่มต้นพิมพ์ชื่อลูกค้า...", - "start_typing_item_name" => "เริ่มต้นพิมพ์ชื่อสินค้า หรือ สแกนบาร์โค๊ด...", - "stock" => "คลังสินค้า", - "stock_location" => "ที่เก็บ", - "sub_total" => "ยอดรวมย่อย", - "successfully_deleted" => "ลบการขายสมยูรณ์", - "successfully_restored" => "คุณกู้คืนสำเร็จแล้ว", - "successfully_suspended_sale" => "การขายของคุณถูกระงับเรียบร้อย", - "successfully_updated" => "อัพเดทการขายสมบูรณ์", - "suspend_sale" => "พักรายการ", - "suspended_doc_id" => "รหัสเอกสาร", - "suspended_sale_id" => "รหัสการขายที่ถูกพัก", - "suspended_sales" => "การขายที่พักไว้", - "table" => "โต๊ะ", - "takings" => "การขายประจำวัน", - "tax" => "ภาษี", - "tax_id" => "รหัสภาษี", - "tax_invoice" => "ใบกำกับภาษี", - "tax_percent" => "ภาษี %", - "taxed_ind" => "ภ", - "total" => "ยอดรวม", - "total_tax_exclusive" => "ยอดไม่รวมภาษี", - "transaction_failed" => "การดำเนินการขายล้มเหลว", - "unable_to_add_item" => "เพิ่มรายการไปยังการขายล้มเหลว", - "unsuccessfully_deleted" => "ลบการขายไม่สำเร็จ", - "unsuccessfully_restored" => "การคืนค่ารายการขายล้มเหลว", - "unsuccessfully_suspended_sale" => "การขายของคุณถูกระงับเรียบร้อย", - "unsuccessfully_updated" => "อัพเดทการขายไม่สมบูรณ์", - "unsuspend" => "ยกเลิกการระงับ", - "unsuspend_and_delete" => "ยกเลิกการระงับ และ ลบ", - "update" => "แก้ไข", - "upi" => "ยูพีไอ", - "visa" => "", - "wholesale" => "", - "work_order" => "คำสั่งงาน", - "work_order_number" => "หมายเลขคำสั่งงาน", - "work_order_number_duplicate" => "หมายเลขคำสั่งงานต้องไม่ซ้ำกัน", - "work_order_sent" => "คำสั่งงานส่งถึง", - "work_order_unsent" => "ส่งคำสั่งงานล้มเหลว", - "selected_customer" => "ลูกค้าที่เลือก", + 'customers_available_points' => "คะแนนที่มี", + 'rewards_package' => "คะแนนสะสม", + 'rewards_remaining_balance' => "คะแนนสะสมคงเหลือ ", + 'account_number' => "บัญชี #", + 'add_payment' => "เพิ่มบิล", + 'amount_due' => "ยอดค้างชำระ", + 'amount_tendered' => "ชำระเข้ามา", + 'authorized_signature' => "ลายเซ็นผู้มีอำนาจ", + 'cancel_sale' => "ยกเลิกการขาย", + 'cash' => "เงินสด", + 'cash_1' => "", + 'cash_2' => "", + 'cash_3' => "", + 'cash_4' => "", + 'cash_adjustment' => "การปรับเงินสดขาย", + 'cash_deposit' => "ฝากเงินสด", + 'cash_filter' => "เงินสด", + 'change_due' => "เงินทอน", + 'change_price' => "เปลี่ยนราคาขาย", + 'check' => "โอนเงิน/พร้อมเพย์/เช็ค", + 'check_balance' => "เช็คยอดคงเหลือ", + 'check_filter' => "ตรวจสอบ", + 'close' => "", + 'comment' => "หมายเหตุ", + 'comments' => "หมายเหตุ", + 'company_name' => "", + 'complete' => "", + 'complete_sale' => "จบการขาย", + 'confirm_cancel_sale' => "แน่ใจหรือไม่ที่จะล้างการขายนี้? ทุกรายการจะถูกลบทั้งหมด", + 'confirm_delete' => "โปรดยืนยันการลบรายการขายที่เลือกไว้ ?", + 'confirm_restore' => "คุณแน่ใจหรือไม่ว่าต้องการยกเลิกการขายที่เลือกไว้?", + 'credit' => "เครดิตการ์ด", + 'credit_deposit' => "เงินฝากเครดิต", + 'credit_filter' => "บัตรเครติด", + 'current_table' => "", + 'customer' => "ลูกค้า", + 'customer_address' => "Customer Address", + 'customer_discount' => "ส่วนลด", + 'customer_email' => "Customer Email", + 'customer_location' => "Customer Location", + 'customer_mailchimp_status' => "สถานะของระบบส่งเมล์เมล์ชิม", + 'customer_optional' => "(ต้องระบุวันที่ชำระเงิน)", + 'customer_required' => "(ต้องระบุ)", + 'customer_total' => "Total", + 'customer_total_spent' => "", + 'daily_sales' => "", + 'date' => "วันที่ขาย", + 'date_range' => "ระหว่างวันที่", + 'date_required' => "กรุณากรอกวันที่ให้ถูกต้อง", + 'date_type' => "กรุณากรอกข้อมูลในช่องวันที่", + 'debit' => "บัตรประชารัฐ/เดบิตการ์ด", + 'debit_filter' => "", + 'delete' => "อนุญาตให้ลบ", + 'delete_confirmation' => "แน่ใจหรือไม่ที่จะลบรายการขายนี้, ลบแล้วไม่สามารถเรียกกลับคืนใด้", + 'delete_entire_sale' => "ลบการขายทั้งหมด", + 'delete_successful' => "คุณลบการขายสำเร็จ", + 'delete_unsuccessful' => "คุณลบการขายไม่สำเร็จ", + 'description_abbrv' => "รายละเอียด", + 'discard' => "ยกเลิก", + 'discard_quote' => "", + 'discount' => "ส่วนลด %", + 'discount_included' => "% ส่วนลด", + 'discount_short' => "%", + 'due' => "วันครบกำหนด", + 'due_filter' => "วันที่ครบกำหนด", + 'edit' => "แก้ไข", + 'edit_item' => "แก้ไขสินค้า", + 'edit_sale' => "แก้ไขการขาย", + 'email_receipt' => "อีเมลบิล", + 'employee' => "พนักงาน", + 'entry' => "การนำเข้า", + 'error_editing_item' => "แก้ไขสินค้าล้มเหลว", + 'negative_price_invalid' => "ราคาไม่สามารถเป็นค่าติดลบได้", + 'negative_quantity_invalid' => "จำนวนไม่สามารถเป็นค่าติดลบได้", + 'negative_discount_invalid' => "ส่วนลดไม่สามารถเป็นค่าติดลบได้", + 'discount_percent_exceeds_100' => "ส่วนลดเปอร์เซ็นต์มีค่าได้ไม่เกิน 100%", + 'discount_exceeds_item_total' => "ส่วนลดต้องไม่เกินจำนวนรายการขายทั้งหมด", + 'negative_total_invalid' => "", + 'find_or_scan_item' => "ค้นหาสินค้า", + 'find_or_scan_item_or_receipt' => "ค้นหา หรือ แสกนรายการ หรือ ใบเสร็จ", + 'giftcard' => "บัตรของขวัญ", + 'giftcard_balance' => "ยอดคงเหลือบัตรของขวัญ", + 'giftcard_filter' => "", + 'giftcard_number' => "เลขที่บัตรของขวัญ", + 'group_by_category' => "กลุ่มตามหมวดหมู่", + 'group_by_type' => "กลุ่มตามประเภท", + 'hsn' => "HSN", + 'id' => "เลขที่ขาย", + 'include_prices' => "รวมในราคา?", + 'invoice' => "ใบแจ้งหนี้", + 'invoice_confirm' => "ใบแจ้งหนี้นี้จะถูกส่งไปที่", + 'invoice_enable' => "เลขที่ใบแจ้งหนี้", + 'invoice_filter' => "ใบแจ้งหนี้", + 'invoice_no_email' => "ลูกค้ารายนี้ไม่มีที่อยู่อีเมล", + 'invoice_number' => "เลขใบแจ้งหนี้ #", + 'invoice_number_duplicate' => "ใบแจ้งหนี้หมายเลข {0} จะต้องไม่ซ้ำกัน", + 'invoice_sent' => "ส่งใบแจ้งหนี้ไปที่", + 'invoice_total' => "ยอดรวมในใบแจ้งหนี้", + 'invoice_type_custom_invoice' => "ใบแจ้งหนี้ที่กำหนดเอง (custom_invoice.php)", + 'invoice_type_custom_tax_invoice' => "ใบกำกับภาษีที่กำหนดเอง (custom_tax_invoice.php)", + 'invoice_type_invoice' => "ใบแจ้งหนี้ (invoice.php)", + 'invoice_type_tax_invoice' => "ใบกำกับภาษี (tax_invoice.php)", + 'invoice_unsent' => "ไม่สามารถส่งใบแจ้งหนี้ถึง", + 'invoice_update' => "คำนวณใหม่", + 'item_insufficient_of_stock' => "จำนวนสินค้าไม่เพียงพอ", + 'item_name' => "ชื่อสินค้า", + 'item_number' => "สินค้า #", + 'item_out_of_stock' => "สินค้าจำหน่ายหมด", + 'key_browser' => "ความช่วยเหลือ", + 'key_cancel' => "ยกเลิกใบเสนอราคา/ใบแจ้งหนี้ /ใบการขาย นี้", + 'key_customer_search' => "ค้นหาลูกค้า", + 'key_finish_quote' => "จบใบเสนอราคา/ใบแจ้งหนี้โดยไม่ต้องชำระเงิน", + 'key_finish_sale' => "เพิ่มการชำระเงินและใบแจ้งหนี้ /ใบรายการขาย", + 'key_full' => "เปิดแบบเต็มหน้าจอ", + 'key_function' => "ฟังก์ชั่น", + 'key_help' => "คำสั่งลัดงานขาย", + 'key_help_modal' => "เปิดหน้าต่างคำสั่งลัดงานขาย", + 'key_in' => "ขยายเข้า", + 'key_item_search' => "ค้นหารายการขาย", + 'key_out' => "ขยายออก", + 'key_payment' => "เพิ่มการชำระเงิน", + 'key_print' => "พิมพ์หน้านี้", + 'key_restore' => "คืนการแสดงผลแบบดั้งเดิม/ขยาย", + 'key_search' => "ค้นหาตารางรายงาน", + 'key_suspend' => "พักรายการขายปัจจุบัน", + 'key_suspended' => "แสดงรายการขายที่พักไว้", + 'key_system' => "ทางลัดระบบ", + 'key_tendered' => "แก้ไขจำนวนเงินรับมา", + 'key_title' => "ทางลัดคียบอร์ดงานขาย", + 'mc' => "", + 'mode' => "รูปแบบการลงทะเบียน", + 'must_enter_numeric' => "จำนวนที่ถุกประมูลต้องใส่ข้อมุลที่เปนตัวเลข", + 'must_enter_numeric_giftcard' => "เลขที่บัตรของขวัญ ต้องใส่ตัวเลขเท่านั้น", + 'new_customer' => "ลูกค้าใหม่", + 'new_item' => "สินค้าใหม่", + 'no_description' => "ไม่ระบุรายละเอียด", + 'no_filter' => "ทั้งหมด", + 'no_items_in_cart' => "ไม่พบสินค้าในตระกร้า", + 'no_sales_to_display' => "ไม่มีการขายที่จะแสดง", + 'none_selected' => "คุณยังไม่ได้เลือกการขายที่จะลบ", + 'nontaxed_ind' => " . ", + 'not_authorized' => "การกระทำนี้ไม่ได้รับอนุญาต", + 'one_or_multiple' => "การขาย", + 'payment' => "รูปแบบชำระเงิน", + 'payment_amount' => "จำนวน", + 'payment_not_cover_total' => "จำนวนเงินที่ชำระต้องมากกว่าหรือเท่ากับยอดรวม", + 'payment_type' => "ชำระโดย", + 'payments' => "", + 'payments_total' => "ยอดชำระแล้ว", + 'price' => "ราคา", + 'print_after_sale' => "พิมพ์บิลหลังการขาย", + 'quantity' => "จำนวน", + 'quantity_less_than_reorder_level' => "คำเตือน ถ้าจำนวนของไม่เพียงพอกับความต้องการหรือไม่ตรงกับยอดในบันชี ก็สามารถทำการขายได้ แต่ต้องเชคปริมานสินค้าคงคลัง", + 'quantity_less_than_zero' => "คำเตือน: ถ้าจำนวนของไม่เพียงพอกับความต้องการหรือไม่ตรงกับยอดในบัญชี ก็สามารถทำการขายได้ แต่ต้องตรวจสอบปริมาญสินค้าคงคลังก่อน", + 'quantity_of_items' => "ปริมาณของ {0} รายการ", + 'quote' => "ใบเสนอราคา", + 'quote_number' => "หมายเลขอ้างอิง", + 'quote_number_duplicate' => "หมายเลขอ้างอิงต้องไม่ซ้ำกัน", + 'quote_sent' => "ส่งการอ้างอิงถึง", + 'quote_unsent' => "ส่งการอ้างอิงถึงผิดพลาด", + 'receipt' => "บิลขาย", + 'receipt_no_email' => "ลูกค้านี้ไม่มีที่อยู่อีเมล์", + 'receipt_number' => "จุดขาย#", + 'receipt_sent' => "ส่งใบเสร็จไปที่", + 'receipt_unsent' => "ไม่สามารถส่งใบเสร็จไปที่", + 'refund' => "ประเภทการยกเลิกการขาย", + 'register' => "ลงทะเบียนขาย", + 'remove_customer' => "ลบลูกค้า", + 'remove_discount' => "", + 'return' => "คืน", + 'rewards' => "คะแนนสะสม", + 'rewards_balance' => "คะแนนสะสมคงเหลือ", + 'sale' => "ขาย", + 'sale_by_invoice' => "การขายโดยใบแจ้งหนี้", + 'sale_for_customer' => "ลูกค้า:", + 'sale_time' => "เวลา", + 'sales_tax' => "ภาษีการขาย", + 'sales_total' => "", + 'select_customer' => "เลือกลูกค้า (Optional)", + 'send_invoice' => "ส่งใบแจ้งหนี้", + 'send_quote' => "ส่งใบเสนอราคา", + 'send_receipt' => "ส่งใบเสร็จ", + 'send_work_order' => "ส่งคำสั่งงาน", + 'serial' => "หมายเลขซีเรียล", + 'service_charge' => "", + 'show_due' => "", + 'show_invoice' => "ใบแจ้งหนี้", + 'show_receipt' => "ใบเสร็จ", + 'start_typing_customer_name' => "เริ่มต้นพิมพ์ชื่อลูกค้า...", + 'start_typing_item_name' => "เริ่มต้นพิมพ์ชื่อสินค้า หรือ สแกนบาร์โค๊ด...", + 'stock' => "คลังสินค้า", + 'stock_location' => "ที่เก็บ", + 'sub_total' => "ยอดรวมย่อย", + 'successfully_deleted' => "ลบการขายสมยูรณ์", + 'successfully_restored' => "คุณกู้คืนสำเร็จแล้ว", + 'successfully_suspended_sale' => "การขายของคุณถูกระงับเรียบร้อย", + 'successfully_updated' => "อัพเดทการขายสมบูรณ์", + 'suspend_sale' => "พักรายการ", + 'suspended_doc_id' => "รหัสเอกสาร", + 'suspended_sale_id' => "รหัสการขายที่ถูกพัก", + 'suspended_sales' => "การขายที่พักไว้", + 'table' => "โต๊ะ", + 'takings' => "การขายประจำวัน", + 'tax' => "ภาษี", + 'tax_id' => "รหัสภาษี", + 'tax_invoice' => "ใบกำกับภาษี", + 'tax_percent' => "ภาษี %", + 'taxed_ind' => "ภ", + 'total' => "ยอดรวม", + 'total_tax_exclusive' => "ยอดไม่รวมภาษี", + 'transaction_failed' => "การดำเนินการขายล้มเหลว", + 'unable_to_add_item' => "เพิ่มรายการไปยังการขายล้มเหลว", + 'unsuccessfully_deleted' => "ลบการขายไม่สำเร็จ", + 'unsuccessfully_restored' => "การคืนค่ารายการขายล้มเหลว", + 'unsuccessfully_suspended_sale' => "การขายของคุณถูกระงับเรียบร้อย", + 'unsuccessfully_updated' => "อัพเดทการขายไม่สมบูรณ์", + 'unsuspend' => "ยกเลิกการระงับ", + 'unsuspend_and_delete' => "ยกเลิกการระงับ และ ลบ", + 'update' => "แก้ไข", + 'upi' => "ยูพีไอ", + 'visa' => "", + 'wholesale' => "", + 'work_order' => "คำสั่งงาน", + 'work_order_number' => "หมายเลขคำสั่งงาน", + 'work_order_number_duplicate' => "หมายเลขคำสั่งงานต้องไม่ซ้ำกัน", + 'work_order_sent' => "คำสั่งงานส่งถึง", + 'work_order_unsent' => "ส่งคำสั่งงานล้มเหลว", + 'selected_customer' => "ลูกค้าที่เลือก", ]; From 68e14191f9fa3ca3dd9d4eb025e3e5493916ea93 Mon Sep 17 00:00:00 2001 From: khao_lek Date: Mon, 13 Apr 2026 04:49:28 +0000 Subject: [PATCH 14/15] Translated using Weblate (Thai) Currently translated at 100.0% (8 of 8 strings) Translation: opensourcepos/bootstrap_tables Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/bootstrap_tables/th/ --- app/Language/th/Bootstrap_tables.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/Language/th/Bootstrap_tables.php b/app/Language/th/Bootstrap_tables.php index c384db69a..76c2f2574 100644 --- a/app/Language/th/Bootstrap_tables.php +++ b/app/Language/th/Bootstrap_tables.php @@ -1,12 +1,12 @@ "ทั้งหมด", - "columns" => "คอลัมน์", - "hide_show_pagination" => "ซ่อน/แสดง รายการหน้า", - "loading" => "กำลังดำเนินการ รอสักครู่", - "page_from_to" => "แสดง {0} ถึง {1} จาก {2} รายการ", - "refresh" => "Refresh ข้อมูล", - "rows_per_page" => "{0} รายการ/หน้า", - "toggle" => "ซ่อน/แสดง", + 'all' => "ทั้งหมด", + 'columns' => "คอลัมน์", + 'hide_show_pagination' => "ซ่อน/แสดง รายการหน้า", + 'loading' => "กำลังดำเนินการ รอสักครู่ ...", + 'page_from_to' => "แสดง {0} ถึง {1} จาก {2} รายการ", + 'refresh' => "Refresh ข้อมูล", + 'rows_per_page' => "{0} รายการ/หน้า", + 'toggle' => "ซ่อน/แสดง", ]; From 7edefe8ee12b65e686155cbbd62e8a368542b71e Mon Sep 17 00:00:00 2001 From: khao_lek Date: Mon, 13 Apr 2026 04:51:35 +0000 Subject: [PATCH 15/15] Translated using Weblate (Thai) Currently translated at 100.0% (15 of 15 strings) Translation: opensourcepos/login Translate-URL: https://translate.opensourcepos.org/projects/opensourcepos/login/th/ --- app/Language/th/Login.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Language/th/Login.php b/app/Language/th/Login.php index e9b7b7cd1..8368a4fb2 100644 --- a/app/Language/th/Login.php +++ b/app/Language/th/Login.php @@ -9,7 +9,9 @@ return [ "login" => "ลงชื่อเข้าใช้", "logout" => "ออกจากระบบ", "migration_needed" => "การย้ายฐานข้อมูลไปยัง {0} จะเริ่มต้นหลังจากเข้าสู่ระบบ", - "migration_required" => "", + "migration_required" => "จําเป็นต้องมีการปรับปรุงฐานข้อมูล", + "migration_auth_message" => "ผู้ดูแลระบบจำเป็นต้องมีสิทธิ์ในการปรับปรุงฐานข้อมูลเวอร์ชั่น {0} กรุณาเข้าระบบเพื่อดำเนินการต่อ", + "migration_complete_redirect" => "ทำการปรับปรุงฐานข้อมูลเรียบร้อย กำลังดำเนินการไปหน้าเข้าสู่ระบบ ...", "migration_auth_message" => "", "migration_initializing" => "", "migration_running" => "", @@ -17,7 +19,6 @@ return [ "migration_complete_login" => "", "migration_failed" => "", "migration_error_connection" => "", - "migration_complete_redirect" => "", "password" => "รหัสผ่าน", "required_username" => "จำเป็นต้องระบุชื่อผู้ใช้งาน", "username" => "ชื่อผู้ใช้",