جارٍ التحقّق من الدخول...
B
Brandzo Hub
جاري الاتصال...

تقرير آلية الربط بأودو والمزامنة الحيّة

كفاءة التكامل عبر الـAPI · المزامنة الدوريّة واللحظيّة · سيناريوهات العوائق

BFP-SCM-PR-2026-ODOO-01

هذا التقرير مبنيٌّ على الكود مصدرًا رئيسيًّا — كل آليّة موصوفة هنا مؤكَّدة من ملفّ ومسار في المستودع (طبقة src/services/odoo، الوسيط odoo-proxy، الموديول brandzo_warehouse، إعداد فايربيز، وعقد التكامل). يميّز التقرير بأمانة بين ما هو منفَّذ وما هو مصمَّم بانتظار التنفيذ.

منفَّذ وقائم مصمَّم — بانتظار خطوة عائقٌ يشترط قرار المالك

الحُكم الصادق — أين نحن اليوم؟

القرار المعماريّ الحاكم: فايربيز (Firestore) مصدر الحقيقة التشغيليّ، وأودو مرآة محاسبيّة/رقابيّة. البوابة تعمل كاملةً حتى لو سقط أودو — صفر تبعيّة تشغيليّة. التكامل «Integration» لا «Migration»: نغذّي أودو من البوابة ونقرأ منه ما يلزم، ولا ننقل قلب العمليّات إليه. عمليًّا: ~70% من بنية التكامل مبنيّة ومعزولة (عميل + وسيط + موديول جاهزون)، والوضع الافتراضيّ training (محاكٍ)، ومنطق المزامنة نفسه مصمَّمٌ في عقد التكامل بانتظار التوصيل. هذه الشفافيّة هي جوهر الاحترافيّة: لا نَعِد بما ليس موصولًا.

  • ~70%
    جاهزية بنية التكامل
  • 5
    بدائيّات API قياسيّة
  • 6
    نماذج بقائمة سماح الوسيط
  • 19
    إصدار أودو الحاليّ
  • 12/12
    مرحلة الدورة المستندية
  • 52
    اختبارًا خضراء (الموديول)
منفَّذٌ اليوم في الكود
  • · عميل أودو بخمس بدائيّات (authenticate/searchRead/create/write/unlink).
  • · مُبدِّل الوضع (تدريب/إنتاج) بمتغيّر بيئة واحد بلا تغيير كود.
  • · مُطابِق الحقول (product.product ⇄ الأصناف · stock.move → السجلّ).
  • · محاكي أودو أوفلاين (نفس الواجهة، بلا شبكة) للتدريب.
  • · وسيطان مرجعيّان (Cloudflare Worker + Firebase Function) بقائمة سماح.
  • · الموديول brandzo_warehouse (إصدار 19، ‏12/12 مرحلة، ‏52 اختبارًا خضراء).
مصمَّمٌ بانتظار التنفيذ
  • · توصيل العميل بشاشة مزامنة + اختبار تكامل يحرسه من التعفّن.
  • · السحب الدوريّ (polling) للموردين/الأصناف/الأوامر/الأرصدة.
  • · طابور الدفع (outbox) لإرسال المستندات كمسوّدات لأودو.
  • · المرايا odoo_mirror_* ووثيقة sync_state وقواعد أمانها.
  • · اللحظيّ الحقيقيّ (Webhooks) — مشروطٌ باستضافة أودو عامّة.
  • · طبقة Repository لتبديل القاعدة (القسم ٦).

١ · آلية الربط عبر الـAPI وإرسال المستندات كمسوّدات

المتصفّح لا يحمل سرّ أودو أبدًا (سرّه سرقةٌ لو شُحن في الحزمة)، ونقطة أودو /jsonrpc لا تُصدر ترويسات CORS. لذا يمرّ كل استدعاء عبر وسيطٍ خادميّ رقيق يحقن الأسرار، ويصادق، وينادي execute_kw، ويفرض قائمة سماح، ويضيف ترويسات CORS لأصل البوابة.

البوابة (متصفّح)
لا سرّ إطلاقًا
odooConfig.js
الوسيط (Cloudflare Worker)
يحمل الأسرار + قائمة السماح + CORS
odoo-proxy/
أودو (Odoo 19)
execute_kw · مستخدم تكامل
bz_integration

