انتقل إلى المحتوى الرئيسي

أدلة تفصيلية حول Playbooks في Ansible

تُقدم منتجات OmniCRM باستخدام Ansible، مما يسمح بإدارة الخدمات بشكل آلي بناءً على المتطلبات المحددة لكل منتج ومخزونه المرتبط.

انظر أيضًا: توفير بطاقة SIM للحصول على مثال كامل عن التوفير المعتمد على Ansible للخدمات المحمولة، بما في ذلك بطاقات SIM الفعلية وeSIMs.

كيف تعمل Playbooks والمنتجات معًا​

المفهوم الحاسم: Playbooks هي ما ينشئ فعليًا الخدمات في OmniCRM. عندما تعين Playbook لمنتج، فإنك تحدد ما يحدث عندما يتم توفير ذلك المنتج. يمكن أن يعني ذلك أشياء مختلفة لمنتجات مختلفة.

المنتجات تُحفز Playbooks​

عندما يتم توفير منتج في OmniCRM:

  1. يحدد تعريف المنتج أي Playbook يتم تشغيله (عبر حقل provisioning_play)
  2. يمرر المنتج متغيرات إلى Playbook (عبر provisioning_json_vars واختيارات المخزون)
  3. يتم تنفيذ Playbook ويقوم بما تم برمجته للقيام به
  4. يحدد Playbook ما يتم إنشاؤه (إذا كان هناك شيء)

ما يمكن أن تفعله Playbooks​

يمكن أن تقوم Playbook توفير واحدة بـ:

إنشاء خدمات متعددة
قد تقوم Playbook منتج مجمعة بإنشاء:

  • سجل خدمة إنترنت رئيسية
  • سجل خدمة IPTV إضافية
  • سجل خدمة VoIP
  • كل ذلك من خلال إجراء توفير منتج واحد

إنشاء خدمات بدون أي
بعض Playbooks لا تنشئ سجلات خدمة على الإطلاق:

  • Playbook تقوم فقط بتكوين معدات CPE
  • Playbook ترسل تكوينًا إلى معدات الشبكة
  • Playbook تقوم بتحديث الأنظمة الخارجية

إنشاء خدمة واحدة
النمط الأكثر شيوعًا:

  • إنشاء سجل خدمة واحد للعميل
  • ربط المخزون بتلك الخدمة
  • إعداد الفوترة لتلك الخدمة

تعديل الخدمات الموجودة
Playbooks التعبئة والإضافات:

  • لا تنشئ خدمات جديدة
  • تحديث سجلات الخدمة الموجودة (إضافة بيانات، تمديد انتهاء الصلاحية، إلخ)
  • إضافة أرصدة إلى حسابات الفوترة الموجودة

تنفيذ إجراءات بدون سجلات خدمة
بعض Playbooks هي عمليات بحتة:

  • إعادة تعيين أرصدة الحسابات
  • تبديل عناصر المخزون بين العملاء
  • توليد تقارير أو تكوينات

مثال: سلوكيات Playbook المختلفة​

# المنتج 1: خدمة SIM المحمولة (تقوم بإنشاء خدمة واحدة)
# provisioning_play: play_simple_service
- إنشاء سجل خدمة في CRM
- إنشاء حساب فوترة في OCS
- تعيين بطاقة SIM ورقم الهاتف من المخزون
- إرسال بريد إلكتروني ترحيبي

# المنتج 2: حزمة الإنترنت (تقوم بإنشا�� 3 خدمات)
# provisioning_play: play_bundle_internet_tv_voice
- إنشاء سجل خدمة إنترنت
- إنشاء سجل خدمة IPTV
- إنشاء سجل خدمة VoIP
- ربط جميعها بنفس العميل
- حساب فوترة واحد للحزمة

# المنتج 3: تعبئة البيانات (تقوم بإنشاء 0 خدمات)
# provisioning_play: play_topup_no_charge
- العثور على الخدمة الحالية بواسطة service_id
- إضافة رصيد بيانات إلى حساب OCS الموجود
- تحديث تاريخ انتهاء الخدمة
- لا توجد خدمة جديدة تم إنشاؤها

# المنتج 4: تكوين CPE (تقوم بإنشاء 0 خدمات)
# provisioning_play: play_prov_cpe_mikrotik
- توليد تكوين جهاز التوجيه
- تحديث سجل المخزون بالتكوين
- إرسال البريد الإلكتروني بالتكوين إلى فريق الدعم
- لا توجد خدمة تم إنشاؤها (فقط إعداد المعدات)

النقطة الرئيسية: تحدد Playbook السلوك، والمنتج هو مجرد محفز.

Plays مقابل المهام​

فهم الفرق بين Plays والمهام هو أمر أساسي للعمل مع Playbooks في OmniCRM.

Play (Playbook)
سير عمل توفير كامل ينظم مهام متعددة لتحقيق هدف تجاري. Plays هي Playbooks على المستوى الأعلى المخزنة في OmniCRM-API/Provisioners/plays/ ويتم الإشارة إليها في تعريفات المنتجات.

أمثلة:

  • play_simple_service.yaml - توفير خدمة أساسية
  • play_topup_no_charge.yaml - تطبيق تعبئة مجانية على خدمة
  • play_prov_cpe_mikrotik.yaml - تكوين معدات العملاء

Task (مكون قابل لإعادة الاستخدام)
مجموعة من العمليات المستقلة والقابلة لإعادة الاستخدام التي يمكن تضمينها بواسطة عدة Plays. تُسبق المهام بـ task_ وتعيش في نفس الدليل.

أمثلة:

  • task_welcome_email.yaml - إرسال بريد إلكتروني ترحيبي إلى عميل
  • task_activate_olt.yaml - تفعيل معدات OLT
  • task_notify_ocs.yaml - إرسال إشعارات إلى نظام الفوترة

العلاقة بينها:

# play_simple_service.yaml (A Play)
- name: Play توفير بسيطة
hosts: localhost
tasks:
- name: الكتلة الرئيسية للتوفير
block:
- name: إنشاء الخدمة
uri: ...

- name: تكوين الفوترة
uri: ...

# تضمين مهمة قابلة لإعادة الاستخدام
- include_tasks: task_welcome_email.yaml

# تضمين المهام بعد التوفير
- include_tasks: post_provisioning_tasks.yaml

هيكل Playbook وتشريحه​

تتبع جميع Playbooks في OmniCRM هيكلًا متسقًا. فهم هذا الهيكل أمر ضروري لإنشاء وصيانة Playbooks.

الهيكل الأساسي​

تبدأ كل Playbook بهذه العناوين القياسية:

