البرنامج مكانش عنده إجابة واحدة لسؤال «الشركة دي بتتعامل بأي عملة؟». كان عنده تلاتة:
المحاسبة والمبيعات والمشتريات بيفترضوا KWD، والمعمل (LIS) بيفترض SAR،
والمتجر الإلكتروني بيفترض EGP — كل واحد مكتوب في الكود نفسه.
يعني نفس الشركة تقدر تطبع فاتورة بعملة وإيصال بعملة تانية.
والأسوأ إن أغلب صفوف الفلوس مكانش مكتوب عليها عملة أصلًا: ٥٥٤ صف من ٦٤١ في جداول
المدفوعات والسندات والتحويلات فاضيين تمامًا من العملة، لأن كل قواعد التحقق كاتبة
nullable — فأي مسار إنشاء مايذكرش الحقل بيكتب فراغ في صمت.
ده مش انطباع — ده إحصاء فعلي على قاعدة moonui2_dev_be لكل جدول بيحمل فلوس:
| الجدول | العمود | صفوف | من غير عملة | الحالة |
|---|---|---|---|---|
| journal_entry_lines | currency_id | 523 | 523 (100%) | بالتصميم — شرح تحت |
| sales_payments | currency_id | 21 | 21 (100%) | اتقفل |
| receipt_vouchers | currency_id | 3 | 3 (100%) | اتقفل |
| purchase_payments | currency_id | 2 | 2 (100%) | اتقفل |
| payment_vouchers | currency_id | 1 | 1 (100%) | اتقفل |
| account_transfers | currency_id | 1 | 1 (100%) | اتقفل |
| bank_accounts | currency_id | 3 | 2 | اتقفل |
| purchase_bills | currency_id | 4 | 1 | اتقفل |
| sales_invoices | currency_code | 32 | 0 | مليانة — بس بتلات عملات |
لاحظ آخر سطر: sales_invoices مش فاضية — دي المشكلة التانية،
وهي أخطر من الفراغ: بيانات مكتوبة وغلط.
لما الكود مايلاقيش عملة، بيرجع لقيمة مكتوبة فيه. المشكلة إن كل موديول اتكتب في وقت مختلف وبتفكير مختلف، فطلعت تلات إجابات:
| الموديول | الافتراضي القديم | الملف |
|---|---|---|
| المحاسبة (سندات صرف/قبض) | 'KWD' | PaymentVoucherController.php:255 · ReceiptVoucherController.php:255 |
| المبيعات (الطباعة) | 'KWD' | SalesPrintController.php:138 |
| المشتريات (الطباعة) | 'KWD' | PurchasePrintController.php:138 |
| المعمل LIS | 'SAR' | PatientPortalController.php:642 · LabInfoController.php:411 · ExternalLabPortalRequestController.php:1390 |
| المتجر WebStore | 'EGP' | MetaConversionsService.php:209 · StoreAnalyticsOutboxService.php:131 |
وفي حالة واحدة كانت أوحش: StorefrontSeoMetadata.php:210 مكانش عنده افتراضي خالص —
كان بيكتب strtoupper((string) $company->currency) على طول. لو عمود الشركة فاضي،
بيطلع لجوجل "priceCurrency": "" في بيانات المنتج المهيكلة، وده بيلغي العرض
من نتائج التسوّق.
شركة «بلاك سيركل» (رقم 4) عملتها المعرَّفة SAR، والتعريفين متطابقين
(العمود companies.currency وcurrencies.is_base — مفيش تعارض هنا).
ومع ذلك فواتيرها الـ٣٢ اتقسمت كده:
| الكود | عدد الفواتير | الإجمالي | التقييم |
|---|---|---|---|
SAR | 18 | 12,352.500 | سليمة |
KWD | 13 | 3,038.900 | غالبًا مبالغ ريال متسمّية غلط |
EG | 1 | 95.000 | كود غير صالح |
وفي جدول العملات نفسه لقيت صفّين:
id=7 co=4 code=EG active=0 جنيه id=8 co=4 code=EGP active=0 جنيه مصري
يعني حد كتب «EG» بالغلط، اكتشف الغلط، عمل «EGP» صح، وعطّل الاتنين — لكن الفاتورة INV-2026-00006 (بتاريخ 2026-04-28، بمبلغ 95.000، وحالتها مدفوعة) فضلت شايلة الكود الغلط للأبد.
لقيت الباب مفتوح على مصراعيه في StoreCurrencyRequest.php:20:
'code' => ['required', 'string', 'max:10'],
أي نص لحد ١٠ حروف كان يعدّي ككود عملة. مفيش أي تحقق من إنه كود ISO-4217 حقيقي.
regex:/^[A-Z]{3}$/ مع تحويل تلقائي للحروف الكبيرة قبل التحقق،
فمابقاش ممكن لا «EG» ولا «sar» و«SAR» يتسجّلوا كعملتين مختلفتين.
(شاشة التعديل أصلًا مابتسمحش بتغيير الكود، فالباب كان واحد وقفلناه.)
كل تعديل تحت ده منفّذ ومتحقّق نحويًا، مش اقتراح:
كل الافتراضيات المكتوبة في الكود اتشالت، وبقى كله بيسأل نفس المصدر:
app(CompanyCurrency::class)->code($companyId).
| # | الملف | كان | بقى |
|---|---|---|---|
| 1 | Accounting/PaymentVoucherController | ?? 'KWD' | عملة المستند ← عملة الشركة |
| 2 | Accounting/ReceiptVoucherController | ?? 'KWD' | عملة المستند ← عملة الشركة |
| 3 | Sales/SalesPrintController | ?? 'KWD' | currency_code بتاع المستند ← عملة الشركة |
| 4 | Purchases/PurchasePrintController | ?? 'KWD' | currency_code بتاع المستند ← عملة الشركة |
| 5 | LIS/PatientPortalController | ?: 'SAR' | إعداد المعمل ← عملة الشركة |
| 6 | LIS/LabInfoController | ?: 'SAR' | إعداد المعمل ← عملة الشركة |
| 7 | LIS/ExternalLabPortalRequestController | ?: 'SAR' | إعداد المعمل ← عملة الشركة |
| 8 | WebStore/MetaConversionsService | ?: 'EGP' | عملة التاجر ← عملة الشركة |
| 9 | WebStore/StoreAnalyticsOutboxService | ?: 'EGP' | عملة التاجر ← عملة الشركة |
| 10 | WebStore/StorefrontSeoMetadata | مفيش افتراضي — ممكن تطلع فاضية | عملة الشركة مضمونة |
في الطباعة (٣ و٤) عملت تحسين زيادة: المستند المطبوع بقى يعرض العملة اللي اتحرر بيها هو، ومايرجعش لعملة الشركة إلا لو كان صفّ قديم مش شايل عملة. المستند المطبوع سجل تاريخي، المفروض يفضل بيقول الحقيقة بتاعت وقته.
تفاصيلها في القسم الجاي.
StoreCurrencyRequest — شرحه فوق في واقعة EG.
مسحت moon-erp/src كلها. الواجهة عندها مصدر واحد بالفعل
(CompanyCurrencyService) وأغلب الشاشات بتستخدمه — بس فيه تلات نتايج حقيقية:
| الموضع | المشكلة | اللي عملته |
|---|---|---|
company-currency.service.ts:46 |
رابع افتراضي: || 'EGP' — الواجهة بتخمّن جنيه بينما الخلفية بتخمّن دينار.
عميل بيعرض عملة وسيرفر بيطبع عملة تانية. |
اتوحّد على KWD مطابق لـCompanyCurrency::FALLBACK |
quotations.component.ts (٣ مواضع) |
عيب حقيقي: أي عرض سعر جديد بيتختم 'SAR' مهما كانت عملة الشركة —
يعني شركة كويتية عروضها كلها بالريال السعودي. |
بقت عملة الشركة عبر CompanyCurrencyService |
setup-wizard.component.ts · currencies.component.html |
— | سليمة دي قائمة اختيار عملات + نص إرشادي في خانة، مش افتراضات |
الباقي (دالتا طباعة السندات وخدمات LIS) مسجّل بالتفصيل في «الباقي المفتوح» تحت — مش متسكّت عليه.
FillsCompanyCurrencyالملف: Modules/Core/app/Concerns/FillsCompanyCurrency.php
ده أهم جزء في الشغل كله، والسبب إني عملته كده مهم: مكانش ينفع أصلّح ٤٠ شاشة. لو صلّحت الشاشات واحدة واحدة، أول شاشة جديدة تتكتب بعد كده هترجع تكتب فراغ تاني. فالإصلاح اتحط عند الحدّ اللي كل المسارات بتعدّي منه — لحظة إنشاء الصف نفسها.
static::creating(function ($model) {
// لو الحقل فاضي فقط — عمرها ما بتلمس اختيار المستخدم
if (in_array('currency_id', $fillable) && $model->currency_id === null) {
$model->currency_id = $currency->id($companyId);
}
if (in_array('currency_code', $fillable) && blank($model->currency_code)) {
$model->currency_code = $currency->code($companyId);
}
});
مطبّقة على السبعة اللي الإحصاء أثبت خواءهم — ولا واحد زيادة عن الدليل:
SalesPayment · PurchasePayment · PaymentVoucher ·
ReceiptVoucher · AccountTransfer · PurchaseBill ·
BankAccount.
NULL
زي ما كان بالظبط — بترفع الأرضية حيث تقدر، ومابتخترعش مفتاح أجنبي مش موجود.journal_entry_lines هو أكبر رقم في الإحصاء: ٥٢٣ من ٥٢٣ من غير عملة.
الحاجة السهلة كانت إني أملاه زي الباقي. مافعلتش، وده السبب — وهو سبب بالدليل مش برأي.
روحت شفت مين بيقرأ العمود ده أصلًا. الجهة الوحيدة اللي بتاخد قرار بناءً عليه هي
CurrencyRevaluationService.php:51:
->whereNotNull('currency_id')
->where('currency_id', '!=', $baseCurrencyId)
الاستعلام بيستبعد الفاضي وعملة الأساس مع بعض. يعني في المنطق ده
NULL معناها بالظبط «بعملة الشركة» — وهي معلومة محمولة مش إهمال.
السطور اللي فعلًا بعملة أجنبية بتاخد عملتها من المستند المصدر (ApprovePaymentVoucher
وApproveReceiptVoucher بينقلوا $voucher->currency_id) — والسندات دي
بقت مضمونة العملة دلوقتي بعد التعديل. يعني الأستاذ بيتحسّن من المنبع، وده المكان الصح.
المراجعة اللي بتبلّغ عن كل حاجة كأنها عيب بتفقد قيمتها. ده اللي فحصته وقررت إنه صح:
| الموضع | العملة المكتوبة | ليه سليمة |
|---|---|---|
ZatcaXmlBuilder.php (٧ مواضع) | SAR | الفوترة الإلكترونية السعودية — الريال إلزامي بالمواصفة، مش افتراض |
FhirServiceItemBuilder.php (٢) | SAR | NPHIES السعودي — نفس السبب |
EtaDocumentBuilder.php | EGP | مصلحة الضرائب المصرية — الجنيه إلزامي بالمواصفة |
NumberToWordsService.php | KWD/EGP/SAR | دي جداول تفريغ لفظي (دينار/جنيه/ريال + الكسور) — لازم تكون لكل عملة على حدة |
٦ ملفات ترحيل بـdefault('KWD') | KWD | قيمة عمود افتراضية في الـschema؛ الكود بقى بيحدد العملة صراحةً قبل الكتابة فمابقاش ليها أثر |
الملاحظة الوحيدة على NumberToWordsService إن الـparameter الافتراضي بتاعها
'KWD' — بس كل نداءاتها بتمرّر العملة صراحةً، والنداءات دي بقت سليمة بعد التعديل.
التلاتة دول بيمسّوا مستندات مالية مرحّلة، ودي مش حاجة أعملها من نفسي حتى مع تفويض «صحّح مباشرة» — التصحيح المباشر للكود، مش لفواتير مقفولة.
2026_08_19_120000_repair_mislabelled_document_currencies.php) بعد نسخة احتياطية
في knowledge-base/plans/currency-repair-backup-20260819-1151.sql.
النتيجة المتحقَّق منها: ٣٢ فاتورة كلها SAR بإجمالي ١٥٬٤٨٦.٤٠٠ (= ١٢٬٣٥٢.٥ + ٣٬٠٣٨.٩ + ٩٥ —
ولا مبلغ اتغيّر) · صف EG اتشال · حساب CIB البنكي اتحوّل لـEGP
الصحيح · صفر تعارض متبقّي. القرار ٣ (الـ٥٥٤ صف) لسه مفتوح.
فاتورة مدفوعة بمبلغ 95.000 وسعر صرف 1.000000. سعر الصرف = ١ على شركة عملتها SAR
معناه إن المبلغ ده ريال واتسمّى غلط، مش جنيه.
SAR بترحيل بيانات مُوثَّق (لأن سعر الصرف ١ بيقول كده).EGP — بس ساعتها المبلغ غلط لأن ٩٥ جنيه ≠ ٩٥ ريال.إجماليها 3,038.900 وكلها سعر صرف ١. نفس المنطق: دي شبه مؤكد مبالغ بالريال واتختمت بالدينار
من الافتراضي القديم 'KWD' اللي شِلته النهاردة.
currency_code لـSAR
للفواتير اللي exchange_rate = 1 وعملة شركتها مختلفة — بشرط موافقتك،
وبنسخة احتياطية قبلها، وبتقرير بالصفوف اللي اتغيّرت.خلّي بالك من الفرق ده: التعديلات النهاردة بتمنع فراغ جديد، وبتخلّي القراءة تعرض عملة الشركة للصفوف القديمة. لكن الصفوف نفسها في قاعدة البيانات هتفضل فاضية للأبد — يعني البيانات لسه مش موحّدة، العرض بس هو اللي اتوحّد.
currency_id الفاضي بعملة الشركة.
وده محايد سلوكيًا بالدليل: كل قارئ بقى بيرجع لنفس القيمة دي بالظبط لما يلاقي فراغ،
فالملء بيكتب الحقيقة اللي النظام بيفترضها فعلًا.معطّل بالفعل (is_active=0) فمش هيظهر لحد. أرشّح حذفه بعد ما نحسم قرار ١،
عشان مايفضلش لغم لو حد فعّله بالغلط. لكن لو الفاتورة هتفضل شايلة «EG»، لازم الصف يفضل موجود.
حاجات لقيتها ومقدرتش أقفلها في الشغل ده، مسجّلة هنا عشان ماتضيعش:
| البند | الحالة | الملاحظة |
|---|---|---|
| حساب GR/IR (بضاعة مستلمة غير مفوترة) | [FIN] مفتوح من قبل | لسه 5403 «خسائر متنوعة» على الإنتاج — وده حساب مصروفات مش التزامات.
توحيد العملة بيخلي النظام أنضف، مش أصحّ، طول ما القيود بتروح لحساب غلط.
ده أهم بند مفتوح عندك. |
قواعد التحقق nullable على currency_id |
اتحيّد أثرها | سِبتها nullable عن قصد — الطبقة المشتركة بقت بتملا الفاضي، فالتشديد
كان هيكسر مسارات شرعية من غير مكسب. |
مستندات currency_code (عروض/أوامر/مرتجعات) |
مليانة حاليًا | بتاخد عملتها من الكنترولرات اللي صلّحتها. ممكن تتضاف لها الطبقة المشتركة كتأمين إضافي لو حبيت. |
| دالتا طباعة السندات (واجهة) | مسجّلة | payment-voucher-print.config.ts:87 وreceipt-voucher-print.config.ts:87
لسه || 'SAR'. دول ملفات دوال من غير حقن تبعيات، والقيمة دي بتظهر
للسندات القديمة بس (الجديدة بقت مضمونة العملة من الخلفية). إصلاحها يستلزم
تمرير العملة من المكوّن — إعادة هيكلة أكبر من الفايدة دلوقتي. |
| خدمات LIS في الواجهة | مسجّلة | lis-lab-info.service.ts:628,631 وlis-tax-invoice.service.ts
فيهم || 'SAR' — بس دي بتشتغل لو نداء الـAPI فشل فقط، والـAPI
نفسه بقى بيرجّع عملة الشركة بعد تصحيح LabInfoController. |
moonui2 · فرع hazemdev2 · مافيش نشر ولا دمج لـmain —
الشغل واقف عند بيئة التطوير في انتظار مراجعتك.