عقد السلك: POST { model, method, args, kwargs } → { result } أو { error } — مرآةٌ شبه مباشرة لـexecute_kw(db, uid, pwd, model, method, args, kwargs). استدعاءٌ خاصّ { method: "authenticate" } يفحص الاتصال ويرجع uid.

البدائيّات الخمس (واجهة موحّدة تُنسخ عليها الصفحات)

الدالّة نظير أودو الغرض
authenticate() common.authenticate التحقّق من الاتصال وإرجاع معرّف المستخدم (uid) — يغذّي زرّ «اختبار الاتصال».
searchRead(model, domain, fields) search_read قراءة سجلّات مطابقة لنطاق — الأساس لجلب الأصناف/الموردين/الأوامر.
create(model, values) create إنشاء سجلّ جديد — به تُرسَل مستندات البوابة إلى أودو كمسوّدات.
write(model, ids, values) write تعديل سجلّات قائمة — لتحديث حقول مطابِقة عند اللزوم.
unlink(model, ids) unlink حذف سجلّات — محصور بقائمة السماح ونادر الاستعمال.
أسرار في الوسيط فقط

ODOO_URL/DB/USER/API_KEY تعيش حصرًا في خزانة أسرار الوسيط — لا في الحزمة ولا في git.

قائمة سماح صريحة

الوسيط يقبل أزواج (نموذج+دالّة) محدّدة فقط ويردّ 403 Blocked لأي استدعاء عشوائيّ.

CORS + مستخدم مقيَّد

CORS محصورٌ بأصل البوابة، ومستخدم التكامل bz_integration بدورٍ مقيَّد لا Admin.

إرسال مستندات البوابة إلى أودو كمسوّدات (Drafts)

عند بلوغ مستندٍ حاكمٍ حالة «منجَز» في البوابة، يُقيَّد في طابور الدفع sync_outbox، ثم يدفعه syncService عبر الوسيط بـ create(model, mapper(doc)) فيُنشأ في أودو كمسوّدة بانتظار الاعتماد المحاسبيّ. مفتاح منع التكرار = معرّف مستند Firestore، فلا ازدواج عند إعادة المحاولة.

مستند البوابة نموذج أودو الحالة عند الإنشاء
PO — أمر شراء purchase.order مسوّدة / طلب عرض أسعار
PR — طلب شراء purchase.requisition مسوّدة
GRN — الاستلام stock.picking (وارد) مسوّدة (Draft)
DN — إذن تسليم stock.picking (صادر) مسوّدة (Draft)
SO / POS — بيع sale.order / pos.order مسوّدة
INV — فاتورة account.move مسوّدة (Draft)

الحالة: القناة والوسيط مكتوبان وجاهزان (غير منشورَين)، ودفع المستندات مصمَّمٌ ويحتاج توسعة المُطابِق للنماذج الحاكمة أوّلًا.

٢ · المزامنة الدوريّة — جلب الأصناف والموردين والأوامر أوّلًا بأوّل

آليّة «السحب التزايديّ» (Incremental Polling): يسأل syncService أودو كل فترة عن السجلّات التي write_date > lastSync فقط (لا سحب كامل مكلف)، فيجلب الجديد والمتغيّر فحسب. المؤشّر lastSync محفوظ في وثيقة sync_state بفايربيز، وتُحدَّث مرايا القراءة odoo_mirror_*. المُشغِّل: Cloudflare Cron Trigger (الأنظف، يعمل على Spark المجانيّة)، أو مؤقّت في متصفّح مديرٍ كبديلٍ بلا بنية.

الأصناف
product.product

أصناف جديدة/معدّلة + الفئات والوحدات

الموردون والعملاء
res.partner

بيانات الشركاء أوّلًا بأوّل للمرآة

أوامر الشراء
purchase.order

حالة الاعتماد والاستلام تِباعًا

أوامر البيع
sale.order

أوامر البيع وحالتها للمرآة المحاسبيّة

النقل
stock.picking

التحويلات الداخليّة وحالاتها

تحديث الرصيد
qty_available

الرصيد المحسوب (قراءة فقط) من stock.quant

الحالة: مصمَّمة في عقد التكامل (المرحلة ١) — تعمل على Spark عبر Cloudflare Cron، ولم تُبنَ خدمة syncService بعد. الرصيد يبقى محسوبًا في أودو (مدفوعًا بـstock.quant) فيُسحَب للمرآة فقط، وتبقى أرصدة البوابة التشغيليّة مرجعها فايربيز.

