كل ما تحتاجه لبناء إضافات احترافية لنظام سيطرة ERP — الأحداث، الصلاحيات، API الكامل، ومثال تطبيقي جاهز للتنزيل.
إضافة سيطرة هي حزمة برمجية (Python + HTML) تُوسّع وظائف النظام. يمكنها:
إضافة صفحات ويب وروابط في القائمة
الاستجابة لـ 65+ حدث في النظام
قراءة/كتابة بصلاحيات محددة
تشغيل كود دوري (ساعة/يوم)
إعدادات قابلة للتخصيص
إرسال إشعارات للمستخدمين
id وname في manifest.json، واكتب منطقك في main.pyzip -r my_plugin.zip custom_my_plugin/
custom_my_plugin/
├── manifest.json ← إجباري — تعريف الإضافة (الاسم، الصلاحيات، الأحداث)
├── main.py ← إجباري — الكلاس الرئيسي (يرث SaytaraPlugin)
├── __init__.py ← فارغ (مطلوب لـ Python)
├── templates/
│ └── ext_custom_my_plugin/
│ ├── index.html ← الصفحة الرئيسية (تستخدم {% extends "base.html" %})
│ └── widget.html ← ويدجت الداشبورد (اختياري)
├── static/
│ ├── css/style.css ← أنماط CSS مخصصة
│ └── js/main.js ← JavaScript مخصص
└── README.md ← توثيق (اختياري)
ext_{plugin_id بدون نقاط} — مثلاً ext_custom_my_plugin. هذا يمنع تعارض الأسماء بين الإضافات.
{
"id": "custom.my_report",
"name": "تقرير الأرباح المتقدم",
"name_en": "Advanced Profit Report",
"version": "1.0.0",
"description": "تقرير أرباح تفصيلي مع رسوم بيانية وتصدير PDF",
"description_en": "Detailed profit report with charts and PDF export",
"category": "reports",
"icon": "chart-line",
"color": "#28a745",
"author": {
"name": "أحمد المطور",
"email": "ahmed@example.com",
"url": "https://example.com"
},
"entry_point": "main.py",
"min_system_version": "1.0.0",
"permissions": [
"accounting.invoices.read",
"accounting.reports.read",
"customers.list.read"
],
"events": {
"listen": ["accounting.invoice.created", "accounting.payment.received"],
"emit": ["custom.my_report.generated"]
},
"settings": [
{"key": "show_charts", "label": "إظهار رسوم بيانية", "type": "boolean", "default": true},
{"key": "currency", "label": "عملة التقرير", "type": "select",
"options": [{"value":"IQD","label":"دينار عراقي"}, {"value":"USD","label":"دولار"}]}
],
"ui": {
"menu_items": [
{"label": "تقرير الأرباح", "icon": "chart-line", "url": "/ext/my-report/", "order": 50}
]
},
"tags": ["reports", "analytics", "charts"]
}
| الحقل | إجباري | النوع | الوصف |
|---|---|---|---|
id | ✅ | string | معرف فريد: namespace.plugin_name — أحرف صغيرة، نقاط، شرطات |
name | ✅ | string | اسم بالعربية |
name_en | — | string | اسم بالإنجليزية |
version | ✅ | string | Semantic Versioning: 1.0.0 |
description | ✅ | string | وصف مختصر بالعربية |
description_en | — | string | وصف بالإنجليزية |
category | — | string | التصنيف: accounting, inventory, pos, crm, hrm, restaurant, reports, integrations, automation, utilities |
icon | — | string | أيقونة FontAwesome 6 (بدون fa-): chart-line, robot, rocket... |
color | — | string | لون HEX: #28a745 |
author | — | object | {name, email, url} |
entry_point | — | string | ملف الدخول (افتراضي: main.py) |
min_system_version | — | string | أدنى إصدار مطلوب من سيطرة |
permissions | ✅ | array | قائمة الصلاحيات المطلوبة (انظر قسم الصلاحيات) |
events.listen | — | array | الأحداث التي تستمع لها (انظر جدول الأحداث) |
events.emit | — | array | الأحداث التي تطلقها |
settings | — | array | إعدادات (انظر قسم الإعدادات) |
ui.menu_items | — | array | روابط القائمة: [{label, icon, url, order}] |
tags | — | array | كلمات مفتاحية للبحث |
dependencies | — | object | إضافات مطلوبة: {"plugin_id": ">=1.0.0"} |
يجب أن يحتوي على كلاس واحد يرث من SaytaraPlugin:
from extensions.plugin_base import SaytaraPlugin, PluginContext
class MyPlugin(SaytaraPlugin):
@property
def plugin_id(self): return 'custom.my_plugin' # يطابق id في manifest
@property
def plugin_name(self): return 'إضافتي'
@property
def plugin_version(self): return '1.0.0'
# ─── الدوال الإجبارية (Properties) ───
# plugin_id, plugin_name, plugin_version (أعلاه)
# ─── دورة الحياة (اختياري) ───
def on_install(self, ctx): ctx.log('info', 'تم التثبيت')
def on_activate(self, ctx): ctx.log('info', 'تم التفعيل')
def on_deactivate(self, ctx): ctx.log('info', 'تم التعطيل')
def on_uninstall(self, ctx): ctx.log('info', 'تم الحذف')
# ─── الأحداث ───
def register_events(self, ctx):
ctx.on_event('accounting.invoice.created', self._on_invoice)
def _on_invoice(self, event):
print(f"فاتورة: {event.data.get('invoice_id')}")
# ─── الصفحات ───
def register_routes(self, app):
from flask import Blueprint, render_template
bp = Blueprint('ext_my_plugin', __name__,
template_folder='templates', url_prefix='/ext/my-plugin')
@bp.route('/')
def index(): return render_template('ext_custom_my_plugin/index.html')
return bp
# ─── القائمة ───
def get_menu_items(self):
return [{'label': 'إضافتي', 'icon': 'rocket', 'url': '/ext/my-plugin/', 'order': 100}]
| الدالة | متى تُستدعى | استخدامات شائعة |
|---|---|---|
on_install(ctx) | مرة واحدة عند التثبيت | إنشاء جداول، تهيئة إعدادات افتراضية |
on_activate(ctx) | كل مرة عند التفعيل | تحميل بيانات مُخَزّنة، بدء خدمات |
register_events(ctx) | بعد التفعيل | تسجيل مستمعي أحداث |
register_routes(app) | بعد التفعيل | إنشاء Blueprint + صفحات |
on_deactivate(ctx) | عند التعطيل | تنظيف موارد، حفظ حالة |
on_uninstall(ctx) | عند الحذف | حذف جداول مخصصة، تنظيف كامل |
register_routes() تُنشئ Flask Blueprint. الصفحات تظهر على /ext/{name}/:
def register_routes(self, app):
bp = Blueprint('ext_my_report', __name__,
template_folder='templates',
static_folder='static',
url_prefix='/ext/my-report')
@bp.route('/')
def index():
return render_template('ext_my_report/index.html')
@bp.route('/api/data')
def api_data():
return jsonify({'success': True, 'items': [...]})
return bp # مهم! أرجع الـ Blueprint
القالب (index.html): استخدم {% extends "base.html" %} لورث تصميم النظام:
{% extends "base.html" %}
{% block title %}تقريري{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<h2>مرحباً من الإضافة!</h2>
<!-- محتوى صفحتك هنا -->
</div>
{% endblock %}
نظام سيطرة يطلق أحداث عند كل عملية مهمة. إضافتك تسجّل مستمعين لهذه الأحداث:
def register_events(self, ctx):
# استمع لحدث واحد أو أكثر
ctx.on_event('accounting.invoice.created', self._on_invoice)
ctx.on_event('accounting.payment.received', self._on_payment)
def _on_invoice(self, event):
# event.data يحتوي بيانات الحدث
invoice_id = event.data.get('invoice_id')
total = event.data.get('total_amount')
user_id = event.data.get('user_id')
print(f"فاتورة #{invoice_id} — المبلغ: {total}")
EventContext يحتوي:
event.event_name — اسم الحدثevent.data — بيانات الحدث (dict)event.tenant_id — معرف المشتركevent.user_id — معرف المستخدم الحاليevent.timestamp — وقت الحدث| اسم الحدث | متى يُطلق | البيانات (data) |
|---|---|---|
accounting.invoice.before_create | قبل إنشاء فاتورة (قابل للإلغاء) | invoice_type, customer_id, total_amount |
accounting.invoice.created | بعد إنشاء فاتورة بيع أو شراء | invoice_id, invoice_type, customer_id, total_amount, status, user_id |
accounting.invoice.updated | تعديل أو اعتماد فاتورة | invoice_id, action, user_id |
accounting.invoice.deleted | حذف أو إلغاء فاتورة | invoice_id, action (deleted/cancelled), user_id |
accounting.invoice.paid | تسديد فاتورة بالكامل | invoice_id, amount |
accounting.invoice.overdue | تجاوز فاتورة موعد السداد | invoice_id |
accounting.voucher.before_create | قبل إنشاء سند (قابل للإلغاء) | voucher_type |
accounting.voucher.created | إنشاء سند (قبض/صرف/يومية/صرف/تبديل/رصيد افتتاحي/بطاقة) | voucher_type, user_id |
accounting.voucher.posted | ترحيل سند | voucher_id |
accounting.voucher.deleted | حذف سند | voucher_type, voucher_id, user_id |
accounting.voucher.reversed | عكس قيد يومية | voucher_id, user_id |
accounting.payment.received | استلام دفعة على فاتورة | invoice_id, amount, payment_method, user_id |
accounting.payment.sent | تسجيل سند صرف | user_id |
accounting.payment.deleted | حذف دفعة | payment_id, user_id |
accounting.cost_center.saved | حفظ مركز كلفة | user_id |
accounting.payment_plan.action | عملية على خطة سداد/أقساط | path, user_id |
| اسم الحدث | متى يُطلق | البيانات |
|---|---|---|
accounting.check.status_changed | تغيير حالة شيك (أي حالة) | check_id, new_status, user_id |
accounting.check.cleared | تحصيل شيك بنجاح | check_id |
accounting.check.bounced | ارتجاع شيك | check_id |
| اسم الحدث | متى يُطلق | البيانات |
|---|---|---|
customers.customer.created | إضافة زبون جديد | customer_id, customer_name, user_id |
customers.customer.updated | تعديل بيانات زبون | customer_id, customer_name, user_id |
customers.customer.deleted | حذف زبون | customer_id, user_id |
suppliers.supplier.created | إضافة مجهّز جديد | supplier_name, user_id |
suppliers.supplier.updated | تعديل بيانات مجهّز | supplier_name, user_id |
suppliers.supplier.deleted | حذف مجهّز | supplier_id, user_id |
| اسم الحدث | متى يُطلق | البيانات |
|---|---|---|
inventory.material.created | إضافة مادة جديدة | material_name, user_id |
inventory.material.updated | تعديل مادة | material_id, user_id |
inventory.material.deleted | حذف مادة | material_id, user_id |
inventory.material.price_changed | تغيير سعر مادة | material_id |
inventory.stock.changed | تغيّر رصيد مخزون | adjustment_type, warehouse_id, user_id |
inventory.stock.low | رصيد مخزون منخفض (تحت الحد الأدنى) | material_id |
inventory.stock.out_of_stock | نفاد مخزون مادة | material_id |
inventory.stock.received | استلام بضاعة | — |
inventory.warehouse.saved | حفظ مخزن | user_id |
inventory.category.saved | حفظ تصنيف مادة | user_id |
inventory.brand.saved | حفظ ماركة | user_id |
inventory.stock_take.created | إنشاء جرد | user_id |
inventory.stock_take.completed | إكمال جرد | take_id, user_id |
inventory.stock_take.approved | اعتماد جرد | take_id, user_id |
| اسم الحدث | متى يُطلق | البيانات |
|---|---|---|
pos.sale.completed | إتمام عملية بيع POS | sale_id/invoice_id, total, user_id |
pos.sale.refunded | إلغاء/استرجاع عملية بيع | sale_id, amount, user_id |
pos.shift.opened | فتح وردية | user_id |
pos.shift.closed | إغلاق وردية | user_id |
| اسم الحدث | متى يُطلق | البيانات |
|---|---|---|
restaurant.order.placed | طلب جديد | order_id, table_id, user_id |
restaurant.order.ready | طلب جاهز من المطبخ | order_id, user_id |
restaurant.order.delivered | تسليم طلب / دفع | order_id, user_id |
restaurant.order.voided | إلغاء طلب | order_id, user_id |
restaurant.order.status_changed | تغيير حالة طلب | order_id, user_id |
restaurant.table.occupied | شَغل طاولة | table_id |
restaurant.table.freed | تحرير طاولة | table_id |
| اسم الحدث | متى يُطلق | البيانات |
|---|---|---|
crm.lead.created | عميل محتمل جديد | lead_id, user_id |
crm.lead.updated | تعديل عميل محتمل | lead_id, user_id |
crm.lead.converted | تحويل عميل محتمل لزبون | lead_id, user_id |
crm.lead.lost | فقدان عميل محتمل | lead_id, action, user_id |
crm.opportunity.created | فرصة بيع جديدة | opportunity_id/lead_id, user_id |
crm.opportunity.updated | تعديل فرصة | opportunity_id, user_id |
crm.opportunity.stage_changed | نقل فرصة بين مراحل Pipeline | opportunity_id, user_id |
crm.opportunity.won | كسب فرصة بيع | opportunity_id |
crm.opportunity.lost | خسارة فرصة بيع | opportunity_id, action, user_id |
crm.leads.bulk_action | عملية جماعية على العملاء | action, count, user_id |
| اسم الحدث | متى يُطلق | البيانات |
|---|---|---|
hrm.employee.hired | تعيين موظف | employee_id, user_id |
hrm.employee.terminated | إنهاء خدمة موظف | employee_id, user_id |
hrm.leave.requested | طلب إجازة | leave_id, user_id |
hrm.leave.approved | الموافقة على إجازة | leave_id, user_id |
hrm.leave.rejected | رفض إجازة | leave_id, user_id |
hrm.payroll.created | إنشاء دورة رواتب | run_id, user_id |
hrm.payroll.approved | اعتماد رواتب | run_id, user_id |
hrm.salary.processed | ترحيل رواتب (قيد محاسبي) | run_id, user_id |
hrm.loan.created | إنشاء قرض/سلفة | loan_id, user_id |
hrm.loan.approved | الموافقة على قرض | loan_id, user_id |
hrm.recruitment.posting_created | إعلان وظيفة | user_id |
hrm.performance.review_created | تقييم أداء | user_id |
hrm.eos.calculated | حساب نهاية خدمة | user_id |
hrm.department.saved | حفظ قسم/إدارة | user_id |
| اسم الحدث | متى يُطلق | البيانات |
|---|---|---|
system.user.logged_in | تسجيل دخول مستخدم | user_id, username |
system.user.logged_out | تسجيل خروج | user_id |
system.user.created | إنشاء مستخدم | user_id |
system.branch.saved | حفظ فرع | user_id |
system.settings.updated | تحديث إعدادات النظام | user_id |
system.backup.created | إنشاء نسخة احتياطية | user_id |
system.data.imported | استيراد بيانات CSV | import_type, user_id |
system.currency.updated | تحديث عملات | user_id |
system.plugin.installed | تثبيت إضافة | plugin_id |
system.plugin.enabled | تفعيل إضافة | plugin_id, tenant_id |
system.plugin.disabled | تعطيل إضافة | plugin_id, tenant_id |
notification.sent | إرسال إشعار | user_id |
| اسم الحدث | متى يُطلق |
|---|---|
orders.order.created | إنشاء طلب/عرض سعر |
assets.asset.saved | حفظ أصل ثابت |
assets.asset.disposed | استبعاد أصل ثابت |
assets.depreciation.run | تشغيل استهلاك شهري |
fiscal.period.created | إنشاء فترة مالية |
fiscal.period.closed | إغلاق فترة مالية |
fiscal.period.reopened | إعادة فتح فترة |
fiscal.year_end.executed | إقفال سنة مالية |
sales.representative.action | عملية على مندوب مبيعات |
supermarket.action | عملية سوبرماركت |
integration.woocommerce.synced | مزامنة WooCommerce |
الأحداث التي تبدأ بـ before_ تسمح لإضافتك بـ إلغاء العملية قبل حدوثها:
def register_events(self, ctx):
ctx.on_event('accounting.invoice.before_create', self._validate, priority=10)
def _validate(self, event):
total = event.data.get('total_amount', 0)
if total > 50000000: # 50 مليون
event.cancel('المبلغ يتجاوز الحد المسموح!', by=self.plugin_id)
الأحداث المتاحة للإلغاء: accounting.invoice.before_create, accounting.invoice.before_update, accounting.invoice.before_delete, accounting.voucher.before_create
# قراءة فواتير
invoices = ctx.query('invoices', filters={'payment_status': 'unpaid'}, limit=50)
# قراءة عميل بالمعرف
customer = ctx.get_by_id('customers', 42)
# استعلام SQL مخصص (قراءة فقط!)
results = ctx.execute_query("SELECT * FROM invoices WHERE total_amount > %s", (1000000,))
# إنشاء سجل في جدول الإضافة
ctx.create('ext_plugin_data', {'key': 'sync', 'value': '2026-01-01'})
# تحديث سجل
ctx.update('customers', 5, {'notes': 'VIP'})
# إطلاق حدث مخصص
ctx.emit_event('custom.report.generated', {'report_id': 1})
# إرسال إشعار
ctx.send_notification('تنبيه!', 'تقرير الأرباح جاهز للتحميل')
def get_settings_schema(self):
return [
{'key': 'api_key', 'label': 'مفتاح API', 'type': 'password'},
{'key': 'auto_sync', 'label': 'مزامنة تلقائية', 'type': 'boolean', 'default': True},
{'key': 'interval', 'label': 'الفترة (دقائق)', 'type': 'number', 'default': 60},
{'key': 'currency', 'label': 'العملة', 'type': 'select',
'options': [{'value':'IQD','label':'دينار'},{'value':'USD','label':'دولار'}]},
]
أنواع الإعدادات: text, password, number, boolean, select, textarea
| الصلاحية | الوصف |
|---|---|
accounting.invoices.read | قراءة الفواتير |
accounting.invoices.write | إنشاء/تعديل فواتير |
accounting.vouchers.read | قراءة السندات |
accounting.vouchers.write | إنشاء سندات |
accounting.reports.read | قراءة التقارير المالية |
accounting.checks.read | قراءة الشيكات |
customers.list.read | قراءة بيانات الزبائن |
customers.list.write | تعديل بيانات الزبائن |
suppliers.list.read | قراءة بيانات المجهّزين |
inventory.materials.read | قراءة المواد والمخزون |
inventory.materials.write | تعديل المواد |
pos.sales.read | قراءة مبيعات POS |
crm.leads.read | قراءة العملاء المحتملين |
hrm.employees.read | قراءة بيانات الموظفين |
system.settings.read | قراءة إعدادات النظام |
zip -r custom_my_plugin.zip custom_my_plugin/
manifest.json (اسم، وصف، أيقونة، tags)| الدالة | الوصف | مثال |
|---|---|---|
ctx.query(table, filters, fields, order_by, limit) | استعلام سجلات | ctx.query('invoices', {'status':'active'}, limit=50) |
ctx.get_by_id(table, id) | جلب سجل واحد | ctx.get_by_id('customers', 5) |
ctx.create(table, data) | إنشاء سجل | ctx.create('ext_data', {'key':'v'}) |
ctx.update(table, id, data) | تحديث سجل | ctx.update('customers', 5, {'name':'أحمد'}) |
ctx.execute_query(sql, params) | SQL مخصص (SELECT فقط) | ctx.execute_query("SELECT ...") |
ctx.on_event(name, handler) | الاستماع لحدث | ctx.on_event('invoice.created', fn) |
ctx.emit_event(name, data) | إطلاق حدث مخصص | ctx.emit_event('report.ready', {}) |
ctx.get_setting(key, default) | قراءة إعداد | ctx.get_setting('api_key', '') |
ctx.set_setting(key, value) | حفظ إعداد | ctx.set_setting('last_run', '...') |
ctx.send_notification(title, msg) | إرسال إشعار | ctx.send_notification('تنبيه', '...') |
ctx.log(level, message) | تسجيل نشاط | ctx.log('info', 'تم') |
invoices, invoice_items, receipts, payments, journals, daily_vouchers, checks, accounts
customers, suppliers, sales_representatives
materials, warehouses, stock_movements, categories, brands
pos_sales, pos_sale_items, pos_shifts
crm_leads, crm_opportunities, crm_pipelines, crm_activities
restaurant_orders, restaurant_tables, kitchen_orders
users, user_sessions, role_permissions, tenant_config — لا يمكن الوصول إليها مطلقاً.
ضع مجلد الإضافة في extensions/installed/، أعد تشغيل سيطرة، ثم ثبّت وفعّل من مدير الإضافات.
لا. كل إضافة تعمل في سياق المشترك الحالي فقط (tenant isolation).
نعم، بشرط أن تكون المكتبة مثبتة على السيرفر. تواصل مع فريق سيطرة لطلب تثبيت مكتبات جديدة.
100 طلب/دقيقة لكل إضافة. للإضافات ذات الحمل العالي، تواصل معنا لرفع الحد.
إذا عرّفت get_menu_items()، يظهر الرابط في الشريط العلوي → قائمة الإعدادات/الأدوات بجانب "مدير الإضافات".