|
""" |
|
وحدة التسعير المتكامل لنظام إدارة المناقصات - Hybrid Face |
|
""" |
|
|
|
import os |
|
import logging |
|
import threading |
|
import datetime |
|
import json |
|
import math |
|
from pathlib import Path |
|
|
|
|
|
logging.basicConfig( |
|
level=logging.INFO, |
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' |
|
) |
|
logger = logging.getLogger('pricing') |
|
|
|
class PricingEngine: |
|
"""محرك التسعير المتكامل""" |
|
|
|
def __init__(self, config=None, db=None): |
|
"""تهيئة محرك التسعير""" |
|
self.config = config |
|
self.db = db |
|
self.pricing_in_progress = False |
|
self.current_project = None |
|
self.pricing_results = {} |
|
|
|
|
|
if config and hasattr(config, 'EXPORTS_PATH'): |
|
self.exports_path = Path(config.EXPORTS_PATH) |
|
else: |
|
self.exports_path = Path('data/exports') |
|
|
|
if not self.exports_path.exists(): |
|
self.exports_path.mkdir(parents=True, exist_ok=True) |
|
|
|
def calculate_pricing(self, project_id, strategy="comprehensive", callback=None): |
|
"""حساب التسعير للمشروع""" |
|
if self.pricing_in_progress: |
|
logger.warning("هناك عملية تسعير جارية بالفعل") |
|
return False |
|
|
|
self.pricing_in_progress = True |
|
self.current_project = project_id |
|
self.pricing_results = { |
|
"project_id": project_id, |
|
"strategy": strategy, |
|
"pricing_start_time": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), |
|
"status": "جاري التسعير", |
|
"direct_costs": {}, |
|
"indirect_costs": {}, |
|
"risk_costs": {}, |
|
"summary": {} |
|
} |
|
|
|
|
|
thread = threading.Thread( |
|
target=self._calculate_pricing_thread, |
|
args=(project_id, strategy, callback) |
|
) |
|
thread.daemon = True |
|
thread.start() |
|
|
|
return True |
|
|
|
def _calculate_pricing_thread(self, project_id, strategy, callback): |
|
"""خيط حساب التسعير""" |
|
try: |
|
|
|
project_data = self._get_project_data(project_id) |
|
|
|
if not project_data: |
|
logger.error(f"لم يتم العثور على بيانات المشروع: {project_id}") |
|
self.pricing_results["status"] = "فشل التسعير" |
|
self.pricing_results["error"] = "لم يتم العثور على بيانات المشروع" |
|
return |
|
|
|
|
|
self._calculate_direct_costs(project_data) |
|
|
|
|
|
self._calculate_indirect_costs(project_data, strategy) |
|
|
|
|
|
self._calculate_risk_costs(project_data, strategy) |
|
|
|
|
|
self._calculate_pricing_summary(strategy) |
|
|
|
|
|
self.pricing_results["status"] = "اكتمل التسعير" |
|
self.pricing_results["pricing_end_time"] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
|
|
|
logger.info(f"اكتمل تسعير المشروع: {project_id}") |
|
|
|
except Exception as e: |
|
logger.error(f"خطأ في تسعير المشروع: {str(e)}") |
|
self.pricing_results["status"] = "فشل التسعير" |
|
self.pricing_results["error"] = str(e) |
|
|
|
finally: |
|
self.pricing_in_progress = False |
|
|
|
|
|
if callback and callable(callback): |
|
callback(self.pricing_results) |
|
|
|
def _get_project_data(self, project_id): |
|
"""الحصول على بيانات المشروع""" |
|
|
|
|
|
|
|
return { |
|
"id": project_id, |
|
"name": "مشروع الطرق السريعة", |
|
"client": "وزارة النقل", |
|
"items": [ |
|
{"id": 1, "name": "أعمال الحفر", "unit": "م³", "quantity": 1500, "unit_cost": 45}, |
|
{"id": 2, "name": "أعمال الخرسانة", "unit": "م³", "quantity": 750, "unit_cost": 1200}, |
|
{"id": 3, "name": "أعمال الأسفلت", "unit": "م²", "quantity": 5000, "unit_cost": 120}, |
|
{"id": 4, "name": "أعمال الإنارة", "unit": "عدد", "quantity": 50, "unit_cost": 3500} |
|
], |
|
"resources": { |
|
"materials": [ |
|
{"id": 1, "name": "أسمنت", "unit": "طن", "quantity": 300, "unit_cost": 950}, |
|
{"id": 2, "name": "حديد تسليح", "unit": "طن", "quantity": 120, "unit_cost": 3200}, |
|
{"id": 3, "name": "رمل", "unit": "م³", "quantity": 450, "unit_cost": 75}, |
|
{"id": 4, "name": "أسفلت", "unit": "طن", "quantity": 200, "unit_cost": 1800} |
|
], |
|
"equipment": [ |
|
{"id": 1, "name": "حفارة", "unit": "يوم", "quantity": 45, "unit_cost": 1500}, |
|
{"id": 2, "name": "لودر", "unit": "يوم", "quantity": 30, "unit_cost": 1200}, |
|
{"id": 3, "name": "شاحنة نقل", "unit": "يوم", "quantity": 60, "unit_cost": 800}, |
|
{"id": 4, "name": "خلاطة خرسانة", "unit": "يوم", "quantity": 40, "unit_cost": 600} |
|
], |
|
"labor": [ |
|
{"id": 1, "name": "عمال", "unit": "يوم", "quantity": 1200, "unit_cost": 150}, |
|
{"id": 2, "name": "فنيون", "unit": "يوم", "quantity": 600, "unit_cost": 300}, |
|
{"id": 3, "name": "مهندسون", "unit": "يوم", "quantity": 180, "unit_cost": 800} |
|
] |
|
}, |
|
"risks": [ |
|
{"id": 1, "name": "تأخر توريد المواد", "probability": "متوسط", "impact": "عالي", "cost_impact": 0.05}, |
|
{"id": 2, "name": "تغير أسعار المواد", "probability": "عالي", "impact": "عالي", "cost_impact": 0.08}, |
|
{"id": 3, "name": "ظروف جوية غير مواتية", "probability": "منخفض", "impact": "متوسط", "cost_impact": 0.03}, |
|
{"id": 4, "name": "نقص العمالة", "probability": "متوسط", "impact": "متوسط", "cost_impact": 0.04} |
|
], |
|
"project_duration": 180, |
|
"location": "المنطقة الشرقية" |
|
} |
|
|
|
def _calculate_direct_costs(self, project_data): |
|
"""حساب التكاليف المباشرة""" |
|
|
|
items_cost = 0 |
|
items_details = [] |
|
|
|
for item in project_data["items"]: |
|
total_cost = item["quantity"] * item["unit_cost"] |
|
items_cost += total_cost |
|
|
|
items_details.append({ |
|
"id": item["id"], |
|
"name": item["name"], |
|
"unit": item["unit"], |
|
"quantity": item["quantity"], |
|
"unit_cost": item["unit_cost"], |
|
"total_cost": total_cost |
|
}) |
|
|
|
|
|
materials_cost = 0 |
|
equipment_cost = 0 |
|
labor_cost = 0 |
|
|
|
for material in project_data["resources"]["materials"]: |
|
materials_cost += material["quantity"] * material["unit_cost"] |
|
|
|
for equipment in project_data["resources"]["equipment"]: |
|
equipment_cost += equipment["quantity"] * equipment["unit_cost"] |
|
|
|
for labor in project_data["resources"]["labor"]: |
|
labor_cost += labor["quantity"] * labor["unit_cost"] |
|
|
|
resources_cost = materials_cost + equipment_cost + labor_cost |
|
|
|
|
|
self.pricing_results["direct_costs"] = { |
|
"items": { |
|
"total": items_cost, |
|
"details": items_details |
|
}, |
|
"resources": { |
|
"total": resources_cost, |
|
"materials": materials_cost, |
|
"equipment": equipment_cost, |
|
"labor": labor_cost |
|
}, |
|
"total_direct_costs": items_cost |
|
} |
|
|
|
def _calculate_indirect_costs(self, project_data, strategy): |
|
"""حساب التكاليف غير المباشرة""" |
|
direct_costs = self.pricing_results["direct_costs"]["total_direct_costs"] |
|
|
|
|
|
if strategy == "comprehensive": |
|
overhead_rate = 0.15 |
|
profit_rate = 0.10 |
|
admin_rate = 0.05 |
|
elif strategy == "competitive": |
|
overhead_rate = 0.12 |
|
profit_rate = 0.07 |
|
admin_rate = 0.04 |
|
else: |
|
overhead_rate = 0.13 |
|
profit_rate = 0.08 |
|
admin_rate = 0.045 |
|
|
|
|
|
overhead_cost = direct_costs * overhead_rate |
|
profit_cost = direct_costs * profit_rate |
|
admin_cost = direct_costs * admin_rate |
|
|
|
|
|
mobilization_cost = direct_costs * 0.03 |
|
bonds_insurance_cost = direct_costs * 0.02 |
|
|
|
|
|
total_indirect_costs = overhead_cost + profit_cost + admin_cost + mobilization_cost + bonds_insurance_cost |
|
|
|
|
|
self.pricing_results["indirect_costs"] = { |
|
"overhead": { |
|
"rate": overhead_rate, |
|
"cost": overhead_cost |
|
}, |
|
"profit": { |
|
"rate": profit_rate, |
|
"cost": profit_cost |
|
}, |
|
"administrative": { |
|
"rate": admin_rate, |
|
"cost": admin_cost |
|
}, |
|
"mobilization": { |
|
"rate": 0.03, |
|
"cost": mobilization_cost |
|
}, |
|
"bonds_insurance": { |
|
"rate": 0.02, |
|
"cost": bonds_insurance_cost |
|
}, |
|
"total_indirect_costs": total_indirect_costs |
|
} |
|
|
|
def _calculate_risk_costs(self, project_data, strategy): |
|
"""حساب تكاليف المخاطر""" |
|
direct_costs = self.pricing_results["direct_costs"]["total_direct_costs"] |
|
|
|
|
|
probability_map = { |
|
"منخفض": 0.3, |
|
"متوسط": 0.5, |
|
"عالي": 0.7 |
|
} |
|
|
|
impact_map = { |
|
"منخفض": 0.3, |
|
"متوسط": 0.5, |
|
"عالي": 0.7 |
|
} |
|
|
|
|
|
risk_costs = [] |
|
total_risk_cost = 0 |
|
|
|
for risk in project_data["risks"]: |
|
probability = probability_map.get(risk["probability"], 0.5) |
|
impact = impact_map.get(risk["impact"], 0.5) |
|
|
|
|
|
risk_score = probability * impact |
|
|
|
|
|
risk_cost = direct_costs * risk["cost_impact"] * risk_score |
|
|
|
|
|
if strategy == "comprehensive": |
|
risk_cost_factor = 1.0 |
|
elif strategy == "competitive": |
|
risk_cost_factor = 0.7 |
|
else: |
|
risk_cost_factor = 0.85 |
|
|
|
adjusted_risk_cost = risk_cost * risk_cost_factor |
|
total_risk_cost += adjusted_risk_cost |
|
|
|
risk_costs.append({ |
|
"id": risk["id"], |
|
"name": risk["name"], |
|
"probability": risk["probability"], |
|
"impact": risk["impact"], |
|
"risk_score": risk_score, |
|
"cost_impact": risk["cost_impact"], |
|
"risk_cost": risk_cost, |
|
"adjusted_risk_cost": adjusted_risk_cost |
|
}) |
|
|
|
|
|
self.pricing_results["risk_costs"] = { |
|
"risks": risk_costs, |
|
"total_risk_cost": total_risk_cost, |
|
"strategy_factor": 1.0 if strategy == "comprehensive" else (0.7 if strategy == "competitive" else 0.85) |
|
} |
|
|
|
def _calculate_pricing_summary(self, strategy): |
|
"""حساب ملخص التسعير""" |
|
direct_costs = self.pricing_results["direct_costs"]["total_direct_costs"] |
|
indirect_costs = self.pricing_results["indirect_costs"]["total_indirect_costs"] |
|
risk_costs = self.pricing_results["risk_costs"]["total_risk_cost"] |
|
|
|
|
|
total_costs = direct_costs + indirect_costs + risk_costs |
|
|
|
|
|
vat = total_costs * 0.15 |
|
|
|
|
|
final_price = total_costs + vat |
|
|
|
|
|
self.pricing_results["summary"] = { |
|
"direct_costs": direct_costs, |
|
"indirect_costs": indirect_costs, |
|
"risk_costs": risk_costs, |
|
"total_costs": total_costs, |
|
"vat": { |
|
"rate": 0.15, |
|
"amount": vat |
|
}, |
|
"final_price": final_price, |
|
"strategy": strategy, |
|
"pricing_notes": self._generate_pricing_notes(strategy) |
|
} |
|
|
|
def _generate_pricing_notes(self, strategy): |
|
"""توليد ملاحظات التسعير""" |
|
if strategy == "comprehensive": |
|
return [ |
|
"تم تطبيق استراتيجية التسعير الشاملة التي تغطي جميع التكاليف والمخاطر", |
|
"تم تضمين هامش ربح مناسب (10%) لضمان ربحية المشروع", |
|
"تم تغطية جميع المخاطر المحتملة بشكل كامل", |
|
"يوصى بمراجعة أسعار المواد قبل تقديم العرض النهائي" |
|
] |
|
elif strategy == "competitive": |
|
return [ |
|
"تم تطبيق استراتيجية التسعير التنافسية لزيادة فرص الفوز بالمناقصة", |
|
"تم تخفيض هامش الربح (7%) لتقديم سعر تنافسي", |
|
"تم تغطية المخاطر بشكل جزئي، مما يتطلب إدارة مخاطر فعالة أثناء التنفيذ", |
|
"يجب مراقبة التكاليف بدقة أثناء تنفيذ المشروع لضمان الربحية" |
|
] |
|
else: |
|
return [ |
|
"تم تطبيق استراتيجية التسعير المتوازنة التي توازن بين الربحية والتنافسية", |
|
"تم تضمين هامش ربح معقول (8%) يوازن بين الربحية والتنافسية", |
|
"تم تغطية المخاطر الرئيسية بشكل مناسب", |
|
"يوصى بمراجعة بنود التكلفة العالية قبل تقديم العرض النهائي" |
|
] |
|
|
|
def get_pricing_status(self): |
|
"""الحصول على حالة التسعير الحالي""" |
|
if not self.pricing_in_progress: |
|
if not self.pricing_results: |
|
return {"status": "لا يوجد تسعير جارٍ"} |
|
else: |
|
return {"status": self.pricing_results.get("status", "غير معروف")} |
|
|
|
return { |
|
"status": "جاري التسعير", |
|
"project_id": self.current_project, |
|
"start_time": self.pricing_results.get("pricing_start_time") |
|
} |
|
|
|
def get_pricing_results(self): |
|
"""الحصول على نتائج التسعير""" |
|
return self.pricing_results |
|
|
|
def export_pricing_results(self, output_path=None): |
|
"""تصدير نتائج التسعير إلى ملف JSON""" |
|
if not self.pricing_results: |
|
logger.warning("لا توجد نتائج تسعير للتصدير") |
|
return None |
|
|
|
if not output_path: |
|
|
|
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') |
|
filename = f"pricing_results_{timestamp}.json" |
|
output_path = os.path.join(self.exports_path, filename) |
|
|
|
try: |
|
with open(output_path, 'w', encoding='utf-8') as f: |
|
json.dump(self.pricing_results, f, ensure_ascii=False, indent=4) |
|
|
|
logger.info(f"تم تصدير نتائج التسعير إلى: {output_path}") |
|
return output_path |
|
|
|
except Exception as e: |
|
logger.error(f"خطأ في تصدير نتائج التسعير: {str(e)}") |
|
return None |
|
|
|
def import_pricing_results(self, input_path): |
|
"""استيراد نتائج التسعير من ملف JSON""" |
|
if not os.path.exists(input_path): |
|
logger.error(f"ملف نتائج التسعير غير موجود: {input_path}") |
|
return False |
|
|
|
try: |
|
with open(input_path, 'r', encoding='utf-8') as f: |
|
self.pricing_results = json.load(f) |
|
|
|
logger.info(f"تم استيراد نتائج التسعير من: {input_path}") |
|
return True |
|
|
|
except Exception as e: |
|
logger.error(f"خطأ في استيراد نتائج التسعير: {str(e)}") |
|
return False |
|
|