٣ · ربط كيانات البوابة بأودو (أصناف · موردون · عملاء · عمليّات)

المبدأ الحاكم: المعرفة الأودووّية محصورة في طبقتين فقط — المُطابِق odooMapper.js (أسماء الحقول وشكل السجلّ) والموديول brandzo_warehouse (المنطق). لا يعرف أيّ مكوّن آخر شيئًا عن أودو، والمُطابِق يتدهور بأمان عند غياب أي حقلٍ مخصّص. حيث لا مطابقة نظيفة نستعمل حقولًا مخصّصة x_* أو نماذج الموديول الخاصّة bz.* (bz.gate.pass · bz.cycle.count · bz.period.close).

الأصناف — Items_Master product.product
⇄ ثنائيّ (عدا الرصيد: سحب) مُطابِق منفَّذ

default_code⇄sku · name⇄nameAr · x_name_en⇄nameEn · categ_id→category · uom_id→unit · qty_available→balance (محسوب، قراءة فقط) · x_min_stock⇄minStock

الموردون / العملاء res.partner
→ سحب (+ دفع اختياريّ) مصمَّم

name · ref · vat · phone · email · علم مورّد/عميل — مرآة قراءة في odoo_mirror، ودفع اختياريّ لشريكٍ جديد

الحركات المخزنيّة stock.move
→ سحب (اتّجاه واحد للسجلّ) مُطابِق منفَّذ

product_id→sku (يُستخرج الرمز من «[CODE] Name») · product_uom_qty→qty · date→timestamp

الاستلام/التسليم/النقل stock.picking (in/out/internal)
⇄ دفع مسوّدة + سحب حالة مصمَّم

دفع المستندات كمسوّدات + سحب الحالة (draft→done) للمرآة

المشتريات purchase.order / purchase.requisition
⇄ دفع + سحب مصمَّم

دفع أمر/طلب الشراء كمسوّدة + سحب حالة الاعتماد والاستلام أوّلًا بأوّل

المبيعات / الفواتير sale.order · pos.order · account.move
→ دفع (مرآة محاسبيّة) مصمَّم

دفع أمر البيع/الفاتورة كمسوّدة إلى أودو للمرآة المحاسبيّة

٤ · آلية المزامنة بين أودو وفايربيز

القاعدة المعماريّة: فايربيز مصدر الحقيقة التشغيليّ · أودو مرآة محاسبيّة/رقابيّة (امتداد لقرار §19-5 المحسوم). لماذا لا العكس؟ لأن البوابة تعمل أوفلاين بكاش Firestore الدائم، وحرّاسها الخمسة والدورة المستندية مبنيّة على Firestore، وخطّة Spark لا تتحمّل ربطًا حيًّا ثقيلًا بأودو.

فايربيز ← أودو (دفع)

المستندات المنجَزة تُدفع عبر الوسيط بـcreate() فتُنشأ في أودو كمسوّدات — طابور sync_outbox بمفتاح idempotency.

أودو ← فايربيز (سحب)

السحب الدوريّ يكتب المرايا odoo_mirror_* (قراءة فقط)؛ لاحقًا Webhooks تكتب مباشرةً عبر REST فتُلغي الـpolling.

سياسة التعارض

«الأحدث يكسب + سجلّ تعارض» يُعرض في شاشة «مراقبة المزامنة» (بنمط أودو ListView) — تُبنى لاحقًا.

صمود أوفلاين

persistentLocalCache + مدير تبويبات → قراءة أوفلاين وطابور كتابة يُدفع تلقائيًّا عند العودة. القيد الوحيد: أوّل دخول أونلاين.

٥ · آلية النسخ الاحتياطي

مستوى فايربيز (مصدر الحقيقة)
  • · كاش محلّيّ دائم على القرص (persistentLocalCache) + طابور كتابة — صمودٌ أمام انقطاع الشبكة بلا فقد قيد.
  • · متانة مُدارة من Google لبنية Firestore نفسها.
  • · قيد Spark: لا نسخ مجدول ولا Cloud Functions — ترقية Blaze تفتح التصدير المجدول (معلّقة على تجديد بطاقة المالك).