- name: الاسم الوصفي لـ Playbook
hosts: localhost # دائمًا localhost لـ OmniCRM
gather_facts: no # معطل لأداء أفضل
become: False # لا تصعيد الامتيازات

tasks:
- name: الكتلة الرئيسية
block:
# مهام التوفير تذهب هنا

rescue:
# مهام التراجع/التنظيف تذهب هنا

شرح العنوان​

name
اسم وصفي يظهر في سجلات التوفير وواجهة المستخدم. يظهر هذا كـ playbook_description في سجل التوفير.

hosts: localhost
تعمل جميع Playbooks في OmniCRM على localhost لأنها تتفاعل مع الأنظمة البعيدة عبر APIs، وليس SSH.

gather_facts: no
تم تعطيل جمع الحقائق في Ansible لأن:

  • لا نحتاج إلى معلومات النظام
  • يضيف عبءًا غير ضروري
  • يمكن أن يتسبب في تعطل المتصفحات إذا تم عرضه في مخرجات التصحيح

become: False
لا حاجة لتصعيد الامتيازات لأننا نقوم بإجراء مكالمات API، وليس تعديل ملفات النظام.

تحميل التكوين​

يجب على كل Playbook تحميل ملف التكوين المركزي:

tasks:
- name: تضمين متغيرات crm_config
ansible.builtin.include_vars:
file: "../../crm_config.yaml"
name: crm_config

هذا يجعل التكوين متاحًا كـ crm_config.ocs.cgrates، crm_config.crm.base_url، إلخ.

عادةً ما يحتوي crm_config.yaml على:

ocs:
cgrates: "10.0.1.100:2080"
ocsTenant: "default_tenant"
crm:
base_url: "https://crm.example.com"

أنماط الوصول إلى المتغيرات​

يمكن أن تأتي المتغيرات من عدة مصادر:

من تعريف المنتج:

- name: الوصول إلى product_id الممرر بواسطة OmniCRM
debug:
msg: "توفير المنتج {{ product_id }}"

من اختيار المخزون:

- name: الحصول على معرف المخزون لبطاقة SIM
set_fact:
sim_card_id: "{{ hostvars[inventory_hostname]['SIM Card'] | int }}"
when: "'SIM Card' in hostvars[inventory_hostname]"

من استجابات API:

- name: الحصول على معلومات المنتج من API CRM
uri:
url: "http://localhost:5000/crm/product/product_id/{{ product_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_product

- name: استخدام اسم المنتج
debug:
msg: "اسم المنتج هو {{ api_response_product.json.product_name }}"

أنماط Playbook الشائعة​

نمط توفير الخدمة​

هذا هو النمط الأكثر شيوعًا لإنشاء خدمات جديدة.

- name: Playbook توفير الخدمة
hosts: localhost
gather_facts: no
become: False

tasks:
- name: الكتلة الرئيسية
block:

# 1. تحميل التكوين
- name: تضمين متغيرات crm_config
ansible.builtin.include_vars:
file: "../../crm_config.yaml"
name: crm_config

# 2. الحصول على معلومات المنتج
- name: الحصول على معلومات المنتج من API CRM
uri:
url: "http://localhost:5000/crm/product/product_id/{{ product_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
validate_certs: no
register: api_response_product

# 3. الحصول على معلومات العميل
- name: الحصول على معلومات العميل من API CRM
uri:
url: "http://localhost:5000/crm/customer/customer_id/{{ customer_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_customer

# 4. تعيين الحقائق من البيانات المسترجعة
- name: تعيين حقائق الحزمة
set_fact:
package_name: "{{ api_response_product.json.product_name }}"
package_comment: "{{ api_response_product.json.comment }}"
setup_cost: "{{ api_response_product.json.retail_setup_cost }}"
monthly_cost: "{{ api_response_product.json.retail_cost }}"

# 5. توليد معرفات فريدة
- name: توليد UUID
set_fact:
uuid: "{{ 99999999 | random | to_uuid }}"

- name: توليد UUID الخدمة
set_fact:
service_uuid: "Service_{{ uuid[0:8] }}"

# 6. إنشاء حساب في نظام الفوترة
- name: إنشاء حساب في OCS/CGRateS
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
headers:
Content-Type: "application/json"
body:
{
"method": "ApierV2.SetAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionPlanIds": [],
"ActionPlansOverwrite": true,
"ExtraOptions": {
"AllowNegative": false,
"Disabled": false
},
"ReloadScheduler": true
}]
}
status_code: 200
register: ocs_response

- name: التحقق من إنشاء حساب OCS
assert:
that:
- ocs_response.status == 200
- ocs_response.json.result == "OK"

# 7. إضافة رصيد أولي
- name: إضافة رصيد مالي 0
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"BalanceType": "*monetary",
"Categories": "*any",
"Balance": {
"ID": "رصيد أولي",
"Value": 0,
"ExpiryTime": "+4320h",
"Weight": 1,
"Blocker": true
}
}]
}
status_code: 200
register: balance_response

# 8. إنشاء سجل الخدمة في CRM
- name: الحصول على التاريخ والوقت الحالي بتنسيق ISO 8601
command: date --utc +%Y-%m-%dT%H:%M:%S%z
register: current_date_time

- name: إضافة الخدمة عبر API
uri:
url: "http://localhost:5000/crm/service/"
method: PUT
body_format: json
headers:
Content-Type: "application/json"
Authorization: "Bearer {{ access_token }}"
body:
{
"customer_id": "{{ customer_id }}",
"product_id": "{{ product_id }}",
"service_name": "{{ package_name }} - {{ service_uuid }}",
"service_type": "generic",
"service_uuid": "{{ service_uuid }}",
"service_billed": true,
"service_taxable": true,
"service_provisioned_date": "{{ current_date_time.stdout }}",
"service_status": "Active",
"wholesale_cost": "{{ api_response_product.json.wholesale_cost | float }}",
"retail_cost": "{{ monthly_cost | float }}"
}
status_code: 200
register: service_creation_response

# 9. إضافة معاملة تكلفة الإعداد
- name: إضافة معاملة تكلفة الإعداد عبر API
uri:
url: "http://localhost:5000/crm/transaction/"
method: PUT
headers:
Content-Type: "application/json"
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"customer_id": {{ customer_id | int }},
"service_id": {{ service_creation_response.json.service_id | int }},
"title": "{{ package_name }} - تكاليف الإعداد",
"description": "تكاليف الإعداد لـ {{ package_comment }}",
"invoice_id": null,
"retail_cost": "{{ setup_cost | float }}"
}
return_content: yes
register: transaction_response

