بنية محرّك RFQ: لماذا تُهمّ حجوزات التوفّر ولقطات الإلغاء
المشكلة التي تتجاهلها معظم أتمتة RFQ
عندما تُؤتمت الفرق سير عمل RFQ، تتركّز عادةً على الواجهة الأمامية: تحليل طلب المشتري، والبحث في الكتالوج، ومطابقة المنتجات، وتطبيق التسعير. هذا العمل حقيقي وتوجد أدوات له — فالذكاء الاصطناعي يستخرج المتطلبات بينما تتولى القواعد الحتمية التسعير والموافقات وتتبع SLA، كما يصف GEP في تحليل أتمتة المشتريات. يؤكد دليل Ivalua للتوريد 2026 النمط ذاته: الذكاء الاصطناعي يتولى الاستخراج والمطابقة، والقواعد تتولى الحوكمة.
لكن مشكلتين تكسران هذا النموذج في الإنتاج، ومعظم الأتمتة لا تعالجهما:
انزياح التوفّر. اقتباس تستغرق مفاوضته ثلاثة أيام هو اقتباس ضد مخزون قد لا يكون موجوداً بعد بالسعر المعروض. إذا وعد الوكيل بـ50 وحدة بسعر 12.40$ ولم يتبقّ لدى المورد سوى 12 وحدة عندما يقبل المشتري، فالاقتباس لم يكن قابلاً للتسليم من البداية. الوكيل لم يكذب — العالم تغيّر تحته.
انزياح السياسات. غرامة إلغاء اقتبست قبل 30 يوماً قد تختلف عن الغرامة السارية وقت الحجز. إذا شدّد المورد الشروط في منتصف المفاوضات ولم يلتقط الوكيل السياسة وقت الاقتباس، فسيتنازع المشتري على الرسوم لاحقاً. النزاع مشروع — الشروط تحرّكت.
البنية التي تحلّ كليهما هي محرّك RFQ بآليتين: حجوزات توفّر ذرّية تحجز المخزون المقيّد لإطار زمني، ولقطات سياسات إلغاء تُجمّد الشروط التجارية لحظة الاقتباس. هذه ليست مزايا مُلحقة بأداة اقتباس. بل التزامات هيكلية في نموذج البيانات — حقول وانتقالات حالة وعمليات دورة حياة يفرضها المحرّك.
نموذج الكيانات الذي يجعله يعمل
يحتاج محرّك RFQ إنتاجي إلى أكثر من طلب واقتباس. يجب أن يفصل نموذج الكيانات نية المشتري عن استجابة المورد، ويجب أن يتتبع العرض المقيّد بشكل مستقل عن الكتالوج. الكيانات التالية تشكّل العمود الفقري:
Request — نية المشتري: جهة الاتصال، العنوان، البنود، عناوين الفواتير والشحن، الانتهاء، الحالة. ينتقل الطلب عبر آلة حالة مُعرَّفة: initial → in_progress → confirmed → completed، مع modified كنقطة إعادة دخول عندما تتغير البنود بعد وجود اقتباسات.
Item — منتج الكتالوج الذي يريده المشتري. البنود عامة؛ لها UUID ونوع ووضع تسعير ووحدات قياس. يُعيَّن نفس البند إلى بنود مورد متعددة.
ProviderItem — عرض المورد المحدد لذلك البند، بسعر أساسي لكل وحدة قياس. ينتمي بند المورد إلى شركة مورد ويمكن أن يحتوي على دفعات متعددة.
ProviderItemBatch — وحدة المخزون المقيّد. لكل دفعة رقم دفعة وتاريخ إنتاج وتاريخ انتهاء وتكلفة لكل وحدة وعلم توافر وعلم slow_move_item للمخزون بطيء الحركة. الدفعات هي الحبيبية التي يُفحص ويُحجز عندها التوفّر.
Quote — استجابة المورد المُسعَّرة لطلب مؤكد. للاقتباس مورد ومندوب مبيعات وطريقة ومبلغ شحن وعملة وسعر صرف وطابع زمني لقفل FX وعدد جولات تفاوض (مُحتسب تلقائياً) وآلة حالة: initial → in_progress → confirmed → completed أو disapproved.
QuoteItem — سطر في الاقتباس، يربط بند المورد والدفعة بكمية وسعر لكل وحدة ومجموع فرعي وخصم ومجموع فرعي نهائي. بشكل حاسم، يحمل بند الاقتباس hold_token وhold_expires_at وguardrail_price_per_uom وslow_move_item — الحقول التي تجعل حجوزات التوفّر وحواجز الربحية مرئية على مستوى السطر.
CancellationPolicy — الشروط التي يمكن بموجبها إلغاء الحجز، بمستويات ومواعيد نهائية وقواعد غرامات، مُحددة لكل بند مورد.
Installment — جدول الدفع المرتبط باقتباس مؤكد، بأولوية وتاريخ مُجدول ومبلغ ونسبة وآلة حالة: pending → paid أو cancelled.
هذا ليس جدولاً مسطحاً. إنه رسم بياني مُنمط بمرجعين متداخلين — يحل الطلب اقتباساته، ويحل الاقتباس بنوده، ويحل بند الاقتباس بند المورد والدفعته. يستخدم المحرّك التحميل الدفعي المبني على DataLoader بحيث لا يطلق اقتباس بـ20 بنداً 20 استعلام قاعدة بيانات تسلسلياً.
حجوزات التوفّر: حجز الطاقة ذرياً
حجز التوفّر هو الآلية التي تمنع انزياح التوفّر. بدونه، يقتبس الوكيل ضد لقطة مخزون قد تكون عتيقة بحين يقرر المشتري. به، يحجز الوكيل الطاقة لإطار زمني ويكون الاقتباس مدعوماً بعرض حقيقي مُخصَّم.
دورة حياة الحجز لها أربع حالات وقواعد انتقال صارمة:
| الحالة | المعنى | الانتقالات المسموحة |
|---|---|---|
held |
الطاقة المُخصَّمة، رمية حجز صادرة بـTTL (15 دقيقة) | confirmed, released, expired |
confirmed |
الحجز مُلتزم، لا خصم طاقة ثانٍ | نهائي |
released |
الحجز مُلغى، الطاقة مُستعادة (تكراري) | نهائي |
expired |
انقضى TTL، الطاقة مُستعادة (تكراري) | نهائي |
القرار التصميمي الحاسم في انتقال confirmed: لا خصم طاقة ثانٍ. الحجز حجز الطاقة بالفعل. تأكيد الحجز يحوّل الحجز إلى حالة مؤكدة دون لمس عداد المخزون مرة أخرى. هذا يمنع العدّ المزدوج — الخطأ الذي يخصم فيه الحجز الطاقة، ثم يؤكّدها الخصم مرة أخرى، ويعتقد النظام أن لديه عرضاً أقل مما هو فعلاً.
الحجز ذري. تُخصم mutation بإجراء acquireAvailabilityHold الطاقة المتاحة وتُعيد hold_token بطابع زمني expires_at. إذا كانت الطاقة غير كافية، تفشل العملية دون آثار جانبية — لا حجز جزئي، لا حالة قذرة. تعيد mutation بإجراءات releaseAvailabilityHold وexpireAvailabilityHold الطاقة وهي تكرارية: استدعاؤها مرتين على نفس الرمية يُعيد نفس النتيجة. هذا مهم لتفاوت الأخطاء — إذا تسبب انتهاء مهلة الشبكة في إعادة المحاولة، لا يُفرط النظام في استعادة الطاقة.
كيف يُرسم هذا إلى سير عمل اقتباس
اعتبر موزعاً يقتبس 200 وحدة مضخة صناعية من مورد لديه ثلاث دفعات بتواريخ انتهاء وتكاليف مختلفة. الوكيل:
- يستدعي
check_availabilityبـUUID بند المورد ونافذة الخدمة والكمية — يحصل على الدفعات المطابقة والكمية المتاحة. - يستدعي
acquire_availability_hold— تُخصم الطاقة ذرياً، وتُعادhold_tokenبـTTL 15 دقيقة. - يحمل بند الاقتباس الآن
hold_tokenوhold_expires_at. يرى المشتري اقتباساً مدعوماً بمخزون حقيقي، لا تخمين كتالوج. - إذا يقبل المشتري خلال 15 دقيقة، يستدعي الوكيل
confirm_availability_hold— ينتقل الحجز إلىconfirmed، تُقفل الطاقة، لا خصم ثانٍ. - إذا يرفض المشتري أو ينقضي TTL، يستدعي الوكيل
release_availability_holdأو يتركه ينتهي — تُستعاد الطاقة، يمكن للاقتباس التالي استخدامها.
hold_token هو مقبض صريح. يمرره النموذج كوسيط، لا كحالة جلسة مخفية. هذا هو نفس نمط المقبض الصريح الذي يُشكّله مرشح إصدار MCP 2026-07-28: الحالة التي يحتاجها الوكيل عبر استدعاءات الأدوات تصبح مقبضاً مرئياً قابلاً للتدقيق بدلاً من بيانات وصفية لطبقة النقل.
لقطات الإلغاء: تجميد الشروط
انزياح سياسة الإلغاء هو وضع الفشل الثاني. قد يغيّر المورد غرامات الإلغاء بين الاقتباس والحجز — غرامة 10% قبل 30 يوماً تصبح 50% قبل 7 أيام. إذا اقتبس الوكيل الغرامة القديمة وقبل المشتري، سيتنازع المشتري على الرسوم الجديدة. النزاع صحيح.
تحل البنية هذا بالتقاط لقطة سياسة الإلغاء وقت الاقتباس. لكيان CancellationPolicy tiers (بنية JSON بمواعيد نهائية ونسب غرامات)، وlabel، وdescription، وstatus. عندما يُنشئ الوكيل اقتباساً، يسترد سياسة الإلغاء النشطة لكل بند مورد ويُرفق شروط السياسة بالاقتباس. يُجمَّد UUID السياسة ومستوياتها — حتى لو حدّث المورد السياسة لاحقاً، يحمل الاقتباس الشروط السارية وقت إصداره.
أداة search_cancellation_policies تُرشّح ببند المورد والحالة، فيستطيع الوكيل استرداد السياسة النشطة لكل بند سطر. تُربط السياسة حينئذ ببند الاقتباس، لا تُشار إليها بمفتاح أجنبي فقط — اللقطة هي السياسة، لا مؤشر إلى سجل قابل للتغيير.
يُعمَّم هذا النمط خارج الإلغاء. أسعار الصرف تستخدم النهج ذاته: يحمل QuoteType fx_rate وfx_rate_locked_at — يُقفل سعر الصرف وقت الاقتباس، لا يُبحث عنه وقت الحجز. إذا عملة عرض المشتري تختلف عن عملة اقتباس المورد، السعر المقفل هو السعر الذي يراه المشتري، بغض النظر عن حركة السوق.
مستويات التسعير، البحث عن القطاع، والحواجز
التسعير ليس حقلاً مسطحاً. يحل المحرّك مستويات التسعير بقطاع العميل والكمية وبند المورد. تأخذ أداة get_item_price_tiers بريد العميل وقائمة بنود اقتباس (كل منها UUID بند وUUID بند مورد وكمية)، وتبحث عن قطاع العميل عبر جهات اتصال القطاع، وتحمّل دفعياً جميع مستويات التسعير المطابقة في استعلام واحد. تُرشَّح المستويات بحدود الكمية على مستوى قاعدة البيانات — تُعاد فقط المستويات التي يحيط فيها quantity_greater_then وquantity_less_then بالكمية المطلوبة.
تعمل مطالبات الخصم بنفس الطريقة، بنظام نطاق هرمي: GLOBAL (ينطبق على جميع الاقتباسات)، SEGMENT (ينطبق على قطاع عميل)، ITEM (ينطبق على بند محدد)، وPROVIDER_ITEM (ينطبق على عرض مورد محدد لبند). أداة get_discount_prompts تحمّل من النطاقات الأربعة جميعها، وتُزيل التكرار، وتُعيد المجموعة المدمجة بالشروط والقواعد. هذا يعني أن اقتباساً يمكن أن يطبق خصم حجم (نطاق قطاع) وعرض موسمي (نطاق بند) وتصفية خاصة بمورد (نطاق بند مورد) في آن واحد — كلٌّ بأولويته الخاصة.
حقل guardrail_price_per_uom على بند الاقتباس هو حاجز الربحية — الحد الأدنى المقبول للسعر للبند، محمّل من دفعة بند المورد. إذا يطبق الوكيل خصماً يُسقط السعر تحت الحاجز، لا تفشل العملية (قد يكون للوكيل سبب مشروع) لكن يظهر العلم في استجابة بند الاقتباس. علم slow_move_item يُعلّم المخزون الباقي طويلاً — يمكن للوكيل استخدامه لتطبيق خصومات أعمق، لكن الحاجز يمنع التسعير غير المربح.
آلات الحالة التي تمنع العمليات غير الصالحة
محرّك RFQ دون فرض الحالة هو جدول بيانات. يفرض المحرّك أربع آلات حالة، كل بقواعد انتقال صريحة وحواجز عملية:
Request: initial → in_progress → confirmed → completed، مع modified كنقطة إعادة دخول. يفرض RequestOperationGuard أن الاقتباسات تُنشأ فقط من طلبات confirmed، وأن تعديلات البنود مسموحة فقط في حالات initial وin_progress وmodified.
Quote: initial → in_progress → confirmed → completed أو disapproved. يفرض QuoteOperationGuard أن تعديلات البنود مسموحة فقط في initial أو in_progress، وأن الأقساط تُنشأ فقط من اقتباسات confirmed.
حجز التوفّر: held → confirmed / released / expired. الحالات المستهدفة الثلاث جميعها نهائية.
Installment: pending → paid أو cancelled. الحالتان المستهدفتان كلتاهما نهائية.
عندما تتغير بنود طلب مؤكد وتوجد اقتباسات بالفعل، ينتقل الطلب إلى modified وتُرفض جميع الاقتباسات الموجودة تلقائياً — يجب أن يحصل المشتري على اقتباسات جديدة مقابل البنود المحدّثة. هذا يمنع قبول اقتباسات عتيقة بعد تغيّر الطلب الذي أنشأها.
عندما تُدفع جميع الأقساط غير المُلغاة على اقتباس، ينتقل الاقتباس تلقائياً إلى completed. عندما يكتمل اقتباس واحد على الأقل من طلب، ينتقل الطلب تلقائياً إلى completed. آلات الحالة ليست استشارية — يتحقق المحرّك من كل انتقال ويُطلق خطأ VALIDATION_FAILED بالحالة الحالية والانتقالات المسموحة إذا كانت العملية غير صالحة.
نمط الوحدة: 38 أداة، 11 mixin نطاق
التنفيذ المرجعي لهذه البنية هو وحدة MCP واحدة — mcp_hospirfq_processor — بـ38 أداة مسجَّلة مُركَّبة من 11 mixin نطاق: Request وItem وAvailability وQuote وPricing وInstallment وBundle وCancellation وFile وSegment وCatalog. تتشارك mixin صنفاً أساسياً GraphQLBackedProcessor يوفر عميل GraphQL والمسجِّل والإعدادات. يساهم كل mixin في أدواته الخاصة ويصل فقط إلى self.logger وself.setting وself._execute_graphql_query.
تُسجَّل الأدوات عبر كتلة تكوين (MCP_CONFIGURATION) تُعرّف اسم كل أداة ووصفها ومدخلها (JSON Schema) ورابط الوحدة (اسم الصنف، اسم الدالة، نوع الإرجاع). يحمّل وقت تشغيل MCP هذا التكوين عند البدء ويوزّع استدعاءات الأدوات إلى دوال mixin المناسبة. يغلّف مُزخرف @handle_errors كل استدعاء أداة بمعالجة أخطاء هيكلية، مُنتجاً استجابات خطأ مُنمطة برموز مثل GRAPHQL_QUERY_FAILED وVALIDATION_FAILED وHOLD_NOT_FOUND وHOLD_ALREADY_EXPIRED وAVAILABILITY_INSUFFICIENT وPRICING_MODE_UNSUPPORTED.
تشمل الأدوات الـ38 دورة حياة RFQ كاملة:
| النطاق | الأدوات | العمليات الرئيسية |
|---|---|---|
| إدارة الطلبات | 8 | تقديم، تحديث، الحصول، البحث، إضافة/إزالة البنود، تعيين/إزالة بنود المورد |
| إدارة البنود | 3 | البحث عن البنود، الحصول على بند، الحصول على بنود المورد بمعلومات الدفعة |
| إدارة الاقتباسات | 4 | تحديث اقتباس، الحصول على اقتباس، البحث عن اقتباسات، تحديث بند اقتباس |
| التسعير | 3 | الحصول على مستويات الأسعار، الحصول على مطالبات الخصم، حساب تسعير الاقتباس |
| الأقساط | 4 | إنشاء، تحديث، إنشاء جدول، الحصول على الأقساط |
| التوفّر | 5 | التحقق، اكتساب الحجز، التحرير، التأكيد، الانتهاء |
| الحزم | 3 | البحث عن الحزم، الحصول على حزمة، البحث عن المكونات |
| الإلغاء | 2 | الحصول على سياسة، البحث عن سياسات |
| الكتالوج | 1 | استعلام الكتالوج (بحث رسم المعرفة) |
| القطاع | 1 | الحصول على جهات اتصال القطاع |
| الملف | 2 | رفع، الحصول على الملفات |
| سير العمل | 2 | تأكيد الطلب + إنشاء اقتباسات، تأكيد الاقتباس + إنشاء أقساط |
أداتا سير العمل — confirm_request_and_create_quotes وconfirm_quote_and_create_installments — عمليات ملائمة تُركّب أدوات ذرّية متعددة في استدعاء واحد. الأولى تؤكد طلباً وتُنشئ اقتباسات لموردين مختارين في عملية واحدة. الثانية تؤكد اقتباساً وتُنشئ خطة الأقساط في عملية واحدة. هاتان تقلّلان عدد الذهاب والإياب الذي يحتاجه الوكيل لأكثر سير العمل متعدد الخطوات شيوعاً.
أداة inquire_catalog هي الجسر إلى رسم المعرفة. تأخذ استعلاماً بلغة طبيعية، وفضاء اسم اختياري (hotel، flight، activity)، وحدّاً، وتُعيد نتائج مرتّبة من محرك رسم المعرفة. هكذا يكتشف الوكيل المنتجات قبل الاقتباس — لا باستعلامات SQL ضد جدول منتجات، بل بالبحث الدلالي عبر رسم كتالوج يعرف البدائل والتوافق والتوفّر.
لماذا تُعمَّم هذه البنية
بُني التنفيذ المرجعي للسفر والضيافة — مخزون مقيّد بالتواريخ، وقواعد الإشغال، وتسعير لكل راكب، وحزم، وودائع، وشروط إلغاء. هذا هو اختبار الإجهاد. إذا يستطيع المحرّك اقتباس وحجز غرفة فندق لتواريخ محددة، وحجز التوفّر بـTTL، والتقاط لقطة سياسة الإلغاء، وقفل سعر الصرف، وتطبيق مستويات تسعير خاصة بالقطاع، وتسليم خطة أقساط، فيستطيع دعم أي سير عمل RFQ B2B بموردين ومشترين ومخزون مقيّد وشروط تجارية.
التعميم هيكلي:
- التوزيع والجملة: استبدل غرف الفندق بمخزون المستودع، وبنود المورد بـSKU المورد، والدفعات بأرقام الدفعات وتواريخ الانتهاء. حجز التوفّر يحجز المخزون؛ لقطة الإلغاء تحفظ شروط الإرجاع.
- المشتريات: استبدل حزم السفر بالقطع والمواد. مستويات التسعير المبنية على القطاع تطبق معدلات العقد المُتفاوض عليها؛ السعر الحاجز يمنع المشتريات غير المربحة.
- التصنيع: استبدل نوافذ الخدمة بفتحات الإنتاج. حجز التوفّر يحجز طاقة خط إنتاج؛ سياسة الإلغاء تحفظ غرامة إعادة الجدولة.
- الخدمات الميدانية: استبدل تواريخ الفندق بنوافذ المواعيد. حجز التوفّر يحجز فترة زمن الفني؛ خطة الأقساط تُهيكل جدول الدفع.
نموذج الكيانات — Request وItem وProviderItem وProviderItemBatch وQuote وQuoteItem وCancellationPolicy وInstallment — مستقل عن الصناعة. mixin النطاق قابلة للتركيب: قد يحذف نشر المشتريات mixin Bundle ويبقي الباقي. قد يضيف نشر التصنيع mixin ProductionSlot بجانب Availability. نمط الوحدة، آلات الحالة، ومقاربة المقبض الصريح تبقى كما هي.
موزع يُشغّل NetSuite وBigCommerce وثلاثة كتالوجات مورد يحصل على وكيل يتلقى RFQ بالبريد أو البوابة، يحل المنتجات والبدائل ضد رسم الكتالوج، يُسعّر لكل قطاع عميل، يحجز المخزون بانتهاء، يلتقط شروط الإلغاء، يقفل سعر الصرف، ويكتب الاقتباس المقبول عائداً إلى NetSuite — كل استدعاء أداة مُسجَّل وكل انتقال حالة مُتحقَّق منه. هذا البناء هو المرحلة 2-3 من طريقة الأربع خطوات وعادةً ما يكون حياً في 5-8 أسابيع.
اطلب بناءً محدد النطاق. اكتشاف أسبوع واحد. تحصل على جرد أنظمة وخريطة سير عمل ونطاق ثابت — سواء تبني معنا أم لا.
هل تريد هذا مبنياً لأنظمتك؟
كل وثيقة هنا من عمل إنتاجي حقيقي. إذا كان لديك نظام مُستهدَف وسير عمل في الذهن، نستطيع تحديد نطاق بناء في أسبوع واحد.
اطلب بناءً محدد النطاقاكتشاف مدته أسبوع واحد. تحصل على جرد للأنظمة وخريطة لسير العمل ونطاق ثابت — سواء بنيت معنا أم لا.