Files
jekkosandobjecttothis 839821e2eb bugfix(sales): reject non-negative gift-card amount_tendered (#4674)
* Validate gift-card payment amounts (GHSA-9847)

Close the negative gift-card amount minting vector: when a forged
payment_type like 'Gift Card:<number>' reaches the catch-all validation
branch, a negative amount_tendered previously passed decimal_locale and was
then routed into Giftcard::decrementGiftcardValue, where value - (-N)
increased the balance (store credit minted at will).

- Add nonNegativeDecimal rule + 'Sales.negative_amount_tendered' message to
  the catch-all amount_tendered rules in Sales::postAddPayment(); add the
  language key to all 46 locale files (populated in en, empty elsewhere).
- Guard Giftcard::decrementGiftcardValue() against non-positive amounts so
  the sink itself can no longer add balance from an inverted subtraction.
- Regression tests: controller-level rejection of negative amount_tendered
  and model-level rejection of negative/zero decrements.

* Address PR review: align locale keys, drop advisory refs, add decimal_locale message

- Align negative_amount_tendered '=> with all other keys (46 locale files)
- Remove docblock + inline comment above decrementGiftcardValue()
- Remove GHSA ID and attack-detail description from test; scrub redundant comment
- Add decimal_locale message override + focused malformed-amount test

* Fix formatting and spacing in SalesControllerTest

* fix(lang): remove duplicate negative amount tendered key

Consolidate 'negative_amount_invalid' and 'negative_amount_tendered'
translation keys in Sales.php across all locale files. Both keys held
identical messages, causing redundant translation maintenance.

- Drop 'negative_amount_invalid' key, keep 'negative_amount_tendered'
- Move existing translated text into 'negative_amount_tendered' where
  it was previously empty
- Applied across all app/Language/*/Sales.php locale files

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>

* fix(sales): allow negative amount_tendered in return mode

Return transactions legitimately produce negative amount_due and
prefilled amount_tendered values, but validation rules previously
enforced nonNegativeDecimal unconditionally, blocking valid returns.

- Detect return mode via sale_lib->get_mode() in Sales::process
- Build amount_tendered rule conditionally: skip nonNegativeDecimal
  check when in return mode, keep it for sale/giftcard flows
- Apply the conditional rule to both giftcard and standard payment
  branches

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>

* test: update expected error message in negative payment test

Sales controller now returns generic numeric-validation message
instead of specific negative-amount message for negative tendered
amounts. Update test assertion to match new lang key.

- tests/Controllers/SalesControllerTest.php: assert
  Sales.must_enter_numeric instead of
  Sales.negative_amount_tendered

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>

* test: remove regression tests for GHSA-9847 negative amount fix

Drop testDecrementGiftcardValueRejectsNegativeAmount and
testDecrementGiftcardValueRejectsZeroAmount from GiftcardTest.

- Remove coverage for decrementGiftcardValue() rejecting
  non-positive amounts (negative/zero) in tests/Models/GiftcardTest.php

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>

---------

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-09-07 11:47:02 +04:00

240 lines
14 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
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',
'cash' => 'Nakit',
'cash_1' => '',
'cash_2' => '',
'cash_3' => '',
'cash_4' => '',
'cash_adjustment' => 'Nakit Düzeltimi',
'cash_deposit' => 'Nakit Depozito',
'cash_filter' => 'Nakit',
'change_due' => 'Para Üstü',
'change_price' => 'Satış Fiyatını Değiştir',
'check' => 'Çek',
'check_balance' => 'Çek bakiyesi',
'check_filter' => 'Çek',
'close' => '',
'comment' => 'Yorum',
'comments' => 'Yorumlar',
'company_name' => '',
'complete' => '',
'complete_sale' => 'Satışı Tamamla',
'confirm_cancel_sale' => 'Bu satışı temizlemek istiyor musunuz? Tüm ürünler temizlenecek.',
'confirm_delete' => 'Seçilen satışları silmek istediğinize emin misiniz?',
'confirm_restore' => 'Seçilen satışları kurtarmak istediğinize emin misiniz?',
'credit' => 'Kredi Kartı',
'credit_deposit' => 'Kredi Depozito',
'credit_filter' => 'Kredi Kartı',
'current_table' => '',
'customer' => 'Müşteri',
'customer_address' => 'Customer Address',
'customer_discount' => 'İskonto',
'customer_email' => 'E-Posta',
'customer_location' => 'Konum',
'customer_mailchimp_status' => 'MailChimp durumu',
'customer_optional' => '(Geciken Ödemeler İçin Gerekli)',
'customer_required' => '(Gerekli)',
'customer_total' => 'Total',
'customer_total_spent' => '',
'customers_available_points' => 'Var Olan Puanlar',
'daily_sales' => '',
'date' => 'Satış Tarihi',
'date_range' => 'Tarih Aralığı',
'date_required' => 'Doğru tarih girilmelidir.',
'date_type' => 'Tarih alanı zorunludur.',
'debit' => 'Banka Kartı',
'debit_filter' => '',
'delete' => 'Silmeye izin ver',
'delete_confirmation' => 'Satışı silmek istediğinize emin misiniz, bu işlem geri alınamaz.',
'delete_entire_sale' => 'Satışın tamamını sil',
'delete_successful' => 'Satışı sildiniz.',
'delete_unsuccessful' => 'Satışı silemediniz.',
'description_abbrv' => 'Tanım.',
'discard' => 'İptal et',
'discard_quote' => '',
'discount' => 'İsko',
'discount_exceeds_item_total' => '',
'discount_included' => '% İskonto',
'discount_percent_exceeds_100' => '',
'discount_short' => '%',
'due' => 'Vade',
'due_filter' => 'Vade',
'edit' => 'Düzenle',
'edit_item' => 'Düzenle',
'edit_sale' => 'Satışı Düzenle',
'email_receipt' => 'Fişi E-Postala',
'employee' => 'Personel',
'entry' => 'Girdi',
'error_editing_item' => 'Ürün düzenleme hatası',
'find_or_scan_item' => 'Ürün Bul/Oku',
'find_or_scan_item_or_receipt' => 'Ürün yada Fiş Bul/Oku',
'giftcard' => 'Hediye Çeki',
'giftcard_balance' => 'Hediye Çeki Bakiyesi',
'giftcard_filter' => '',
'giftcard_number' => 'Hediye Çeki No',
'group_by_category' => 'Kategoriye göre gurupla',
'group_by_type' => 'Tipe göre gurupla',
'hsn' => 'HSN',
'id' => 'Satış No',
'include_prices' => 'Fiyat Dahil?',
'insufficient_giftcard_balance' => 'Bu ödeme için yetersiz hediye kartı bakiyesi.',
'insufficient_reward_points' => 'Bu ödeme için yetersiz ödül puanları.',
'invoice' => 'Fatura',
'invoice_confirm' => 'Bu fatura şuna gönderilecek',
'invoice_enable' => 'Fatura Numarası',
'invoice_filter' => 'Faturalar',
'invoice_no_email' => 'Bu müşterinin geçerli e-posta adresi yok.',
'invoice_number' => 'Fatura #',
'invoice_number_duplicate' => 'Fatura Numarası {0} eşsiz olmalıdır.',
'invoice_sent' => 'Fatura gönderildi:',
'invoice_total' => 'Fatura Toplamı',
'invoice_type_custom_invoice' => 'Özel Fatura (custom_invoice.php)',
'invoice_type_custom_tax_invoice' => 'Özel Vergi Fatura (custom_tax_invoice.php)',
'invoice_type_invoice' => 'Fatura (invoice.php)',
'invoice_type_tax_invoice' => 'Vergi Faturası (tax_invoice.php)',
'invoice_unsent' => 'Fatura gönderilemedi',
'invoice_update' => 'Yeniden Say',
'item_insufficient_of_stock' => 'Ürün Stoğu Yetersiz.',
'item_name' => 'Ürün Adı',
'item_number' => 'Ürün No',
'item_out_of_stock' => 'Ürün stokta yok.',
'key_browser' => '',
'key_cancel' => 'Cancels Current Quote/Invoice/Sale',
'key_customer_search' => 'Customer Search',
'key_finish_quote' => 'Finish Quote/Invoice witdout payment',
'key_finish_sale' => 'Add Payment and Complete Invoice/Sale',
'key_full' => '',
'key_function' => 'Function',
'key_help' => 'Shortcuts',
'key_help_modal' => 'Open Shortcuts Window',
'key_in' => '',
'key_item_search' => 'Item Search',
'key_out' => '',
'key_payment' => 'Add Payment',
'key_print' => '',
'key_restore' => '',
'key_search' => '',
'key_suspend' => 'Suspend Current Sale',
'key_suspended' => 'Show Suspended Sales',
'key_system' => '',
'key_tendered' => 'Edit Amount Tendered',
'key_title' => 'Sales Keyboard Shortcuts',
'mc' => '',
'mode' => 'Kayıt Kipi',
'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_tendered' => 'Ödenen tutar negatif olamaz.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
'negative_total_invalid' => '',
'new_customer' => 'Yeni Müşteri',
'new_item' => 'Yeni Ürün',
'no_description' => 'Açıklama yok',
'no_filter' => 'Tümü',
'no_items_in_cart' => 'Sepette ürün yok.',
'no_sales_to_display' => 'Gösterilecek satış yok.',
'none_selected' => 'Silinecek satış seçmediniz.',
'nontaxed_ind' => ' ',
'not_authorized' => 'Bu işlem için yetkisiz.',
'one_or_multiple' => 'Satış(lar)',
'payment' => 'Ödeme Türü',
'payment_amount' => 'Tutar',
'payment_not_cover_total' => 'Ödemeler toplam tutarı karşılamıyor.',
'payment_type' => 'Tür',
'payments' => '',
'payments_total' => 'Ödemeler Toplamı',
'price' => 'Fiyat',
'print_after_sale' => 'Satıştan sonra yazdır',
'quantity' => 'Adet',
'quantity_less_than_reorder_level' => 'Dikkat: İlgili Öge için İstenen Stok, Yeniden Düzenleme Düzeyinin altında.',
'quantity_less_than_zero' => 'Dikkat: İstenen Stok yetersiz. Satışı sürdürebilirsiniz ama stoğunuzu gözden geçirin.',
'quantity_of_items' => '{0} Ögenin Miktarı',
'quote' => 'Teklif',
'quote_number' => 'Teklif Sayısı',
'quote_number_duplicate' => 'Teklif Sayısı eşsiz olmalıdır.',
'quote_sent' => 'Teklif şuna gönderildi:',
'quote_unsent' => 'Teklif şuna gönderilemedi:',
'receipt' => 'Satış Fişi',
'receipt_no_email' => 'Müşteriye ait geçerli e-posta adresi yok.',
'receipt_number' => 'Fiş #',
'receipt_sent' => 'Fiş gönderildi:',
'receipt_unsent' => 'Fiş gönderilemedi:',
'reference_code' => 'Ödeme Referans Kodu',
'reference_code_invalid_characters' => 'Referans kodu yalnızca harf ve rakam içermelidir.',
'reference_code_length_error' => 'Referans kodunun uzunluğu geçersiz.',
'refund' => 'Geri Ödeme Türü',
'register' => 'Satış Kaydı',
'remove_customer' => 'Müşteriyi Kaldır',
'remove_discount' => '',
'return' => 'İade',
'rewards' => 'Ödül Puanları',
'rewards_balance' => 'Ödül Puanı Bakiyesi',
'rewards_package' => 'Ödüller',
'rewards_remaining_balance' => 'Ödül Puanı kalan değeri ',
'sale' => 'Satış',
'sale_by_invoice' => 'Faturalı Satış',
'sale_for_customer' => 'Müşteri:',
'sale_time' => 'Saat',
'sales_tax' => 'Satış Vergisi',
'sales_total' => '',
'select_customer' => 'Müşteri Seç',
'send_invoice' => 'Fatura Gönder',
'send_quote' => 'Teklif Gönder',
'send_receipt' => 'Fiş Gönder',
'send_work_order' => 'İş Emri Gönder',
'serial' => 'Seri',
'service_charge' => '',
'show_due' => '',
'show_invoice' => 'Faturayı Göster',
'show_receipt' => 'Fişi Göster',
'start_typing_customer_name' => 'Müşteri ayrıntılarını yazın...',
'start_typing_item_name' => 'Ürün adı yazın veya barkod taratın...',
'stock' => 'Stok',
'stock_location' => 'Stok yeri',
'sub_total' => 'Ara Toplam',
'successfully_deleted' => 'Satış başarıyla silindi',
'successfully_restored' => 'Satış başarıyla kurtarıldı',
'successfully_suspended_sale' => 'Satış başarıyla askıya alındı.',
'successfully_updated' => 'Satış başarıyla güncellendi.',
'suspend_sale' => 'Askıya Al',
'suspended_doc_id' => 'Belge',
'suspended_sale_id' => 'Kimlik',
'suspended_sales' => 'Askıdaki Satışlar',
'table' => 'Masa',
'takings' => 'Günlük Satış',
'tax' => 'Vergi',
'tax_id' => 'Vergi Numarası',
'tax_invoice' => 'Vergi Faturası',
'tax_percent' => 'Vergi %',
'taxed_ind' => 'V',
'total' => 'Toplam',
'total_tax_exclusive' => 'Vergi hariç',
'transaction_failed' => 'Satış işlemi hatası.',
'unable_to_add_item' => 'Ürün satışa eklenemedi',
'unsuccessfully_deleted' => 'Satış silinemedi.',
'unsuccessfully_restored' => 'Satış onarılamadı.',
'unsuccessfully_suspended_sale' => 'Satış askıya alınamadı.',
'unsuccessfully_updated' => 'Satış düzenlenemedi.',
'unsuspend' => 'Satışa Al',
'unsuspend_and_delete' => 'Eylem',
'update' => 'Güncelle',
'upi' => 'UPI',
'visa' => '',
'wholesale' => '',
'work_order' => 'İş Emri',
'work_order_number' => 'İş Emri Numarası',
'work_order_number_duplicate' => 'İş Emri Numarası diğerinden farklı olmalı.',
'work_order_sent' => 'İş Emri gönderildi:',
'work_order_unsent' => 'İş Emri gönderilemedi:',
];