# 10. تضمين المهام بعد التوفير
- include_tasks: post_provisioning_tasks.yaml

rescue:

# قسم التراجع/التنظيف
- name: طباعة جميع المتغيرات لأغراض التصحيح
debug:
var: hostvars[inventory_hostname]

- name: إزالة الحساب في OCS
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV2.RemoveAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ReloadScheduler": true
}]
}
status_code: 200
ignore_errors: True
when: service_uuid is defined

- name: حذف الخدمة من CRM إذا تم إنشاؤها
uri:
url: "http://localhost:5000/crm/service/service_id/{{ service_creation_response.json.service_id }}"
method: DELETE
headers:
Authorization: "Bearer {{ access_token }}"
status_code: 200
ignore_errors: True
when: service_creation_response is defined

- name: الفشل إذا لم يكن إلغاء التوفير مقصودًا
assert:
that:
- action == "deprovision"

نمط التعبئة/إعادة الشحن​

يستخدم لإضافة أرصدة أو بيانات أو وقت إلى الخدمات الموجودة.

- name: Playbook تعبئة الخدمة
hosts: localhost
gather_facts: no
become: False

tasks:
- name: تضمين متغيرات crm_config
ansible.builtin.include_vars:
file: "../../crm_config.yaml"
name: crm_config

# 1. الحصول على معلومات الخدمة
- name: الحصول على معلومات الخدمة من API CRM
uri:
url: "http://localhost:5000/crm/service/service_id/{{ service_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_service

# 2. الحصول على معلومات المنتج (ما يجب تعبئته)
- name: الحصول على معلومات المنتج من API CRM
uri:
url: "http://localhost:5000/crm/product/product_id/{{ product_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_product

# 3. استخراج تفاصيل الخدمة
- name: تعيين حقائق الخدمة
set_fact:
service_uuid: "{{ api_response_service.json.service_uuid }}"
customer_id: "{{ api_response_service.json.customer_id }}"
package_name: "{{ api_response_product.json.product_name }}"
topup_value: "{{ api_response_product.json.retail_cost }}"

# 4. تنفيذ الإجراء في نظ��م الفوترة (تعبئة مجانية)
- name: تنفيذ الإجراء لإضافة أرصدة
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "APIerSv1.ExecuteAction",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionsId": "Action_Topup_Standard"
}]
}
status_code: 200
register: action_response

- name: التحقق من تنفيذ الإجراء بنجاح
assert:
that:
- action_response.status == 200
- action_response.json.result == "OK"

# 5. إعادة تعيين أي حدود تم تشغيلها
- name: إعادة تعيين ActionTriggers
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "APIerSv1.ResetAccountActionTriggers",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"Executed": false
}]
}
status_code: 200

# 6. تحديث تواريخ الخدمة
- name: حساب تاريخ انتهاء جديد
command: "date --utc +%Y-%m-%dT%H:%M:%S%z -d '+30 days'"
register: new_expiry_date

- name: تحديث الخدمة مع انتهاء جديد
uri:
url: "http://localhost:5000/crm/service/{{ service_id }}"
method: PATCH
headers:
Authorization: "Bearer {{ access_token }}"
Content-Type: "application/json"
body_format: json
body:
{
"service_deactivate_date": "{{ new_expiry_date.stdout }}",
"service_status": "Active"
}

# 7. اختياري: إرسال إشعار
- name: إرسال إشعار SMS
uri:
url: "http://sms-gateway/api/send"
method: POST
body_format: json
body:
{
"source": "اسم الشركة",
"destination": "{{ customer_phone }}",
"message": "تم تعبئة خدمتك. انتهاء جديد: {{ new_expiry_date.stdout }}"
}
status_code: 201
ignore_errors: True

نمط توفير CPE​

يستخدم لتكوين معدات العملاء (أجهزة التوجيه، المودمات، ONTs).

- name: Playbook توفير CPE
hosts: localhost
gather_facts: no
become: False

tasks:
- name: تضمين متغيرات crm_config
ansible.builtin.include_vars:
file: "../../crm_config.yaml"
name: crm_config

# 1. الحصول على عنصر المخزون لـ CPE
- name: تعيين معرف مخزون CPE من hostvars
set_fact:
cpe_inventory_id: "{{ hostvars[inventory_hostname]['WiFi Router CPE'] | int }}"
when: "'WiFi Router CPE' in hostvars[inventory_hostname]"

# 2. الحصول على تفاصيل CPE من المخزون
- name: الحصول على بيانات المخزون لـ CPE
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ cpe_inventory_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_cpe

# 3. الحصول على معلومات موقع العميل
- name: الحصول على معلومات الموقع من API
uri:
url: "{{ crm_config.crm.base_url }}/crm/site/customer_id/{{ customer_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_site

# 4. تحديث مخزون CPE بالموقع
- name: Patch عنصر مخزون CPE بالموقع
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ cpe_inventory_id }}"
method: PATCH
body_format: json
headers:
Authorization: "Bearer {{ access_token }}"
body:
{
"address_line_1": "{{ api_response_site.json.0.address_line_1 }}",
"city": "{{ api_response_site.json.0.city }}",
"state": "{{ api_response_site.json.0.state }}",
"latitude": "{{ api_response_site.json.0.latitude }}",
"longitude": "{{ api_response_site.json.0.longitude }}"
}
status_code: 200

# 5. توليد بيانات الاعتماد
- name: تعيين اسم مضيف CPE
set_fact:
cpe_hostname: "CPE_{{ cpe_inventory_id }}"
cpe_username: "admin_{{ cpe_inventory_id }}"

- name: توليد كلمة مرور عشوائية
set_fact:
cpe_password: "{{ lookup('pipe', 'cat /dev/urandom | tr -dc a-zA-Z0-9 | head -c 16') }}"

# 6. توليد بيانات اعتماد WiFi
- name: تعيين SSID WiFi
set_fact:
wifi_ssid: "Network_{{ cpe_inventory_id }}"

- name: توليد كلمة مرور WiFi
set_fact:
word_list:
- apple
- cloud
- river
- mountain
- ocean

- name: إنشاء WiFi PSK
set_fact:
random_word: "{{ word_list | random }}"
random_number: "{{ 99999 | random(start=10000) }}"

- name: دمج WiFi PSK
set_fact:
wifi_psk: "{{ random_word }}{{ random_number }}"

# 7. توليد ملف التكوين
- name: تعيين اسم ملف التكوين
set_fact:
config_name: "{{ cpe_hostname }}_{{ lookup('pipe', 'date +%Y%m%d%H%M%S') }}.cfg"
config_dest: "/tmp/{{ cpe_hostname }}_{{ lookup('pipe', 'date +%Y%m%d%H%M%S') }}.cfg"