مستوى أودو (PostgreSQL)
  • · pg_dump يوميّ مجدول + لقطة إلزاميّة قبل أي ترقية أو تهجير.
  • · التغيير بالهجرة لا بالمسح (Odoo/OpenUpgrade في المكان) — ممنوع إعادة إنشاء قاعدة من الصفر على بيانات حيّة.
  • · تمرين استعادة شهريّ على قاعدة جانبيّة — «النسخة التي لم تُجرَّب استعادتها ليست نسخة».
  • · التحقّق من فصل volume القاعدة عن حاوية أودو في docker-compose.

٦ · آلية الانتقال إلى قاعدة بيانات محليّة خاصّة بالشركة

العائق الحقيقيّ: لا توجد طبقة عزل اليوم — Firestore خام في ~151 موضعًا عبر 25 ملفًّا، و59 مستمع onSnapshot حيّ، والأهمّ أن منطق العمل نفسه يعيش في firestore.rules (اعتماد · لا-تعديل · «الرقم مرّة واحدة») لا في JS.

القالب الصحيح موجودٌ بالفعل داخل المشروع — وهو نفس نمط أودو: واجهةٌ واحدة تُختار بمتغيّر بيئة.

// النمط القائم (odoo/index.js)
export const odoo = isProduction ? realOdooClient : mockOdooClient;

// المستهدف بنفس النمط (src/services/db/index.js)
export const store = PROVIDER === 'firestore' ? firestoreStore : localStore;  // PUBLIC_DB_PROVIDER
١طبقة Repository (شرط مسبق)

إنشاء src/services/db/ بمستودعات لكل مجموعة تُخفي بدائيّات Firestore — تنتقل الـ151 موضعًا خلف ~15 وحدة، تدريجيًّا، بدءًا بمجموعات الكاشير.

٢مُبدِّل المزوّد

مبدّل PUBLIC_DB_PROVIDER يختار Firestore أو مزوّدًا محلّيًّا (SQLite عبر Tauri · IndexedDB) بلا لمس الصفحات.

العوائق التي يجب أن يحلّها أيّ مزوّد بديل: الاشتراك الحيّ (وإلّا استقصاء)، والذرّية (runTransaction/increment شرط صحّة لا رفاهية)، وطبقة إلزامٍ تكافئ firestore.rules (وإلّا هبطت الضمانات إلى عميلٍ يُتجاوَز). المصادقة يمكن إبقاؤها Firebase Auth، أو هويّة محلّية للمحطّة المنعزلة.

٧ · حوكمة تغيّر النموذج (الإصدار ١٩) وطلب التحديث من المطوّرين

الخطّة الحاليّة مبنيّة على أودو ١٩ (الموديول إصدار 19.0.1.2.0). ولأن المعرفة الأودووّية محصورة في طبقتين فقط، فإن تغيّر النموذج أو الإصدار يُمتَصّ في موضعٍ واحد لا في كل المشروع — وسطح execute_kw مستقرٌّ عبر الإصدارات منذ سنوات، فالقناة نفسها لا تتأثّر بالترقية.

src/services/odoo/odooMapper.js

يحمل أسماء الحقول وشكل السجلّ — يُحدَّث وحده إن تغيّرت أسماء الحقول أو بنيتها.

brandzo-addons/brandzo_warehouse

يحمل المنطق الأودو-جانبيّ (4 نماذج خاصّة bz.* + توسعة 7 نماذج قياسيّة) — يُرقَّى ويُختبر بالإصدار الجديد.

مسار الترقية الإلزاميّ (لا إنتاج قبل خُضرة المختبر)

  1. 1 تثبيت الإصدار الجديد في صورة Docker على فرع ترقية منفصل.
  2. 2 مختبر بالإصدار الجديد ثم odoo -u brandzo_warehouse.
  3. 3 تشغيل الاختبارات الـ52 كلّها والتأكّد من خضرتها.
  4. 4 جدول توافق الحقول — سابقة موثّقة: ترقية 19 كشفت product_uom→product_uom_id وحذف category_id.
  5. 5 لا نشر للإنتاج قبل خضرة المختبر بالكامل.

