|
"""
|
|
وحدة التسعير المتكامل لنظام إدارة المناقصات - 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
|
|
|