- name: إنشاء التكوين من القالب
template:
src: "templates/cpe_router_config.j2"
dest: "{{ config_dest }}"

# 8. قراءة التكوين الناتج
- name: قراءة ملف التكوين
ansible.builtin.slurp:
src: "{{ config_dest }}"
register: config_content

# 9. تحديث المخزون بمعلومات التوفير
- name: Patch مخزون CPE بالتكوين
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ cpe_inventory_id }}"
method: PATCH
body_format: json
headers:
Authorization: "Bearer {{ access_token }}"
body:
{
"itemtext3": "{{ wifi_ssid }}",
"itemtext4": "{{ wifi_psk }}",
"management_url": "{{ cpe_hostname }}",
"management_username": "{{ cpe_username }}",
"management_password": "{{ cpe_password }}",
"config_content": "{{ config_content.content | b64decode }}",
"inventory_notes": "تم التوفير: {{ lookup('pipe', 'date +%Y-%m-%d') }}"
}
status_code: 200

# 10. إرسال التكوين إلى فريق الدعم
- name: إرسال التكوين عبر البريد الإلكتروني إلى الدعم
uri:
url: "https://api.mailjet.com/v3.1/send"
method: POST
body_format: json
headers:
Content-Type: "application/json"
body:
{
"Messages": [{
"From": {
"Email": "provisioning@example.com",
"Name": "نظام التوفير"
},
"To": [{
"Email": "support@example.com",
"Name": "فريق الدعم"
}],
"Subject": "تكوين CPE - {{ cpe_hostname }}",
"Attachments": [{
"ContentType": "text/plain",
"Filename": "{{ config_name }}",
"Base64Content": "{{ config_content.content }}"
}]
}]
}
user: "{{ mailjet_api_key }}"
password: "{{ mailjet_api_secret }}"
force_basic_auth: true
status_code: 200

نمط التجديد التلقائي​

تكوين رسوم متكررة تلقائية أو تجديدات باستخدام خطط العمل في CGRateS.

# جزء من Playbook التعبئة الذي يقوم بإعداد التجديد التلقائي

# 1. تطبيع معلمة auto_renew
- name: تطبيع auto_renew إلى قيمة منطقية
set_fact:
auto_renew_bool: "{{ (auto_renew | string | lower) in ['true', '1', 'yes'] }}"

# 2. إنشاء إجراء للتجديد التلقائي
- name: إنشاء إجراء للتجديد التلقائي
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.SetActions",
"params": [{
"ActionsId": "Action_AutoTopup_{{ service_uuid }}_{{ product_id }}",
"Overwrite": true,
"Actions": [
{
"Identifier": "*http_post",
"ExtraParameters": "{{ crm_config.crm.base_url }}/crm/provision/simple_provision_addon/service_id/{{ service_id }}/product_id/{{ product_id }}"
},
{
"Identifier": "*cdrlog",
"BalanceType": "*generic",
"ExtraParameters": "{\"Category\":\"^activation\",\"Destination\":\"Auto Renewal\"}"
}
]
}]
}
status_code: 200
register: action_response
when: auto_renew_bool

# 3. إنشاء خطة العمل الشهرية
- name: إنشاء خطة العمل للتجديد الشهري
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.SetActionPlan",
"params": [{
"Id": "ActionPlan_Monthly_{{ service_uuid }}_{{ product_id }}",
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"ActionPlan": [{
"ActionsId": "Action_AutoTopup_{{ service_uuid }}_{{ product_id }}",
"Years": "*any",
"Months": "*any",
"MonthDays": "*any",
"WeekDays": "*any",
"Time": "*monthly",
"StartTime": "*now",
"Weight": 10
}],
"Overwrite": true,
"ReloadScheduler": true
}]
}
status_code: 200
when: auto_renew_bool

# 4. تعيين خطة العمل للحساب
- name: تعيين خطة العمل للحساب
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV2.SetAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionPlanIds": ["ActionPlan_Monthly_{{ service_uuid }}_{{ product_id }}"],
"ActionPlansOverwrite": true,
"ReloadScheduler": true
}]
}
status_code: 200
when: auto_renew_bool

# 5. إزالة خطة العمل إذا تم تعطيل التجديد التلقائي
- name: إزالة خطة العمل من الحساب
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.RemoveActionPlan",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Id": "ActionPlan_Monthly_{{ service_uuid }}_{{ product_id }}"
}]
}
status_code: 200
ignore_errors: true
when: not auto_renew_bool

المهام القابلة لإعادة الاستخدام​

المهام القابلة لإعادة الاستخدام هي Playbooks صغيرة ومستقلة يمكن تضمينها بواسطة عدة Plays. إنها تعزز إعادة استخدام الكود والاتساق.

مهمة البريد الإلكتروني الترحيبي​

task_welcome_email.yaml - ترسل بريدًا إلكترونيًا ترحيبيًا للعملاء الجدد.

# تتوقع هذه المهمة أن يتم تعيين هذه المتغيرات بواسطة Play الأب:
# - api_response_customer (تفاصيل العميل)
# - package_name (اسم المنتج)
# - monthly_cost (التكلفة المتكررة)
# - setup_cost (التكلفة لمرة واحدة)

- name: تعيين تكوين البريد الإلكتروني
set_fact:
mailjet_api_key: "{{ lookup('env', 'MAILJET_API_KEY') }}"
mailjet_api_secret: "{{ lookup('env', 'MAILJET_SECRET') }}"
email_from: "noreply@example.com"
recipients: []

- name: تعيين موضوع البريد الإلكتروني واسم المرسل
set_fact:
email_subject: "مرحبًا بك في خدمتنا!"
email_from_name: "فريق خدمة العملاء"

- name: إعداد قائمة المستلمين من جهات اتصال العميل
loop: "{{ api_response_customer.json.contacts }}"
set_fact:
recipients: "{{ recipients + [{'Email': item.contact_email, 'Name': item.contact_firstname ~ ' ' ~ item.contact_lastname}] }}"

- name: الحصول على اسم أول جهة اتصال
set_fact:
first_contact: "{{ api_response_customer.json.contacts[0].contact_firstname }}"