آليّة طلب التحديث من إدارة التقنية مستقبلًا

  • · مصدر تغيير واحد: تغيّر حقلٍ ← تعديل odooMapper.js وحده · تغيّر منطقٍ ← الموديول + اختباراته. لا انحراف.
  • · مسار الطلب: فرع ترقية ← تعديل المُطابِق/الموديول ← تشغيل 52 اختبار الموديول + حواجز البوابة (npm test ‏657 اختبارًا · npm run audit · npm run lint) ← توثيق جدول توافق الحقول ← لا دمج/نشر قبل الخُضرة.
  • · حرّاس صلبة: قائمة سماح الوسيط تمنع أي نموذج غير مُدرَج، وحاجزا النشر يمنعان صفحة يتيمة أو مجموعة بلا قاعدة أمان.
  • · توثيق حاكم: عقد التكامل docs/عقد-التكامل-مع-أودو.md + خطة التطوير الشاملة + ODOO_BACKEND_DEVELOPMENT_PLAN.md — أي مطوّر جديد يبدأ منها.

٨ · سيناريوهات العوائق وكفاءة الربط

السيناريو السبب المعالجة
الوسيط غير منشور بعد لم يُنشَر Cloudflare Worker ولم يُضبط PUBLIC_ODOO_PROXY_URL نشر الوسيط على حساب Cloudflare مجانيّ (خطوة المالك) — بلا تغيير كود
أودو على localhost:8069 فقط الوسيط السحابيّ لا يرى عنوانًا محلّيًّا — المزامنة اللحظيّة مشروطة قرار المالك: استضافة أودو على عنوان عامّ — يفتح المرحلة الفوريّة
خطّة Spark بلا Cloud Functions لا مشغّل خادميّ دائم للسحب الدوريّ Cloudflare Cron Trigger كمشغّل مجانيّ؛ أو ترقية Blaze لاحقًا
تعذّر الوصول للوسيط (شبكة) انقطاع شبكة/DNS/CORS — بلا استجابة HTTP العميل يرمي رسالة واضحة، والبوابة تبقى عاملة كاملةً (صفر تبعيّة تشغيليّة)
فشل مصادقة أودو مفتاح API/مستخدم خاطئ — uid فارغ رسالة «فشلت مصادقة أودو» + زرّ «اختبار الاتصال» يكشفها مبكرًا
استدعاء خارج قائمة السماح نموذج/دالّة غير مُدرَجَين في allowlist الوسيط الوسيط يردّ 403 Blocked — أي توسعة تُضاف صراحةً بمراجعة
تعارض بيانات (عُدِّل الجانبان) نفس السجلّ تغيّر في البوابة وأودو سياسة «الأحدث يكسب + سجلّ تعارض» يُعرض في شاشة «مراقبة المزامنة»
انقطاع الإنترنت أثناء العمل الموظّف يعمل في موقعٍ ضعيف/بلا شبكة كاش Firestore الدائم + طابور كتابة يُدفع تلقائيًّا عند العودة (قيد: أوّل دخول أونلاين)
تغيّر إصدار أودو أو أسماء الحقول ترقية أودو تُغيّر بنية النموذج (سابقة: product_uom→product_uom_id) مسار ترقية إلزاميّ: فرع منفصل + جدول توافق + 52 اختبارًا قبل الإنتاج

خلاصة الكفاءة

أقوى خصائص الكفاءة هنا هي صفر تبعيّة تشغيليّة: البوابة تسلّم عملها كاملًا سواء أكان أودو موصولًا أم لا، فالتكامل قيمةٌ مضافة لا نقطة انهيار. البنية جاهزة بنسبة ~70% ومعزولة بنظافة (تُوصَل بلا إعادة تصميم)، والمزامنة الدوريّة تعمل على الخطّة المجانيّة عبر Cloudflare Cron. ما تبقّى خطوتان بيد المالك: نشر الوسيط (يفتح السحب الدوريّ ودفع المستندات)، واستضافة أودو على عنوان عامّ (يفتح المزامنة اللحظيّة الحقيقيّة). حتى ذلك الحين: «شبه لحظيّ» عبر سحبٍ قصير ودفعٍ فور «منجَز».

إعداد: محمد البرشي — متخصص سلاسل الإمداد والمخازن
بنغازي · 2026 · تقريرٌ داخليٌّ غير تجاريّ — مبنيٌّ على مراجعة الكود
BFP-SCM-PR-2026-ODOO-01
آخر تحديث: 2026-08-05