|
""" |
|
كتالوج بنود نموذجية للمقاولات |
|
يحتوي هذا الملف على قائمة كاملة من النماذج الجاهزة للبنود الشائعة في مشاريع المقاولات، مثل: |
|
- أعمال الخرسانة بأنواعها |
|
- المناهل وأنواع المواسير |
|
- التركيبات المختلفة |
|
- الطرق والأسفلت |
|
- وغيرها من أعمال المقاولات |
|
""" |
|
|
|
import os |
|
import json |
|
import sys |
|
from typing import Dict, List, Any, Optional |
|
from datetime import datetime |
|
|
|
|
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../"))) |
|
try: |
|
import config |
|
except ImportError: |
|
|
|
class DefaultConfig: |
|
DATA_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../data")) |
|
config = DefaultConfig |
|
|
|
|
|
if not os.path.exists(config.DATA_DIR): |
|
os.makedirs(config.DATA_DIR) |
|
|
|
|
|
class ConstructionTemplates: |
|
"""كتالوج بنود نموذجية للمقاولات""" |
|
|
|
def __init__(self): |
|
"""تهيئة كتالوج البنود النموذجية""" |
|
self.templates_file = os.path.join(config.DATA_DIR, 'construction_templates.json') |
|
self.market_prices_file = os.path.join(config.DATA_DIR, 'saudi_market_prices.json') |
|
|
|
|
|
self.templates = self._load_templates() |
|
|
|
|
|
self.market_prices = self._load_market_prices() |
|
|
|
def _load_templates(self) -> Dict[str, Dict[str, Any]]: |
|
"""تحميل قوالب البنود النموذجية من الملف""" |
|
if os.path.exists(self.templates_file): |
|
try: |
|
with open(self.templates_file, 'r', encoding='utf-8') as f: |
|
return json.load(f) |
|
except Exception as e: |
|
print(f"خطأ في تحميل قوالب البنود النموذجية: {str(e)}") |
|
|
|
|
|
default_templates = self._create_default_templates() |
|
|
|
|
|
self._save_templates(default_templates) |
|
|
|
return default_templates |
|
|
|
def _save_templates(self, templates: Dict[str, Dict[str, Any]]) -> None: |
|
"""حفظ قوالب البنود النموذجية إلى الملف""" |
|
try: |
|
with open(self.templates_file, 'w', encoding='utf-8') as f: |
|
json.dump(templates, f, ensure_ascii=False, indent=4) |
|
except Exception as e: |
|
print(f"خطأ في حفظ قوالب البنود النموذجية: {str(e)}") |
|
|
|
def _load_market_prices(self) -> Dict[str, Dict[str, Any]]: |
|
"""تحميل أسعار السوق السعودي من الملف""" |
|
if os.path.exists(self.market_prices_file): |
|
try: |
|
with open(self.market_prices_file, 'r', encoding='utf-8') as f: |
|
return json.load(f) |
|
except Exception as e: |
|
print(f"خطأ في تحميل أسعار السوق السعودي: {str(e)}") |
|
|
|
|
|
default_prices = self._create_default_market_prices() |
|
|
|
|
|
self._save_market_prices(default_prices) |
|
|
|
return default_prices |
|
|
|
def _save_market_prices(self, prices: Dict[str, Dict[str, Any]]) -> None: |
|
"""حفظ أسعار السوق السعودي إلى الملف""" |
|
try: |
|
with open(self.market_prices_file, 'w', encoding='utf-8') as f: |
|
json.dump(prices, f, ensure_ascii=False, indent=4) |
|
except Exception as e: |
|
print(f"خطأ في حفظ أسعار السوق السعودي: {str(e)}") |
|
|
|
def _create_default_templates(self) -> Dict[str, Dict[str, Any]]: |
|
"""إنشاء قوالب افتراضية للبنود النموذجية""" |
|
templates = { |
|
"categories": { |
|
"أعمال_خرسانية": { |
|
"name": "أعمال خرسانية", |
|
"description": "بنود أعمال الخرسانة المسلحة والعادية", |
|
"icon": "building" |
|
}, |
|
"أعمال_صحية": { |
|
"name": "أعمال صحية", |
|
"description": "بنود أعمال المناهل والمواسير والتركيبات الصحية", |
|
"icon": "pipe" |
|
}, |
|
"أعمال_طرق": { |
|
"name": "أعمال طرق", |
|
"description": "بنود أعمال الطرق والأسفلت والرصف", |
|
"icon": "road" |
|
}, |
|
"أعمال_كهربائية": { |
|
"name": "أعمال كهربائية", |
|
"description": "بنود أعمال الكهرباء والإنارة", |
|
"icon": "zap" |
|
}, |
|
"أعمال_ميكانيكية": { |
|
"name": "أعمال ميكانيكية", |
|
"description": "بنود أعمال التكييف والتهوية والتبريد", |
|
"icon": "thermometer" |
|
} |
|
}, |
|
"templates": { |
|
|
|
"خرسانة_مسلحة_أساسات": { |
|
"category": "أعمال_خرسانية", |
|
"name": "خرسانة مسلحة للأساسات", |
|
"description": "توريد وصب خرسانة مسلحة للأساسات بقوة لا تقل عن 300 كجم/سم2", |
|
"unit": "م3", |
|
"components": { |
|
"materials": [ |
|
{"الاسم": "خرسانة جاهزة", "الكمية": 1.0, "الوحدة": "م3", "سعر_الوحدة": 750.0}, |
|
{"الاسم": "حديد تسليح", "الكمية": 0.12, "الوحدة": "طن", "سعر_الوحدة": 5500.0} |
|
], |
|
"labor": [ |
|
{"النوع": "عامل خرسانة", "العدد": 4, "المدة": 0.3, "سعر_اليوم": 150.0}, |
|
{"النوع": "نجار مسلح", "العدد": 2, "المدة": 0.5, "سعر_اليوم": 250.0}, |
|
{"النوع": "حداد مسلح", "العدد": 2, "المدة": 0.5, "سعر_اليوم": 250.0} |
|
], |
|
"equipment": [ |
|
{"النوع": "هزاز خرسانة", "العدد": 1, "المدة": 0.3, "سعر_اليوم": 150.0} |
|
] |
|
}, |
|
"admin_expenses": 0.05, |
|
"profit_margin": 0.10, |
|
"tags": ["خرسانة", "أساسات", "مسلحة"] |
|
}, |
|
"خرسانة_مسلحة_أعمدة": { |
|
"category": "أعمال_خرسانية", |
|
"name": "خرسانة مسلحة للأعمدة", |
|
"description": "توريد وصب خرسانة مسلحة للأعمدة بقوة لا تقل عن 350 كجم/سم2", |
|
"unit": "م3", |
|
"components": { |
|
"materials": [ |
|
{"الاسم": "خرسانة جاهزة", "الكمية": 1.0, "الوحدة": "م3", "سعر_الوحدة": 750.0}, |
|
{"الاسم": "حديد تسليح", "الكمية": 0.18, "الوحدة": "طن", "سعر_الوحدة": 5500.0} |
|
], |
|
"labor": [ |
|
{"النوع": "عامل خرسانة", "العدد": 4, "المدة": 0.4, "سعر_اليوم": 150.0}, |
|
{"النوع": "نجار مسلح", "العدد": 2, "المدة": 0.7, "سعر_اليوم": 250.0}, |
|
{"النوع": "حداد مسلح", "العدد": 2, "المدة": 0.7, "سعر_اليوم": 250.0} |
|
], |
|
"equipment": [ |
|
{"النوع": "هزاز خرسانة", "العدد": 2, "المدة": 0.4, "سعر_اليوم": 150.0} |
|
] |
|
}, |
|
"admin_expenses": 0.05, |
|
"profit_margin": 0.10, |
|
"tags": ["خرسانة", "أعمدة", "مسلحة"] |
|
}, |
|
"خرسانة_مسلحة_أسقف": { |
|
"category": "أعمال_خرسانية", |
|
"name": "خرسانة مسلحة للأسقف", |
|
"description": "توريد وصب خرسانة مسلحة للأسقف والبلاطات بقوة لا تقل عن 350 كجم/سم2", |
|
"unit": "م3", |
|
"components": { |
|
"materials": [ |
|
{"الاسم": "خرسانة جاهزة", "الكمية": 1.0, "الوحدة": "م3", "سعر_الوحدة": 750.0}, |
|
{"الاسم": "حديد تسليح", "الكمية": 0.16, "الوحدة": "طن", "سعر_الوحدة": 5500.0} |
|
], |
|
"labor": [ |
|
{"النوع": "عامل خرسانة", "العدد": 5, "المدة": 0.5, "سعر_اليوم": 150.0}, |
|
{"النوع": "نجار مسلح", "العدد": 3, "المدة": 0.8, "سعر_اليوم": 250.0}, |
|
{"النوع": "حداد مسلح", "العدد": 3, "المدة": 0.8, "سعر_اليوم": 250.0} |
|
], |
|
"equipment": [ |
|
{"النوع": "هزاز خرسانة", "العدد": 2, "المدة": 0.5, "سعر_اليوم": 150.0} |
|
] |
|
}, |
|
"admin_expenses": 0.05, |
|
"profit_margin": 0.10, |
|
"tags": ["خرسانة", "أسقف", "بلاطات", "مسلحة"] |
|
}, |
|
|
|
|
|
"منهل_تفتيش_خرساني": { |
|
"category": "أعمال_صحية", |
|
"name": "منهل تفتيش خرساني", |
|
"description": "توريد وتركيب منهل تفتيش خرساني قطر 1 متر وعمق 2 متر", |
|
"unit": "عدد", |
|
"components": { |
|
"materials": [ |
|
{"الاسم": "خرسانة جاهزة", "الكمية": 1.5, "الوحدة": "م3", "سعر_الوحدة": 750.0}, |
|
{"الاسم": "حديد تسليح", "الكمية": 0.15, "الوحدة": "طن", "سعر_الوحدة": 5500.0}, |
|
{"الاسم": "غطاء منهل حديد", "الكمية": 1, "الوحدة": "عدد", "سعر_الوحدة": 1500.0} |
|
], |
|
"labor": [ |
|
{"النوع": "عامل خرسانة", "العدد": 3, "المدة": 1, "سعر_اليوم": 150.0}, |
|
{"النوع": "نجار مسلح", "العدد": 2, "المدة": 1, "سعر_اليوم": 250.0}, |
|
{"النوع": "حداد مسلح", "العدد": 1, "المدة": 1, "سعر_اليوم": 250.0}, |
|
{"النوع": "سباك", "العدد": 2, "المدة": 1, "سعر_اليوم": 250.0} |
|
], |
|
"equipment": [ |
|
{"النوع": "حفار صغير", "العدد": 1, "المدة": 0.5, "سعر_اليوم": 1200.0} |
|
] |
|
}, |
|
"admin_expenses": 0.05, |
|
"profit_margin": 0.12, |
|
"tags": ["صرف صحي", "منهل", "تفتيش"] |
|
}, |
|
"مواسير_بلاستيك_قطر_200_مم": { |
|
"category": "أعمال_صحية", |
|
"name": "مواسير بلاستيك قطر 200 مم", |
|
"description": "توريد وتركيب مواسير بلاستيك UPVC قطر 200 مم لشبكات الصرف الصحي", |
|
"unit": "م.ط", |
|
"components": { |
|
"materials": [ |
|
{"الاسم": "مواسير بلاستيك UPVC قطر 200 مم", "الكمية": 1.05, "الوحدة": "م.ط", "سعر_الوحدة": 180.0}, |
|
{"الاسم": "وصلات ومثبتات", "الكمية": 1, "الوحدة": "مجموعة", "سعر_الوحدة": 35.0}, |
|
{"الاسم": "مواد لاصقة", "الكمية": 0.1, "الوحدة": "لتر", "سعر_الوحدة": 120.0} |
|
], |
|
"labor": [ |
|
{"النوع": "سباك", "العدد": 2, "المدة": 0.2, "سعر_اليوم": 250.0}, |
|
{"النوع": "مساعد سباك", "العدد": 2, "المدة": 0.2, "سعر_اليوم": 120.0} |
|
], |
|
"equipment": [ |
|
{"النوع": "حفار صغير", "العدد": 1, "المدة": 0.1, "سعر_اليوم": 1200.0} |
|
] |
|
}, |
|
"admin_expenses": 0.05, |
|
"profit_margin": 0.12, |
|
"tags": ["صرف صحي", "مواسير", "بلاستيك"] |
|
}, |
|
|
|
|
|
"طبقة_أساس_للطرق": { |
|
"category": "أعمال_طرق", |
|
"name": "طبقة أساس للطرق", |
|
"description": "توريد وفرد ودمك طبقة أساس للطرق سمك 20 سم، درجة دمك 98%", |
|
"unit": "م3", |
|
"components": { |
|
"materials": [ |
|
{"الاسم": "مواد طبقة أساس", "الكمية": 1.25, "الوحدة": "م3", "سعر_الوحدة": 90.0}, |
|
{"الاسم": "مياه للدمك", "الكمية": 0.2, "الوحدة": "م3", "سعر_الوحدة": 10.0} |
|
], |
|
"labor": [ |
|
{"النوع": "عامل طرق", "العدد": 4, "المدة": 0.05, "سعر_اليوم": 150.0}, |
|
{"النوع": "مراقب فني", "العدد": 1, "المدة": 0.05, "سعر_اليوم": 300.0} |
|
], |
|
"equipment": [ |
|
{"النوع": "جريدر", "العدد": 1, "المدة": 0.05, "سعر_اليوم": 2200.0}, |
|
{"النوع": "رصاصة دمك", "العدد": 1, "المدة": 0.05, "سعر_اليوم": 1800.0}, |
|
{"النوع": "شاحنة نقل", "العدد": 2, "المدة": 0.05, "سعر_اليوم": 1200.0} |
|
] |
|
}, |
|
"admin_expenses": 0.05, |
|
"profit_margin": 0.12, |
|
"tags": ["طرق", "أساس", "دمك"] |
|
}, |
|
"طبقة_إسفلت_سطحية": { |
|
"category": "أعمال_طرق", |
|
"name": "طبقة إسفلت سطحية", |
|
"description": "توريد وفرد ودمك طبقة إسفلت سطحية سمك 5 سم", |
|
"unit": "م2", |
|
"components": { |
|
"materials": [ |
|
{"الاسم": "خلطة إسفلتية ساخنة", "الكمية": 0.125, "الوحدة": "طن", "سعر_الوحدة": 400.0}, |
|
{"الاسم": "مواد رش تأسيسي", "الكمية": 0.5, "الوحدة": "لتر", "سعر_الوحدة": 8.0} |
|
], |
|
"labor": [ |
|
{"النوع": "عامل طرق", "العدد": 6, "المدة": 0.01, "سعر_اليوم": 150.0}, |
|
{"النوع": "مراقب فني", "العدد": 1, "المدة": 0.01, "سعر_اليوم": 300.0} |
|
], |
|
"equipment": [ |
|
{"النوع": "فرادة إسفلت", "العدد": 1, "المدة": 0.01, "سعر_اليوم": 4000.0}, |
|
{"النوع": "رصاصة دمك", "العدد": 2, "المدة": 0.01, "سعر_اليوم": 1800.0}, |
|
{"النوع": "سيارة رش إسفلت", "العدد": 1, "المدة": 0.01, "سعر_اليوم": 2000.0}, |
|
{"النوع": "شاحنة نقل", "العدد": 4, "المدة": 0.01, "سعر_اليوم": 1200.0} |
|
] |
|
}, |
|
"admin_expenses": 0.05, |
|
"profit_margin": 0.12, |
|
"tags": ["طرق", "إسفلت", "سطحية"] |
|
}, |
|
|
|
|
|
"عمود_إنارة_10_متر": { |
|
"category": "أعمال_كهربائية", |
|
"name": "عمود إنارة 10 متر", |
|
"description": "توريد وتركيب عمود إنارة جلفانيزي بارتفاع 10 متر مع ذراع مفردة وكشاف LED بقدرة 150 واط", |
|
"unit": "عدد", |
|
"components": { |
|
"materials": [ |
|
{"الاسم": "عمود إنارة جلفانيزي 10 متر", "الكمية": 1, "الوحدة": "عدد", "سعر_الوحدة": 3500.0}, |
|
{"الاسم": "ذراع إنارة مفردة", "الكمية": 1, "الوحدة": "عدد", "سعر_الوحدة": 450.0}, |
|
{"الاسم": "كشاف LED 150 واط", "الكمية": 1, "الوحدة": "عدد", "سعر_الوحدة": 850.0}, |
|
{"الاسم": "كابل كهرباء 3×4 مم²", "الكمية": 15, "الوحدة": "م.ط", "سعر_الوحدة": 32.0}, |
|
{"الاسم": "قاعدة خرسانية مسلحة", "الكمية": 0.25, "الوحدة": "م3", "سعر_الوحدة": 750.0} |
|
], |
|
"labor": [ |
|
{"النوع": "كهربائي", "العدد": 2, "المدة": 1, "سعر_اليوم": 270.0}, |
|
{"النوع": "مساعد كهربائي", "العدد": 2, "المدة": 1, "سعر_اليوم": 120.0}, |
|
{"النوع": "عامل خرسانة", "العدد": 2, "المدة": 0.5, "سعر_اليوم": 150.0} |
|
], |
|
"equipment": [ |
|
{"النوع": "ونش شوكة", "العدد": 1, "المدة": 0.5, "سعر_اليوم": 1500.0}, |
|
{"النوع": "حفار صغير", "العدد": 1, "المدة": 0.2, "سعر_اليوم": 1200.0} |
|
] |
|
}, |
|
"admin_expenses": 0.05, |
|
"profit_margin": 0.12, |
|
"tags": ["كهرباء", "إنارة", "LED"] |
|
} |
|
} |
|
} |
|
|
|
return templates |
|
|
|
def _create_default_market_prices(self) -> Dict[str, Dict[str, Any]]: |
|
"""إنشاء بيانات افتراضية لأسعار السوق السعودي""" |
|
current_date = datetime.now().strftime("%Y-%m-%d") |
|
|
|
prices = { |
|
"metadata": { |
|
"last_update": current_date, |
|
"source": "أسعار السوق السعودي الافتراضية", |
|
"disclaimer": "هذه الأسعار تقريبية وقد تختلف حسب المنطقة والكميات والموردين" |
|
}, |
|
"materials": { |
|
|
|
"خرسانة_جاهزة": { |
|
"name": "خرسانة جاهزة", |
|
"unit": "م3", |
|
"current_price": 750.0, |
|
"previous_price": 730.0, |
|
"price_trend": "up", |
|
"category": "أعمال خرسانية", |
|
"specifications": "خرسانة جاهزة بقوة 350 كجم/سم2", |
|
"note": "السعر يشمل توريد فقط، الضخ بتكلفة إضافية", |
|
"price_history": [ |
|
{"date": "2023-06-01", "price": 700.0}, |
|
{"date": "2023-09-01", "price": 715.0}, |
|
{"date": "2023-12-01", "price": 730.0}, |
|
{"date": current_date, "price": 750.0} |
|
] |
|
}, |
|
"حديد_تسليح": { |
|
"name": "حديد تسليح", |
|
"unit": "طن", |
|
"current_price": 5500.0, |
|
"previous_price": 5200.0, |
|
"price_trend": "up", |
|
"category": "أعمال خرسانية", |
|
"specifications": "حديد تسليح قطر 8-32 مم، انتاج سابك", |
|
"note": "السعر يتغير بشكل دوري حسب أسعار الحديد العالمية", |
|
"price_history": [ |
|
{"date": "2023-06-01", "price": 4800.0}, |
|
{"date": "2023-09-01", "price": 5000.0}, |
|
{"date": "2023-12-01", "price": 5200.0}, |
|
{"date": current_date, "price": 5500.0} |
|
] |
|
}, |
|
"أسمنت": { |
|
"name": "أسمنت", |
|
"unit": "كيس", |
|
"current_price": 30.0, |
|
"previous_price": 28.0, |
|
"price_trend": "up", |
|
"category": "أعمال خرسانية", |
|
"specifications": "أسمنت بورتلاندي عادي، كيس 50 كجم", |
|
"note": "السعر للكميات الكبيرة", |
|
"price_history": [ |
|
{"date": "2023-06-01", "price": 25.0}, |
|
{"date": "2023-09-01", "price": 27.0}, |
|
{"date": "2023-12-01", "price": 28.0}, |
|
{"date": current_date, "price": 30.0} |
|
] |
|
}, |
|
|
|
|
|
"خلطة_إسفلتية_ساخنة": { |
|
"name": "خلطة إسفلتية ساخنة", |
|
"unit": "طن", |
|
"current_price": 400.0, |
|
"previous_price": 380.0, |
|
"price_trend": "up", |
|
"category": "أعمال طرق", |
|
"specifications": "خلطة إسفلتية ساخنة للطبقة السطحية", |
|
"note": "السعر يشمل التوريد من المصنع، النقل بتكلفة إضافية", |
|
"price_history": [ |
|
{"date": "2023-06-01", "price": 350.0}, |
|
{"date": "2023-09-01", "price": 370.0}, |
|
{"date": "2023-12-01", "price": 380.0}, |
|
{"date": current_date, "price": 400.0} |
|
] |
|
}, |
|
|
|
|
|
"مواسير_بلاستيك_UPVC": { |
|
"name": "مواسير بلاستيك UPVC قطر 200 مم", |
|
"unit": "م.ط", |
|
"current_price": 180.0, |
|
"previous_price": 165.0, |
|
"price_trend": "up", |
|
"category": "أعمال صحية", |
|
"specifications": "مواسير بلاستيك UPVC قطر 200 مم لشبكات الصرف الصحي", |
|
"note": "السعر للكميات الكبيرة", |
|
"price_history": [ |
|
{"date": "2023-06-01", "price": 150.0}, |
|
{"date": "2023-09-01", "price": 160.0}, |
|
{"date": "2023-12-01", "price": 165.0}, |
|
{"date": current_date, "price": 180.0} |
|
] |
|
}, |
|
|
|
|
|
"كشاف_LED": { |
|
"name": "كشاف LED 150 واط", |
|
"unit": "عدد", |
|
"current_price": 850.0, |
|
"previous_price": 820.0, |
|
"price_trend": "up", |
|
"category": "أعمال كهربائية", |
|
"specifications": "كشاف إنارة LED بقدرة 150 واط للاستخدام الخارجي، IP65", |
|
"note": "السعر شامل الضريبة", |
|
"price_history": [ |
|
{"date": "2023-06-01", "price": 780.0}, |
|
{"date": "2023-09-01", "price": 800.0}, |
|
{"date": "2023-12-01", "price": 820.0}, |
|
{"date": current_date, "price": 850.0} |
|
] |
|
} |
|
}, |
|
"labor": { |
|
"عامل_خرسانة": { |
|
"name": "عامل خرسانة", |
|
"unit": "يوم", |
|
"current_price": 150.0, |
|
"previous_price": 140.0, |
|
"price_trend": "up", |
|
"category": "عمالة", |
|
"specifications": "عامل لصب وتسوية الخرسانة", |
|
"note": "أجرة اليوم الواحد لا تشمل السكن والمواصلات", |
|
"price_history": [ |
|
{"date": "2023-06-01", "price": 130.0}, |
|
{"date": "2023-09-01", "price": 135.0}, |
|
{"date": "2023-12-01", "price": 140.0}, |
|
{"date": current_date, "price": 150.0} |
|
] |
|
}, |
|
"مهندس_موقع": { |
|
"name": "مهندس موقع", |
|
"unit": "يوم", |
|
"current_price": 500.0, |
|
"previous_price": 480.0, |
|
"price_trend": "up", |
|
"category": "إشراف", |
|
"specifications": "مهندس إشراف موقع", |
|
"note": "أجرة اليوم الواحد لا تشمل السكن والمواصلات", |
|
"price_history": [ |
|
{"date": "2023-06-01", "price": 450.0}, |
|
{"date": "2023-09-01", "price": 470.0}, |
|
{"date": "2023-12-01", "price": 480.0}, |
|
{"date": current_date, "price": 500.0} |
|
] |
|
} |
|
}, |
|
"equipment": { |
|
"حفار_صغير": { |
|
"name": "حفار صغير", |
|
"unit": "يوم", |
|
"current_price": 1200.0, |
|
"previous_price": 1150.0, |
|
"price_trend": "up", |
|
"category": "معدات حفر", |
|
"specifications": "حفار صغير (بوبكات) بقدرة 70 حصان", |
|
"note": "السعر يشمل المشغل والوقود", |
|
"price_history": [ |
|
{"date": "2023-06-01", "price": 1100.0}, |
|
{"date": "2023-09-01", "price": 1120.0}, |
|
{"date": "2023-12-01", "price": 1150.0}, |
|
{"date": current_date, "price": 1200.0} |
|
] |
|
}, |
|
"فرادة_إسفلت": { |
|
"name": "فرادة إسفلت", |
|
"unit": "يوم", |
|
"current_price": 4000.0, |
|
"previous_price": 3800.0, |
|
"price_trend": "up", |
|
"category": "معدات طرق", |
|
"specifications": "فرادة إسفلت بعرض 3 متر", |
|
"note": "السعر يشمل المشغل والوقود", |
|
"price_history": [ |
|
{"date": "2023-06-01", "price": 3500.0}, |
|
{"date": "2023-09-01", "price": 3650.0}, |
|
{"date": "2023-12-01", "price": 3800.0}, |
|
{"date": current_date, "price": 4000.0} |
|
] |
|
} |
|
} |
|
} |
|
|
|
return prices |
|
|
|
def get_all_templates(self) -> Dict[str, Dict[str, Any]]: |
|
"""الحصول على جميع القوالب النموذجية""" |
|
return self.templates |
|
|
|
def get_templates_by_category(self, category_id: str) -> List[Dict[str, Any]]: |
|
"""الحصول على القوالب النموذجية حسب الفئة""" |
|
result = [] |
|
|
|
|
|
if category_id not in self.templates["categories"]: |
|
return result |
|
|
|
|
|
for template_id, template in self.templates["templates"].items(): |
|
if template["category"] == category_id: |
|
template_copy = template.copy() |
|
template_copy["id"] = template_id |
|
result.append(template_copy) |
|
|
|
return result |
|
|
|
def get_template_by_id(self, template_id: str) -> Optional[Dict[str, Any]]: |
|
"""الحصول على قالب نموذجي بواسطة المعرف""" |
|
if template_id in self.templates["templates"]: |
|
template = self.templates["templates"][template_id].copy() |
|
template["id"] = template_id |
|
return template |
|
|
|
return None |
|
|
|
def add_template(self, template_data: Dict[str, Any]) -> str: |
|
"""إضافة قالب نموذجي جديد""" |
|
|
|
template_name = template_data.get("name", "").strip() |
|
if not template_name: |
|
raise ValueError("يجب تحديد اسم القالب") |
|
|
|
|
|
import re |
|
template_id = re.sub(r'[^\w\s]', '', template_name) |
|
template_id = template_id.replace(" ", "_") |
|
|
|
|
|
import random |
|
if template_id in self.templates["templates"]: |
|
template_id = f"{template_id}_{random.randint(1000, 9999)}" |
|
|
|
|
|
self.templates["templates"][template_id] = template_data |
|
|
|
|
|
self._save_templates(self.templates) |
|
|
|
return template_id |
|
|
|
def update_template(self, template_id: str, template_data: Dict[str, Any]) -> bool: |
|
"""تحديث قالب نموذجي موجود""" |
|
if template_id not in self.templates["templates"]: |
|
return False |
|
|
|
|
|
self.templates["templates"][template_id] = template_data |
|
|
|
|
|
self._save_templates(self.templates) |
|
|
|
return True |
|
|
|
def delete_template(self, template_id: str) -> bool: |
|
"""حذف قالب نموذجي""" |
|
if template_id not in self.templates["templates"]: |
|
return False |
|
|
|
|
|
del self.templates["templates"][template_id] |
|
|
|
|
|
self._save_templates(self.templates) |
|
|
|
return True |
|
|
|
def get_market_prices(self, category: Optional[str] = None, item_type: Optional[str] = None) -> Dict[str, Any]: |
|
"""الحصول على أسعار السوق السعودي""" |
|
result = { |
|
"metadata": self.market_prices["metadata"] |
|
} |
|
|
|
|
|
sections = [] |
|
if item_type: |
|
if item_type in ["materials", "المواد"]: |
|
sections = ["materials"] |
|
elif item_type in ["labor", "العمالة"]: |
|
sections = ["labor"] |
|
elif item_type in ["equipment", "المعدات"]: |
|
sections = ["equipment"] |
|
else: |
|
sections = ["materials", "labor", "equipment"] |
|
|
|
|
|
for section in sections: |
|
result[section] = {} |
|
for item_id, item_data in self.market_prices[section].items(): |
|
if not category or (item_data.get("category", "") == category): |
|
result[section][item_id] = item_data |
|
|
|
return result |
|
|
|
def update_market_price(self, item_type: str, item_id: str, new_price: float) -> bool: |
|
"""تحديث سعر في قائمة أسعار السوق""" |
|
section = "" |
|
if item_type in ["materials", "المواد"]: |
|
section = "materials" |
|
elif item_type in ["labor", "العمالة"]: |
|
section = "labor" |
|
elif item_type in ["equipment", "المعدات"]: |
|
section = "equipment" |
|
else: |
|
return False |
|
|
|
if item_id not in self.market_prices[section]: |
|
return False |
|
|
|
|
|
current_price = self.market_prices[section][item_id]["current_price"] |
|
self.market_prices[section][item_id]["previous_price"] = current_price |
|
self.market_prices[section][item_id]["current_price"] = new_price |
|
|
|
|
|
if new_price > current_price: |
|
self.market_prices[section][item_id]["price_trend"] = "up" |
|
elif new_price < current_price: |
|
self.market_prices[section][item_id]["price_trend"] = "down" |
|
else: |
|
self.market_prices[section][item_id]["price_trend"] = "stable" |
|
|
|
|
|
current_date = datetime.now().strftime("%Y-%m-%d") |
|
self.market_prices[section][item_id]["price_history"].append({ |
|
"date": current_date, |
|
"price": new_price |
|
}) |
|
|
|
|
|
self.market_prices["metadata"]["last_update"] = current_date |
|
|
|
|
|
self._save_market_prices(self.market_prices) |
|
|
|
return True |
|
|
|
def add_market_price_item(self, item_type: str, item_data: Dict[str, Any]) -> str: |
|
"""إضافة عنصر جديد إلى قائمة أسعار السوق""" |
|
section = "" |
|
if item_type in ["materials", "المواد"]: |
|
section = "materials" |
|
elif item_type in ["labor", "العمالة"]: |
|
section = "labor" |
|
elif item_type in ["equipment", "المعدات"]: |
|
section = "equipment" |
|
else: |
|
raise ValueError("نوع العنصر غير صحيح") |
|
|
|
|
|
if "name" not in item_data or "current_price" not in item_data or "unit" not in item_data: |
|
raise ValueError("يجب تحديد الاسم والسعر الحالي والوحدة") |
|
|
|
|
|
item_name = item_data["name"].strip() |
|
import re |
|
item_id = re.sub(r'[^\w\s]', '', item_name) |
|
item_id = item_id.replace(" ", "_") |
|
|
|
|
|
import random |
|
if item_id in self.market_prices[section]: |
|
item_id = f"{item_id}_{random.randint(1000, 9999)}" |
|
|
|
|
|
current_date = datetime.now().strftime("%Y-%m-%d") |
|
new_item = { |
|
"name": item_name, |
|
"unit": item_data["unit"], |
|
"current_price": item_data["current_price"], |
|
"previous_price": item_data.get("previous_price", item_data["current_price"]), |
|
"price_trend": "stable", |
|
"category": item_data.get("category", ""), |
|
"specifications": item_data.get("specifications", ""), |
|
"note": item_data.get("note", ""), |
|
"price_history": [ |
|
{"date": current_date, "price": item_data["current_price"]} |
|
] |
|
} |
|
|
|
|
|
self.market_prices[section][item_id] = new_item |
|
|
|
|
|
self.market_prices["metadata"]["last_update"] = current_date |
|
|
|
|
|
self._save_market_prices(self.market_prices) |
|
|
|
return item_id |
|
|
|
def convert_template_to_item(self, template_id: str) -> Dict[str, Any]: |
|
"""تحويل قالب نموذجي إلى بند للاستخدام في حاسبة تكاليف البناء""" |
|
template = self.get_template_by_id(template_id) |
|
if not template: |
|
raise ValueError("القالب غير موجود") |
|
|
|
|
|
item = { |
|
"وصف_البند": template["description"], |
|
"الكمية": 1.0, |
|
"الوحدة": template["unit"], |
|
"المواد": template["components"]["materials"], |
|
"العمالة": template["components"]["labor"], |
|
"المعدات": template["components"]["equipment"], |
|
"المصاريف_الإدارية": template["admin_expenses"], |
|
"هامش_الربح": template["profit_margin"], |
|
"عوامل_التعديل": { |
|
"location_factor": 1.0, |
|
"time_factor": 1.0, |
|
"risk_factor": 1.0, |
|
"market_factor": 1.0 |
|
} |
|
} |
|
|
|
return item |