- name: إرسال بريد إلكتروني ترحيبي
uri:
url: "https://api.mailjet.com/v3.1/send"
method: POST
body_format: json
headers:
Content-Type: "application/json"
body:
{
"Messages": [{
"From": {
"Email": "{{ email_from }}",
"Name": "{{ email_from_name }}"
},
"To": "{{ recipients }}",
"Subject": "{{ email_subject }}",
"TextPart": "عزيزي {{ first_contact }}, مرحبًا! خدمتك جاهزة.",
"HTMLPart": "عزيزي {{ first_contact }},<br/><h3>مرحبًا!</h3><br/>خدمة {{ package_name }} الخاصة بك نشطة الآن.<br/>التكلفة الشهرية: ${{ monthly_cost }}<br/>رسوم الإعداد: ${{ setup_cost }}<br/>إذا كانت لديك أي استفسارات، اتصل بـ support@example.com"
}]
}
user: "{{ mailjet_api_key }}"
password: "{{ mailjet_api_secret }}"
force_basic_auth: true
status_code: 200
register: email_response

المهام بعد التوفير​

post_provisioning_tasks.yaml - عمليات التنظيف والإشعارات القياسية التي تُنفذ بعد كل توفير.

# يتم تضمين هذا الملف في نهاية معظم Playbooks للتوفير
# يتعامل مع العمليات الشائعة بعد التوفير

- include_tasks: task_notify_ocs.yaml

قد تحتوي task_notify_ocs.yaml على:

- name: إعلام OCS بإكمال التوفير
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "APIerSv1.ReloadCache",
"params": [{
"ArgsCache": "*all"
}]
}
status_code: 200
ignore_errors: true

العمليات الشائعة​

العمل مع المخزون​

استرجاع تفاصيل المخزون:

- name: الحصول على معرف مخزون بطاقة SIM
set_fact:
sim_inventory_id: "{{ hostvars[inventory_hostname]['SIM Card'] | int }}"
when: "'SIM Card' in hostvars[inventory_hostname]"

- name: الحصول على تفاصيل بطاقة SIM
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ sim_inventory_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: sim_response

- name: استخراج تفاصيل SIM
set_fact:
iccid: "{{ sim_response.json.iccid }}"
imsi: "{{ sim_response.json.imsi }}"
ki: "{{ sim_response.json.ki }}"

تعيين المخزون للعميل:

- name: تعيين SIM للعميل
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ sim_inventory_id }}"
method: PATCH
headers:
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"customer_id": {{ customer_id }},
"service_id": {{ service_id }},
"item_state": "Assigned"
}
status_code: 200

عمليات التاريخ والوقت​

الحصول على التاريخ/الوقت الحالي:

- name: الحصول على التاريخ والوقت الحالي بتنسيق ISO 8601
command: date --utc +%Y-%m-%dT%H:%M:%S%z
register: current_date_time

- name: الحصول على تاريخ اليوم فقط
set_fact:
today: "{{ lookup('pipe', 'date +%Y-%m-%d') }}"

حساب التواريخ المستقبلية:

- name: حساب تاريخ انتهاء بعد 30 يومًا
command: "date --utc +%Y-%m-%dT%H:%M:%S%z -d '+30 days'"
register: expiry_date

- name: حساب التاريخ بعد 90 يومًا
command: "date --utc +%Y-%m-%d -d '+{{ days }} days'"
register: future_date
vars:
days: 90

توليد قيم عشوائية​

UUIDs والمعرفات:

- name: توليد UUID
set_fact:
uuid: "{{ 99999999 | random | to_uuid }}"

- name: توليد معرف الخدمة
set_fact:
service_uuid: "SVC_{{ uuid[0:8] }}"

كلمات مرور عشوائية:

- name: توليد كلمة مرور آمنة
set_fact:
password: "{{ lookup('pipe', 'cat /dev/urandom | tr -dc a-zA-Z0-9 | head -c 16') }}"

عبارات مرور سهلة التذكر:

- name: تعيين قائمة الكلمات
set_fact:
words:
- alpha
- bravo
- charlie
- delta
- echo

- name: توليد عبارة مرور
set_fact:
word: "{{ words | random }}"
number: "{{ 99999 | random(start=10000) }}"

- name: دمجها في عبارة مرور
set_fact:
passphrase: "{{ word }}{{ number }}"

العمل مع CGRateS/OCS​

إنشاء الحسابات:

- name: إنشاء حساب فوترة
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV2.SetAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionPlanIds": [],
"ActionPlansOverwrite": true,
"ExtraOptions": {
"AllowNegative": false,
"Disabled": false
},
"ReloadScheduler": true
}]
}
status_code: 200
register: account_response

إضافة الأرصدة:

- name: إضافة رصيد البيانات
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"BalanceType": "*data",
"Categories": "*any",
"Balance": {
"ID": "حزمة البيانات",
"Value": 10737418240,
"ExpiryTime": "+720h",
"Weight": 10
}
}]
}
status_code: 200

تنفيذ الإجراءات:

- name: تنفيذ إجراء الشحن
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "APIerSv1.ExecuteAction",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionsId": "Action_Standard_Charge"
}]
}
status_code: 200

الحصول على معلومات الحساب:

- name: الحصول على تفاصيل الحساب
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV2.GetAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}"
}]
}
status_code: 200
register: account_info

العمل مع ملفات تعريف السمات:

- name: الحصول على ملف تعريف السمة
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "APIerSv1.GetAttributeProfile",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"ID": "ATTR_{{ service_uuid }}"
}]
}
return_content: yes
status_code: 200
register: attr_response
ignore_errors: true

- name: استخراج قيمة السمة
set_fact:
phone_number: "{{ attr_response.json.result.Attributes | json_query(\"[?Path=='*req.PhoneNumber'].Value[0].Rules\") | first }}"
when: attr_response is defined

المنطق الشرطي​

التحقق مما إذا كانت المتغيرات موجودة:

- name: استخدام القيمة المخصصة أو الافتراضية
set_fact:
monthly_cost: "{{ custom_cost | default(50.00) }}"

- name: تشغيل فقط إذا تم تعريف المتغير
debug:
msg: "معرف الخدمة هو {{ service_uuid }}"
when: service_uuid is defined

الشروط المنطقية:

- name: توفير المعدات
include_tasks: configure_cpe.yaml
when: provision_cpe | default(false) | bool

- name: تخطي إذا كان إلغاء التوفير
assert:
that:
- action != "deprovision"
when: action is defined

شروط متعددة:

- name: مهمة شرطية معقدة
uri:
url: "{{ endpoint }}"
method: POST
when:
- service_uuid is defined
- customer_id is defined
- action != "deprovision"
- enable_feature | default(true) | bool

الحلقات والتكرار​

حلقات بسيطة:

