العميل (مصنع تصنيع-للغير) عايز: كل عميل من عملائه له رصيد أمانة من نفس المنتج، متميّز في المخزن، يشوف جرده لكل عميل، يصرف منه للإنتاج، ويقدر يستلف منه — إمّا يشتريه من العميل أو يرجّعه. وكل ده لازم يبقى option بيتقفل ويتشغّل من غير ما يخلّ بنظام المخازن الحالي.
محرّك "Phase C" في الإنتاج: دفتر أمانة لكل عميل + استلام + استلاف + تسوية (شراء/إرجاع) + مخزن أمانة مفروض + شاشة + صلاحيات. مش هنبني من الصفر.
صرف التول بيستهلك مادة العميل فعليًا لكن recordIssue مالوش أي caller → رصيد أمانة العميل ما بينقصش → الأرصدة متضخّمة من وقت تشغيل Phase C.
توصيل الصرف · استثناء التقييم · حراسة المنافذ · سطح مخزني · بوابة on/off. صفر جراحة على StockBalance، وصفر خطر على المحلات اللي البوابة عندها مقفولة.
النظام بيشحن محرّك أمانة كامل تقريبًا (Phase C) — لكنه مربوط بالإنتاج، دايمًا شغّال، وسطحه في موديول الإنتاج مش المخازن.
| القدرة | الحالة | التفاصيل (code-verified) |
|---|---|---|
| جرد أمانة لكل عميل | موجود | ConsignmentMaterialLedger — رصيد لكل (عميل + منتج + مخزن): received/issued/borrowed/settled/balance + تكلفة معلنة. هو "system of record" منفصل عن الـGL. = الجرد لكل عميل اللي إنت عايزه. |
| استلام مادة العميل | موجود | CreateConsignmentReceipt: يحدّث الدفتر + يضيف الكمية الفعلية لـStockBalance بالتكلفة المعلنة (بدون قيد GL) + ينشئ Batches. |
| فصل الأمانة في المخزن | مفروض | الاستلام والاستلاف يشترطان مخزن is_consignment=true (validation). يعني بضاعة الأمانة لازم في مخزن أمانة مخصّص — منفصلة فيزيائيًا عن مخزون الشركة. |
| الاستلاف (Borrow) | موجود | RecordBorrow: يستهلك المادة في WIP وقت الاستلاف → DR WIP / CR مواد مستحقة للعميل (التزام) بالتكلفة المعلنة. idempotent. |
| التسوية: شراء أو إرجاع | موجود | SettleBorrow: شراء = فاتورة شراء (المورّد = العميل، بلا ضريبة) → DR مخزون / CR دائنون + قيد تصفية للالتزام → العميل يبقى دائن AP عادي · إرجاع/replenish = إرجاع نفس المادة، بلا فاتورة، تصفية الالتزام + حركة إرجاع فعلية. |
| الشاشة + الصلاحيات + الحسابات | موجود | شاشة consignment (استلام/استلاف/تسوية/دفتر لكل عميل) · صلاحيات production.consignment.{view,receive,settle} · إعداد حساب production.consignment_clearing_account_id + materials_due_customer. |
ConsignmentService::recordIssue موجود لكن مفيش أي كود بينادي عليه خارج الاختبارات. صرف التول (IssueMaterials / ApproveIssue) بيحرّك المادة فعليًا ويختم السطر ownership=customer، لكن issued_quantity في دفتر العميل ما بيتحرّكش → كل صرف تول من وقت Phase C ضخّم رصيد أمانة العميل (بيبان أكتر من الحقيقي). كمان recordIssue مالوش حارس رصيد (على عكس Borrow) → أمانة سالبة ممكنة.
ده مش جزء من الميزة الجديدة — ده تصحيح. لازم يتعمل أولًا (Phase 0) قبل أي بناء فوقه، وإلا كل جرد وبيان عميل هيتبني على أرقام غلط. الإصلاح: توصيل recordIssue في حلقة الصرف لكل سطر ownership=customer بمفتاح العميل $order->tollContract->business_partner_id + إضافة حارس الرصيد.
الصرف للإنتاج ما بينقّصش دفتر العميل (فوق). + منع الصرف أكتر من المتاح.
تقارير تقييم المخزون بتجمع StockBalance.total_value بدون استثناء مخازن الأمانة → دفتر المخزون الفرعي يزيد دائمًا عن حساب المخزون في الـGL بقيمة الأمانة (فرق وهمي في أي مطابقة).
GRN المشتريات / التحويلات / التسويات العامة مفيهاش حارس يمنعها من لمس مخزن أمانة → أي تحويل/استلام غلط بيخلط تكلفة الشركة بالأمانة ويخلّي الدفتر ينحرف عن الفعلي (خصوصًا التحويل بيحرّك مخزون بدون تحريك الدفتر).
النهاردة كل حاجة في موديول الإنتاج. العميل عايزها "في المخازن": استلام + جرد لكل عميل + صرف من موديول المخازن نفسه.
RecordBorrow بيفترض الاستهلاك في WIP (لأمر). الاستلاف لمخزون الشركة (مش مربوط بأمر) — اللي طلبته صراحةً — لسه مش موجود.
Phase C دايمًا شغّال — مفيش flag. عايزينه production.enable_consignment يتقفل/يتشغّل بدون أي أثر على المحلات اللي مش بتعمل تول.
الاختيار الحاسم: نفصل بضاعة كل عميل بالمخزن ولّا بعمود مالك على الرصيد؟
| الخيار | الحكم | السبب |
|---|---|---|
| مخزن أمانة لكل عميل (القائم + الموصى به) | ✅ v1 | StockBalance مفتاحه أصلًا (منتج+مخزن) → الفصل لكل عميل مجاني. جرد كل عميل = تقرير مخزن عادي، FEFO ما يقدرش يخلط بضاعة عميل بعميل، والدفتر (عميل+منتج+مخزن) يبقى قاطع. صفر كود — شغّال النهاردة. 5–20 عميل = 5–20 مخزن (تافه). معالج wizard ينشئ مخزن لكل عميل. |
عمود owner_partner_id على StockBalance | ✗ مرفوض v1 | بيغيّر الـunique key وكل lookup للرصيد في 15 موديول (getIssueCost/getProductCost/الحجز/السالب/الجرد/التحويل). فخ: MySQL بيعامل NULL كمتمايز في الـunique → صفوف مخزون-شركة مكرّرة. ومبيجيبش جديد — الحقيقة لكل عميل موجودة في الدفتر أصلًا. (ذكّرنا بباج حديث من نفس نوع الـlookup.) |
| الدفتر فقط (بلا StockBalance) | جزئي | لو شِلنا الكمية الفعلية من StockBalance → بنكسر FEFO/الصلاحية/الحجر/الجرد الفعلي — كله محتاجه الدواء. لأ. |
owner_partner_id يتعمل v3 فقط لو ظهر عميل حقيقي محتاج عدّة ملّاك في نفس المخزن الفيزيائي — وساعتها بميزانية تدقيق الـlookups. مفيش حاجة في v1 بتقفل الباب ده (الدفتر يقدر يعمل backfill).القاعدة الحاكمة: بضاعة الأمانة مش ملك الشركة — فيزيائيًا موجودة ومرئية، لكن قيمتها خارج ميزانية الشركة (نفس نمط SAP "customer special stock"). الدفتر (بالتكلفة المعلنة) هو مرجع الملكية.
| الحدث | المعالجة |
|---|---|
| استلام أمانة | دفتر: received++ · كمية فعلية لـStockBalance (مخزن الأمانة) بالتكلفة المعلنة · لا قيد GL. GL: لا قيد (off-book) |
| صرف مباشر أمر العميل من أمانته | دفتر: issued++ (مع حارس رصيد) · لا قيد GL (آلية WIP-exclusion بتتكفّل بالقيمة). ملكية السطر = customer. |
| استلاف (Borrow) أمر تاني/شركة يستهلك أمانة العميل | التزام على الشركة بالتكلفة المعلنة: DR WIP (أو DR مخزون لو الاستلاف لمخزون الشركة) CR مواد مستحقة للعميل |
| تسوية: شراء من العميل | فاتورة شراء (المورّد=العميل) + قيد تصفية: DR مخزون CR دائنون (AP) · DR مواد مستحقة CR مخزون → الالتزام يتصفّى، والعميل يبقى دائن AP عادي. ⚠️ ضريبة الفاتورة مغلقة على صفر ضريبة حاليًا = غير مطابق لعميل مسجّل VAT (يتصلح: نسمح ضريبة على فاتورة التسوية). |
| تسوية: إرجاع للعميل | إرجاع نفس المادة → بلا فاتورة → تصفية الالتزام + حركة إرجاع فعلية. ⚠️ فرق تكلفة بيصرف مخزون الشركة بالتكلفة المعلنة بينما تكلفته الفعلية مختلفة (خصوصًا FIFO → انحراف GL عن المخزون). الإصلاح: يصرف بالتكلفة الفعلية والفرق يروح حساب ربح/خسارة تسوية أمانة (P&L). |
مفتاح القرار: أمر مين بيستهلك مادة مين.
| المستهلِك | مالك المادة | المسار | القيد |
|---|---|---|---|
| أمر تول للعميل A | أمانة A | صرف مباشر → recordIssue (issued+=) | لا قيد (WIP-exclusion) |
| أمر الشركة، أو أمر العميل B | أمانة A | استلاف → RecordBorrow | DR WIP / CR مواد مستحقة لـA |
RecordBorrow بالظبط). لو الصرف لأمر الشركة لقى نقص ولقى أمانة من نفس المنتج → يعرض زر "استلف من الأمانة" يفتح تدفّق الاستلاف الصريح، مش يستلف بصمت.production.enable_consignment (افتراضي false) — زي sales.enable_delivery_notesproduction.consignment.* موجودة — الصلاحية = مين، البوابة = هلmaterial_ownership، WIP-exclusion، ختم ownership) — دي per-order opt-in أصلًا؛ قفلها هيكسر عملاء تول شغّالين.consignment_material_ledgers/consignment_borrows أو مخزن is_consignment → يتفعّل ON تلقائيًا؛ الباقي OFF. (الـflag إضافي بحت — لا تغيير في بيانات الجداول.)| # | الخطر | المعالجة |
|---|---|---|
| 1 | انحراف الدفتر — بيحصل الآن | recordIssue مش موصول → بيانات العملاء غلط لأي استهلاك تول حيّ. → Phase 0. |
| 2 | تسرّب التقييم | قيمة الأمانة في المخزون الفرعي بلا مقابل GL → استثناء مخازن الأمانة من التقييم/التكلفة. |
| 3 | منافذ غير محروسة | GRN/تحويل/تسوية على مخزن أمانة → انحراف فعلي-دفتر صامت. → حراسة المنافذ. |
| 4 | أمانة سالبة | لا حارس رصيد على recordIssue → إضافة حارس (نسخة من Borrow). |
| 5 | فرق GL/مخزون في الإرجاع (FIFO) | صرف بالتكلفة المعلنة بدل الفعلية → حساب ربح/خسارة تسوية. |
| 6 | خلط عبر-العملاء في مخزن مشترك | FEFO أعمى للملكية → نموذج مخزن-لكل-عميل يحلها تشغيليًا. |
| 7 | ترحيل البوابة | لازم تفعيل تلقائي للعملاء اللي عندهم بيانات أمانة وإلا الإطلاق يقفل ميزة حيّة. |
recordIssue في IssueMaterials لكل سطر ownership=customer + حارس الرصيد.is_consignment من تقارير التقييم/التكلفة.production.enable_consignment + seeder + ترحيل تفعيل تلقائي للعملاء الحاليين. حجب routes/قوائم/أكشنز.CreateConsignmentReceipt) · تقرير جرد لكل عميل (الدفتر + رصيد مخزن الأمانة، مع عمود فرق فعلي-دفتر = نظام إنذار مبكر).borrow_target: stock — استلاف على مستوى المخزون (مش مربوط بأمر): تحويل من مخزن الأمانة لمخزن الشركة + DR مخزون / CR مواد مستحقة.| القرار | الخيارات | التوصية |
|---|---|---|
| الفصل | مخزن لكل عميل · مخزن أمانة مشترك | مخزن لكل عميل (افتراضي؛ ندعم المشترك كمان) |
| البوابة الافتراضية | OFF للجميع + تفعيل تلقائي للحاليين | OFF + backfill ON للحاليين |
| ضريبة على شراء الأمانة | بضريبة · بلا ضريبة | بضريبة لعميل مسجّل VAT (يُصلَح في Phase 2) |
| الإرجاع | نفس الـlot · مادة مكافئة | مكافئة (قياسي في التول) مع التقاط Batch للتتبّع |
| استلاف لمخزون الشركة | مطلوب الآن · لاحقًا | Phase 2 (اللي طلبته صراحةً — مش موجود حاليًا) |