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
17 KiB
PHP

<?php
return [
'account_number' => 'حساب',
'add_payment' => 'إضافة دفع',
'amount_due' => 'المبلغ المطلوب',
'amount_due_not_covered' => 'المدفوعات لا تغطي المبلغ المستحق.',
'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_discount' => 'الخصم',
'customer_email' => 'البريد الإلكترونى',
'customer_location' => 'المكان',
'customer_mailchimp_status' => 'حالة بريد ميل تشيمب',
'customer_optional' => '(مطلوب للدفعات المستحقة)',
'customer_required' => '(اجباري)',
'customer_total' => 'المجموع',
'customer_total_spent' => '',
'customers_available_points' => 'النقاط المتاحة',
'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_exceeds_item_total' => '',
'discount_included' => '% خصم',
'discount_percent_exceeds_100' => '',
'discount_short' => '%',
'due' => 'مستحق',
'due_filter' => 'مستحق',
'edit' => 'تعديل',
'edit_item' => 'تعديل مادة',
'edit_sale' => 'تعديل عملية بيع',
'email_receipt' => 'إرسال الايصال بالبريد الالكترونى',
'employee' => 'الموظف',
'entry' => 'ادخال',
'error_editing_item' => 'خطاء فى تعديل المادة',
'find_or_scan_item' => 'بحث/مسح باركود المادة',
'find_or_scan_item_or_receipt' => 'بحث/مسح باركود المادة أو الايصال',
'giftcard' => 'بطاقة هدية',
'giftcard_balance' => 'رصيد بطاقة الهدية',
'giftcard_filter' => '',
'giftcard_number' => 'رقم بطاقة الهدية',
'group_by_category' => 'ترتيب حسب الفئة',
'group_by_type' => 'ترتيب حسب النوع',
'hsn' => 'رمز نظام منسق',
'id' => 'رقم عملية البيع',
'include_prices' => 'يشمل الاسعار؟',
'insufficient_giftcard_balance' => 'رصيد بطاقة الهدية غير كافي لهذه الدفعة.',
'insufficient_reward_points' => 'نقاط المكافأة غير كافية لهذه الدفعة.',
'invoice' => 'فاتورة',
'invoice_confirm' => 'هذه الفاتورة سوف ترسل إلى',
'invoice_enable' => 'رقم فاتورة',
'invoice_filter' => 'الفواتير',
'invoice_no_email' => 'هذا العميل ليس لدية بريد الكترونى صالح.',
'invoice_number' => 'فاتورة رقم #',
'invoice_number_duplicate' => 'من فضلك أدخل رقم فاتورة غير مكرر.',
'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' => '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' => 'رمز بطاقة الهدية يجب أن يكتون ارقام فقط.',
'must_enter_reference_code' => 'يجب إدخال رقم المرجع/الاسترداد.',
'negative_amount_tendered' => 'لا يمكن أن يكون المبلغ المدفوع سالبًا.',
'negative_discount_invalid' => '',
'negative_price_invalid' => '',
'negative_quantity_invalid' => '',
'negative_total_invalid' => '',
'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' => 'فشل إرسال الإيصال إلى',
'reference_code' => 'رمز مرجع الدفع',
'reference_code_invalid_characters' => 'يجب أن يحتوي رمز المرجع على أحرف وأرقام فقط.',
'reference_code_length_error' => 'طول رمز المرجع غير صالح.',
'refund' => 'نوع/سبب الاسترجاع',
'register' => 'سجل المبيعات',
'remove_customer' => 'حذف عميل',
'remove_discount' => '',
'return' => 'إسترجاع',
'rewards' => 'نقاط المكافئة',
'rewards_balance' => 'رصيد نقاط المكافئة',
'rewards_package' => 'فئة المكافئة',
'rewards_remaining_balance' => 'رصيد النقاط المتبقي هو ',
'sale' => 'بيع',
'sale_by_invoice' => 'البيع بفاتورة رسمية',
'sale_for_customer' => 'العميل:',
'sale_time' => 'الوقت',
'sales_tax' => 'ضريبة البيع',
'sales_total' => '',
'select_customer' => 'اختيار عميل (اختياري)',
'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' => 'فشل في ارسال طلب العمل الى',
];