- name: إنشاء أرصدة متعددة
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Account": "{{ service_uuid }}",
"BalanceType": "{{ item.type }}",
"Balance": {
"Value": "{{ item.value }}"
}
}]
}
loop:
- { type: "*voice", value: 3600 }
- { type: "*data", value: 10737418240 }
- { type: "*sms", value: 100 }

التكرار على استجابات API:

- name: الحصول على جميع مواقع العملاء
uri:
url: "{{ crm_config.crm.base_url }}/crm/site/customer_id/{{ customer_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
register: sites_response

- name: تكوين المعدات في كل موقع
debug:
msg: "تكوين الموقع في {{ item.address_line_1 }}"
loop: "{{ sites_response.json }}"

معالجة الأخطاء​

استخدام ignore_errors:

- name: إشعا�� SMS اختياري
uri:
url: "http://sms-gateway/send"
method: POST
body: {...}
ignore_errors: true

التحقق من صحة الاستجابة:

- name: التحقق من استجابة API
assert:
that:
- response.status == 200
- response.json.result == "OK"
fail_msg: "فشلت مكالمة API: {{ response.json }}"

معالجة الأخطاء الشرطية:

- name: محاولة الحصول على الخدمة الحالية
uri:
url: "{{ crm_config.crm.base_url }}/crm/service/service_uuid/{{ service_uuid }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
register: service_lookup
failed_when: false

- name: إنشاء الخدمة إذا لم تكن موجودة
uri:
url: "{{ crm_config.crm.base_url }}/crm/service/"
method: PUT
body: {...}
when: service_lookup.status == 404

أفضل الممارسات​

تسمية المتغيرات​

استخدم أسماء وصفية ومتسقة:

# جيد
service_uuid: "SVC_12345"
customer_name: "جون سميث"
monthly_cost: 49.99

# سيء
svc: "SVC_12345"
name: "جون سميث"
cost: 49.99

قم ببادئة المتغيرات حسب المصدر:

api_response_customer: {...}
api_response_product: {...}
cgr_account_info: {...}

التصحيح​

طباعة المتغيرات لأغراض استكشاف الأخطاء:

- name: طباعة جميع المتغيرات
debug:
var: hostvars[inventory_hostname]

- name: طباعة متغير محدد
debug:
msg: "معرف الخدمة: {{ service_uuid }}"

- name: طباعة استجابة API
debug:
var: api_response_product.json

التحقق​

تحقق دائمًا من استجابات API الحاسمة:

- name: إنشاء حساب
uri:
url: "{{ billing_endpoint }}"
method: POST
body: {...}
register: response

- name: التحقق من إنشاء الحساب
assert:
that:
- response.status == 200
- response.json.result == "OK"
fail_msg: "فشل إنشاء الحساب: {{ response.json }}"

عدم التكرار​

صمم المهام لتكون قابلة للتشغيل بأمان مرة أخرى:

# تحقق مما إذا كان المورد موجودًا أولاً
- name: التحقق مما إذا كان الحساب موجودًا
uri:
url: "{{ ocs_endpoint }}/get_account"
method: POST
body: {"Account": "{{ service_uuid }}"}
register: account_check
failed_when: false

# إنشاء فقط إذا لم يكن موجودًا
- name: إنشاء حساب
uri:
url: "{{ ocs_endpoint }}/create_account"
method: POST
body: {...}
when: account_check.status == 404

الأمان​

لا تقم بتشفير بيانات الاعتماد:

# سيء
mailjet_api_key: "abc123def456"

# جيد - استخدم المتغيرات البيئية
mailjet_api_key: "{{ lookup('env', 'MAILJET_API_KEY') }}"

# جيد - استخدم ملف التكوين
mailjet_api_key: "{{ crm_config.email.api_key }}"

استخدم دائمًا HTTPS والمصادقة:

- name: استدعاء API خارجي
uri:
url: "https://api.example.com/endpoint"
method: POST
headers:
Authorization: "Bearer {{ access_token }}"
validate_certs: yes

الوثائق​

وثق المنطق المعقد:

# حساب الرسوم النسبية لشهر جزئي
# إذا قام العميل بالتسجيل في اليوم الخامس عشر والفوترة في اليوم الأول،
# قم بفرض 50% من التكلفة الشهرية للأيام المتبقية
- name: حساب الأيام حتى نهاية الشهر
command: "date -d 'last day of this month' +%d"
register: days_in_month

- name: الحصول على اليوم الحالي
command: "date +%d"
register: current_day

- name: حساب المبلغ النسبي
set_fact:
days_remaining: "{{ (days_in_month.stdout | int) - (current_day.stdout | int) }}"
pro_rata_cost: "{{ (monthly_cost | float) * (days_remaining | float) / (days_in_month.stdout | float) }}"

اختبار Playbooks​

نهج الاختبار​

  1. اختبار جاف أولاً: اختبر مع الأنظمة غير الإنتاجية
  2. تحقق من المتغيرات: استخدم مهام التصحيح للتأكد من وجود جميع المتغيرات المطلوبة
  3. تحقق من الاستجابات: تحقق من استجابات API قبل المتابعة
  4. اختبار التراجع: افشل المهام عمدًا للتحقق من عمل كتل الإنقاذ
  5. اختبار إلغاء التوفير: اختبر باستخدام action: "deprovision" للتحقق من التنظيف

مثال على Playbook اختبار:

- name: اختبار توفير الخدمة
hosts: localhost
gather_facts: no

tasks:
- name: التحقق من المتغيرات المطلوبة
assert:
that:
- product_id is defined
- customer_id is defined
- access_token is defined
fail_msg: "المتغيرات المطلوبة مفقودة"

- name: اختبار الاتصال بـ API
uri:
url: "http://localhost:5000/crm/health"
method: GET
register: health_check

- name: التحقق من فحص الصحة
assert:
that:
- health_check.status == 200

الأخطاء الشائعة​

التحويلات المفقودة للنوع:

# خطأ - قد تكون سلسلة
customer_id: "{{ customer_id }}"

# صحيح - تأكد من أنها عدد صحيح
customer_id: {{ customer_id | int }}

عدم التعامل مع المتغيرات غير المعرفة:

# خطأ - يفشل إذا لم يكن معرفًا
service_uuid: "{{ service_uuid }}"

# صحيح - قدم افتراضي
service_uuid: "{{ service_uuid | default('') }}"

نسيان التحقق:

# خطأ - لا يتحقق من الاستجابة
- name: إنشاء حساب
uri: ...
register: response

# صحيح - تحقق من الاستجابة
- name: إنشاء حساب
uri: ...
register: response

- name: التحقق من الإنشاء
assert:
that:
- response.json.result == "OK"

سير عمل التوفير​

بشكل عام، سيعمل موظفو Omnitouch مع العميل على:

  1. تحديد متطلبات المنتج
  2. تطوير Playbooks Ansible اللازمة لأتمتة عملية التوفير
  3. اختبار Playbooks في بيئة staging
  4. نشرها في الإنتاج

هذا يضمن أن كل خدمة يتم نشرها بشكل متسق وموثوق، مما يقلل من مخاطر الأخطاء ويضمن إكمال جميع الخطوات الضرورية بالترتيب الصحيح.

متغيرات Ansible​

تشمل المتغيرات الممررة إلى Playbooks في Ansible:

متغيرات المنتج
مشتقة من تكوينات منتج OmniCRM وتحدد كيفية إعداد الخدمة.

متغيرات المخزون
محددة من المخزون، تشمل عناصر مثل المودمات، بطاقات SIM، كتل عناوين IP، أو أرقام الهواتف المطلوبة للتوفير.

المتغيرات النظامية
تضاف تلقائيًا بواسطة OmniCRM:

  • product_id - المنتج الذي يتم توفيره
  • customer_id - العميل الذي يتلقى الخدمة
  • service_id - الخدمة التي يتم تعديلها (للتعبئة/التغييرات)
  • access_token - JWT لمصادقة API

إلغاء التوفير​

عندما لم تعد الخدمة مطلوبة، يتم أيضًا استخدام Playbooks Ansible لإلغاء توفير الخدمة باستخدام نمط كتلة rescue. هذا:

  • يزيل أي تكوينات
  • يحرر المخزون مرة أخرى إلى المجموعة
  • يحذف حسابات الفوترة
  • يضمن الحفاظ على نظافة النظام

طرق إلغاء التوفير​

هناك طريقتان رئيسيتان يتم تحفيز إلغاء التوفير:

1. التراجع التلقائي (فشل التوفير)
عندما يفشل أي مهمة في كتلة التوفير الرئيسية، يتم تنفيذ قسم الإنقاذ تلقائيًا لتنظيف التغييرات الجزئية.

2. إلغاء التوفير اليدوي
تعيين action: "deprovision" عند تشغيل Playbook يحفز كتلة الإنقاذ عمدًا لإزالة خدمة.

نمط إلغاء التوفير​

تتبع جميع Playbooks في OmniCRM هذا الهيكل للتنظيف الآمن:

- name: Playbook توفير الخدمة
hosts: localhost
gather_facts: no
become: False

tasks:
- name: الكتلة الرئيسية
block:
# جميع مهام التوفير تذهب هنا
- name: إنشاء حساب OCS
uri: ...

- name: إنشاء سجل الخدمة
uri: ...
register: service_creation_response

- name: إضافة معاملة
uri: ...

rescue:
# مهام إلغاء التوفير/التراجع تذهب هنا
- name: طباعة جميع المتغيرات لأغراض التصحيح
debug:
var: hostvars[inventory_hostname]

# تنفيذ مهام التنظيف بترتيب عكسي
- name: إزالة الحساب في OCS
uri: ...

- name: حذف الخدمة من CRM
uri: ...

- name: الفشل إذا لم يكن إلغاء التوفير مقصودًا
assert:
that:
- action == "deprovision"

مثال كامل لإلغاء التوفير​

إليك مثال كامل من play_simple_service.yaml:

rescue:
# 1. معلومات التصحيح
- name: طباعة جميع المتغيرات للتراجع/التنظيف
debug:
var: hostvars[inventory_hostname]

- name: الحصول على تاريخ اليوم
set_fact:
today: "{{ lookup('pipe', 'date +%Y-%m-%d') }}"

# 2. الحصول على معلومات الخدمة إذا كانت متاحة
- name: محاولة الحصول على معلومات الخدمة من API CRM لإلغاء التوفير
uri:
url: "http://localhost:5000/crm/service/service_id/{{ service_creation_response.json.service_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_service
ignore_errors: True
when: service_creation_response is defined and service_creation_response.json is defined and service_creation_response.json.service_id is defined

- name: طباعة api_response_service
debug:
var: api_response_service
when: api_response_service is defined

# 3. تعيين service_uuid للتنظيف
- name: تعيين حقائق service_uuid لإلغاء التوفير
set_fact:
service_uuid: "{{ api_response_service.json.service_uuid }}"
when:
- service_uuid is not defined
- api_response_service is defined
- api_response_service.json is defined

# 4. إزالة حساب OCS
- name: إزالة الحساب في OCS
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
headers:
Content-Type: "application/json"
Authorization: "Bearer {{ access_token }}"
body:
{
"method": "ApierV2.RemoveAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ReloadScheduler": true
}]
}
status_code: 200
register: response
ignore_errors: True
when: service_uuid is defined

- name: طباعة الاستجابة من إزالة حساب OCS
debug:
var: response
when: response is defined

# 5. حذف سجل الخدمة من CRM
- name: حذف الخدمة من CRM إذا تم إنشاؤها
uri:
url: "http://localhost:5000/crm/service/service_id/{{ service_creation_response.json.service_id }}"
method: DELETE
headers:
Authorization: "Bearer {{ access_token }}"
status_code: 200
register: delete_service_response
ignore_errors: True
when: service_creation_response is defined and service_creation_response.json is defined and service_creation_response.json.service_id is defined

- name: طباعة استجابة حذف الخدمة
debug:
var: delete_service_response
when: delete_service_response is defined

# 6. تحديد ما إذا كان هذا مقصودًا أم فشلًا
- name: تعيين الحالة إلى "نجاح" إذا كان إلغاء التوفير يدويًا / الفشل إذا كان التوفير فشلًا
assert:
that:
- action == "deprovision"
fail_msg: "فشل ��لتوفير وتم التراجع عنه"
success_msg: "تم إلغاء توفير الخدمة بنجاح"

مفاهيم إلغاء التوفير الرئيسية​

ignore_errors: True
تستخدم معظم مهام التنظيف ignore_errors: True لأن الموارد قد لا توجد (على سبيل المثال، إذا فشل التوفير قبل إنشائها).

التنفيذ الشرطي
استخدم جمل when لتنظيف الموارد فقط التي تم إنشاؤها:

when: service_creation_response is defined

ترتيب عكسي
يحدث التنظيف بترتيب عكسي لإنشاء:

  1. حذف الموارد التابعة أولاً (المعاملات، تعيينات المخزون)
  2. حذف سجل الخدمة
  3. حذف حساب OCS/الفوترة
  4. تحرير أي موارد محتجزة

التحقق النهائي
التحقق النهائي يميز بين:

  • إلغاء التوفير المقصود (action == "deprovision") → نجاح
  • فشل التوفير (لم يتم تعيين الإجراء) → فشل

إلغاء التوفير لموارد مختلفة​

حسابات OCS/CGRateS:

- name: إزالة الحساب في OCS
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV2.RemoveAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ReloadScheduler": true
}]
}
status_code: 200
ignore_errors: True
when: service_uuid is defined

سجلات الخدمة في CRM:

- name: حذف الخدمة من CRM
uri:
url: "http://localhost:5000/crm/service/service_id/{{ service_id }}"
method: DELETE
headers:
Authorization: "Bearer {{ access_token }}"
status_code: 200
ignore_errors: True
when: service_id is defined

عناصر المخزون (إعادتها إلى المجموعة):

- name: تحرير بطاقة SIM مرة أخرى إلى مجموعة المخزون
uri:
url: "http://localhost:5000/crm/inventory/inventory_id/{{ sim_inventory_id }}"
method: PATCH
headers:
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"customer_id": null,
"service_id": null,
"item_state": "Available"
}
status_code: 200
ignore_errors: True
when: sim_inventory_id is defined

خطط العمل (الرسوم المتكررة):

- name: إزالة خطة العمل من الحساب
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.RemoveActionPlan",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Id": "ActionPlan_Monthly_{{ service_uuid }}_{{ product_id }}"
}]
}
status_code: 200
ignore_errors: True

تفويضات الدفع:

- name: تحرير تفويض الدفع
uri:
url: "http://localhost:5000/crm/payments/release/{{ authorization_id }}"
method: POST
headers:
Authorization: "Bearer {{ access_token }}"
body_format: json
body:
{
"metadata": {
"release_reason": "فشل التوفير"
}
}
return_content: yes
ignore_errors: True
when: authorization_id is defined

سير عمل إلغاء التوفير اليدوي​

لإلغاء توفير خدمة يدويًا:

  1. تحديد الخدمة المراد إلغاء توفيرها
  2. تشغيل Playbook التوفير الأصلي مع action: "deprovision"
  3. تدخل Playbook في كتلة الإنقاذ على الفور
  4. تنفيذ جميع مهام التنظيف
  5. يتم إزالة الخدمة بشكل نظيف

مثال على استدعاء API:

POST /crm/provision/run_playbook
{
"product_id": 123,
"customer_id": 456,
"service_id": 789,
"action": "deprovision"
}

سيناريوهات التنظيف الجزئي​

السيناريو 1: تم إنشاء حساب OCS، فشل إنشاء الخدمة

  • يوجد حساب OCS
  • لا يوجد سجل خدمة
  • يقوم قسم الإنقاذ بإزالة حساب OCS
  • لا توجد خدمة لحذفها (تم تخطيها بأمان مع شرط when)

السيناريو 2: تم إنشاء الخدمة، فشلت المعاملة

  • يوجد حساب OCS
  • يوجد سجل خدمة
  • لا توجد معاملة
  • يقوم قسم الإنقاذ بإزالة كل من حساب OCS والخدمة
  • لا توجد معاملة لإبطالها (لم يتم إنشاؤها أبدًا)

السيناريو 3: توفير كامل، فشل التقاط الدفع

  • يوجد حساب OCS
  • يوجد سجل خدمة
  • تم تفويض الدفع ولكن لم يتم التقاطه
  • يقوم قسم الإنقاذ:
    • بإطلاق تفويض الدفع (لم يتم تحميل العميل)
    • إزالة سجل الخدمة
    • إزالة حساب OCS

أفضل الممارسات لإلغاء التوفير​

1. استخدم ignore_errors بشكل واسع
يجب أن يكون التنظيف متساهلاً. لا تفشل إذا لم يكن المورد موجودًا.

2. تحقق من وجود المورد باستخدام جمل when
حاول التنظيف فقط إذا تم إنشاء المورد:

when: service_creation_response is defined

3. طباعة معلومات التصحيح
قم دائمًا بتضمين مهام التصحيح للمساعدة في استكشاف الأخطاء في التوفير الفاشل:

- name: طباعة جميع المتغيرات لأغراض التصحيح
debug:
var: hostvars[inventory_hostname]

4. تنظيف بترتيب عكسي حسب الاعتماد
احذف الأطفال قبل الآباء:

  • المعاملات قبل الخدمات
  • الخدمات قبل حسابات OCS
  • تعيينات المخزون قبل تحرير المخزون

5. التعامل مع تفويضات الدفع
قم دائمًا بإطلاق تفويضات الدفع في كتل الإنقاذ لتجنب تحميل العملاء مقابل التوفير الفاشل.

6. إعادة تحميل المجدولين بعد التنظيف
عند إزالة موارد OCS، قم بتضمين ReloadScheduler: true لضمان تحديث CGRateS على الفور.

إلغاء التوفير مقابل الحذف​

إلغاء التوفير (عبر كتلة الإنقاذ):

  • يزيل الخدمة من جميع الأنظمة
  • يحرر المخزون
  • يلغي الرسوم المتكررة
  • يترك أثر تدقيق
  • نهج موصى به

الحذف المباشر (عبر API):

  • يحذف فقط سجل CRM
  • لا ينظف حساب OCS
  • لا يحرر المخزون
  • يمكن أن يترك موارد يتيمة
  • غير موصى به للإنتاج

التراجع ومعالجة الأخطاء​

تستخدم ميزة block/rescue في Ansible أثناء كل من التوفير وإلغاء التوفير للتعامل مع الأخطاء بشكل سلس. إذا فشلت أي مهمة في أي نقطة أثناء التوفير، يتم تنفيذ قسم الإنقاذ تلقائيًا للتراجع عن التغييرات للعودة إلى حالة متسقة. يضمن ذلك الموثوقية ويقلل من مخاطر النشر الجزئي أو الفاشل.

مثال على معالجة الأخطاء​

- name: الكتلة الرئيسية
block:
- name: الخطوة 1: إنشاء الحساب
uri: ...
register: response

- name: التحقق من الخطوة 1
assert:
that:
- response.status == 200

- name: الخطوة 2: إنشاء الخدمة
uri: ...

rescue:
# إذا فشلت أي تأكيد أو حدث خطأ في أي مهمة:
# 1. ينتقل التنفيذ إلى كتلة الإنقاذ
# 2. يتم تنفيذ مهام التنظيف
# 3. تفشل Playbook مع رسالة خطأ
- name: تنظيف التغييرات الجزئية
uri: ...

للحصول على تفاصيل كاملة حول نظام التوفير، وسير العمل، والمصادقة، انظر concepts_provisioning.