EGYADMIN commited on
Commit
e18a4aa
·
verified ·
1 Parent(s): 60c64fb

Delete utils

Browse files
utils/assets/logo.svg DELETED
utils/components/about_system.py DELETED
@@ -1,262 +0,0 @@
1
- """
2
- مكون عرض معلومات حول النظام
3
- """
4
-
5
- import streamlit as st
6
-
7
-
8
- def display_about_system():
9
- """
10
- عرض معلومات حول النظام وميزاته
11
- """
12
- # مربع معلومات النظام
13
- st.markdown("""
14
- <div class="about-system">
15
- <h2>نبذة عن النظام</h2>
16
- <p>
17
- نظام WAHBi AI هو نظام متكامل لتحليل العقود والمناقصات باستخدام تقنيات الذكاء الاصطناعي المتقدمة.
18
- تم تطوير النظام خصيصًا لشركة شبه الجزيرة للمقاولات لتمكينها من تحليل وثائق المناقصات، وتسعير المشاريع، وتقييم المخاطر،
19
- وإدارة الموارد بكفاءة عالية.
20
- </p>
21
-
22
- <h2>المميزات الرئيسية</h2>
23
- <ul>
24
- <li><strong>تحليل المستندات ذكيًا</strong> - استخراج البنود والكميات والمخاطر من المستندات تلقائيًا</li>
25
- <li><strong>حاسبة تكاليف البناء المتكاملة</strong> - حساب التكاليف التفصيلية للمواد الخام والمعدات والعمالة والمصاريف الإدارية وهوامش الربح</li>
26
- <li><strong>تحليل الأسعار غير المتزنة</strong> - تحليل ومقارنة الأسعار مع متوسطات السوق لتحقيق أقصى استفادة</li>
27
- <li><strong>تتبع المعدات والموارد</strong> - متابعة وتنظيم موارد الشركة وتخصيصها للمشاريع المختلفة</li>
28
- <li><strong>تحليل المخاطر</strong> - تحديد المخاطر المحتملة في المشاريع وتقييم تأثيرها واقتراح إجراءات التخفيف</li>
29
- <li><strong>مؤشرات أداء المشاريع</strong> - متابعة الأداء المالي والفني للمشاريع وقياس الإنجاز</li>
30
- <li><strong>حاسبة المحتوى المحلي</strong> - حساب وتحسين نسبة المحتوى المحلي في المشاريع لتلبية متطلبات المملكة</li>
31
- <li><strong>التنبؤ بأسعار المواد</strong> - استخدام نماذج التعلم الآلي للتنبؤ بتغيرات الأسعار المستقبلية</li>
32
- <li><strong>مؤقت مواعيد التسليم</strong> - متابعة مواعيد بدء الدراسة ومواعيد تسليم العروض للمشاريع</li>
33
- <li><strong>دعم اللغة العربية</strong> - واجهة مستخدم باللغة العربية مع إمكانية التبديل للغة الإنجليزية</li>
34
- <li><strong>توافق متعدد الأجهزة</strong> - يعمل على جميع الأجهزة (أيفون، أندرويد، أجهزة لوحية، حواسيب مكتبية)</li>
35
- </ul>
36
-
37
- <h2>معلومات الاتصال</h2>
38
- <div class="contact-info">
39
- <p><strong>العنوان:</strong> المملكة العربية السعودية - الرياض - حي الملز - شارع السبالة</p>
40
- <p><strong>البريد الإلكتروني:</strong> [email protected]</p>
41
- <p><strong>هاتف:</strong> +966 12 345 6789</p>
42
- <p><strong>الموقع الإلكتروني:</strong> www.peninsula-contracting.com</p>
43
- </div>
44
-
45
- <h2>فريق التطوير</h2>
46
- <p>
47
- تم تطوير هذا النظام بواسطة فريق متخصص من المهندسين والمطورين بقيادة م. بدر وهبي،
48
- بالتعاون مع فريق التسعير والمشاريع في شركة شبه الجزيرة للمقاولات.
49
- </p>
50
-
51
- <h2>الإصدار والترخيص</h2>
52
- <p>
53
- <strong>رقم الإصدار:</strong> 2.5.0 (مارس 2025)<br>
54
- <strong>الترخيص:</strong> جميع الحقوق محفوظة © 2025 شركة شبه الجزيرة للمقاولات
55
- </p>
56
- </div>
57
- """, unsafe_allow_html=True)
58
-
59
- # أضف مساحة أسفل المحتوى
60
- st.markdown("<br><br>", unsafe_allow_html=True)
61
-
62
- # إضافة أزرار تفاعلية
63
- col1, col2, col3 = st.columns(3)
64
-
65
- with col1:
66
- st.button("تحميل دليل المستخدم")
67
-
68
- with col2:
69
- st.button("الدعم الفني")
70
-
71
- with col3:
72
- st.button("التحديثات والمزايا القادمة")
73
-
74
-
75
- def display_settings():
76
- """
77
- عرض صفحة الإعدادات
78
- """
79
- st.markdown("<h2 class='module-title'>إعدادات النظام</h2>", unsafe_allow_html=True)
80
-
81
- # تقسيم الصفحة إلى قسمين
82
- col1, col2 = st.columns(2)
83
-
84
- with col1:
85
- st.markdown("""
86
- <div class="settings-form">
87
- <div class="settings-group">
88
- <h3>خيارات اللغة</h3>
89
- <div class="settings-item">
90
- <label>لغة الواجهة</label>
91
- </div>
92
- </div>
93
- </div>
94
- """, unsafe_allow_html=True)
95
-
96
- language = st.selectbox(
97
- "اختر لغة الواجهة",
98
- options=["العربية", "English"],
99
- index=0,
100
- label_visibility="collapsed"
101
- )
102
-
103
- st.markdown("""
104
- <div class="settings-form">
105
- <div class="settings-group">
106
- <h3>الإشعارات</h3>
107
- <div class="settings-item">
108
- <label>إعدادات الإشعارات</label>
109
- </div>
110
- </div>
111
- </div>
112
- """, unsafe_allow_html=True)
113
-
114
- notifications_email = st.checkbox("إشعارات البريد الإلكتروني", value=True)
115
- notifications_sms = st.checkbox("إشعارات الرسائل النصية SMS", value=False)
116
- notifications_system = st.checkbox("إشعارات النظام", value=True)
117
-
118
- if st.button("حفظ الإعدادات"):
119
- st.success("تم حفظ الإعدادات بنجاح")
120
-
121
- with col2:
122
- st.markdown("""
123
- <div class="settings-form">
124
- <div class="settings-group">
125
- <h3>معلومات الحساب</h3>
126
- </div>
127
- </div>
128
- """, unsafe_allow_html=True)
129
-
130
- username = st.text_input("اسم المستخدم", value="admin")
131
- email = st.text_input("البريد الإلكتروني", value="[email protected]")
132
-
133
- st.markdown("""
134
- <div class="settings-form" style="margin-top: 20px;">
135
- <div class="settings-group">
136
- <h3>تغيير كلمة المرور</h3>
137
- </div>
138
- </div>
139
- """, unsafe_allow_html=True)
140
-
141
- current_password = st.text_input("كلمة المرور الحالية", type="password")
142
- new_password = st.text_input("كلمة المرور الجديدة", type="password")
143
- confirm_password = st.text_input("تأكيد كلمة المرور الجديدة", type="password")
144
-
145
- if st.button("تغيير كلمة المرور"):
146
- if not current_password or not new_password or not confirm_password:
147
- st.error("يرجى ملء جميع الحقول")
148
- elif new_password != confirm_password:
149
- st.error("كلمات المرور غير متطابقة")
150
- else:
151
- st.success("تم تغيير كلمة المرور بنجاح")
152
-
153
- # إضافة مزيد من الإعدادات
154
- st.markdown("<hr>", unsafe_allow_html=True)
155
- st.markdown("<h3>إعدادات النظام المتقدمة</h3>", unsafe_allow_html=True)
156
-
157
- col3, col4 = st.columns(2)
158
-
159
- with col3:
160
- theme = st.selectbox(
161
- "سمة النظام",
162
- options=["الافتراضية", "الوضع الفاتح", "الوضع الداكن"]
163
- )
164
-
165
- date_format = st.selectbox(
166
- "تنسيق التاريخ",
167
- options=["DD/MM/YYYY", "MM/DD/YYYY", "YYYY-MM-DD"]
168
- )
169
-
170
- with col4:
171
- currency = st.selectbox(
172
- "العملة الافتراضية",
173
- options=["ريال سعودي (SAR)", "دولار أمريكي (USD)", "يورو (EUR)"]
174
- )
175
-
176
- notifications_frequency = st.selectbox(
177
- "تكرار الإشعارات",
178
- options=["فوري", "يومي", "أسبوعي"]
179
- )
180
-
181
-
182
- def display_countdown_timer():
183
- """
184
- عرض مؤقت العد التنازلي للمواعيد النهائية
185
- """
186
- st.markdown("<h3>مواعيد المناقصات</h3>", unsafe_allow_html=True)
187
-
188
- # بيانات المواعيد
189
- deadlines = [
190
- {
191
- "name": "مناقصة توسعة مستشفى الملك فهد",
192
- "submission_date": "15 أبريل 2025",
193
- "days_left": 15,
194
- "start_date": "1 مارس 2025"
195
- },
196
- {
197
- "name": "مناقصة إنشاء مبنى كلية الطب",
198
- "submission_date": "30 مارس 2025",
199
- "days_left": 0,
200
- "start_date": "15 فبراير 2025"
201
- },
202
- {
203
- "name": "مناقصة طريق الدائري الشمالي",
204
- "submission_date": "10 مايو 2025",
205
- "days_left": 40,
206
- "start_date": "5 مارس 2025"
207
- }
208
- ]
209
-
210
- for i, deadline in enumerate(deadlines):
211
- # تحديد لون المؤقت بناءً على عدد الأيام المتبقية
212
- color_class = "danger" if deadline["days_left"] <= 5 else "warning" if deadline["days_left"] <= 15 else "success"
213
-
214
- # عرض معلومات الموعد والمؤقت
215
- st.markdown(f"""
216
- <div class="card" style="margin-bottom: 15px;">
217
- <h4>{deadline["name"]}</h4>
218
- <div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
219
- <div>
220
- <small style="color: var(--text-light);">بدء الدراسة: {deadline["start_date"]}</small>
221
- </div>
222
- <div>
223
- <small style="color: var(--text-light);">تاريخ التسليم: {deadline["submission_date"]}</small>
224
- </div>
225
- </div>
226
- <div class="progress" style="margin-bottom: 5px;">
227
- <div class="progress-bar progress-{color_class}" style="width: {100 - min(100, deadline["days_left"] * 2)}%;"></div>
228
- </div>
229
- <div style="display: flex; justify-content: space-between; align-items: center;">
230
- <div>
231
- <small>متبقي: <span class="bold">{deadline["days_left"]} أيام</span></small>
232
- </div>
233
- <div>
234
- <button class="btn-secondary btn-sm">تفاصيل المناقصة</button>
235
- </div>
236
- </div>
237
- </div>
238
- """, unsafe_allow_html=True)
239
-
240
- # عرض مؤقت تفصيلي للمناقصة الأولى
241
- st.markdown("<h4>العد التنازلي للتسليم</h4>", unsafe_allow_html=True)
242
-
243
- st.markdown("""
244
- <div class="countdown-timer">
245
- <div class="time-block">
246
- <div class="time-value">15</div>
247
- <div class="time-label">يوم</div>
248
- </div>
249
- <div class="time-block">
250
- <div class="time-value">08</div>
251
- <div class="time-label">ساعة</div>
252
- </div>
253
- <div class="time-block">
254
- <div class="time-value">45</div>
255
- <div class="time-label">دقيقة</div>
256
- </div>
257
- <div class="time-block">
258
- <div class="time-value">20</div>
259
- <div class="time-label">ثانية</div>
260
- </div>
261
- </div>
262
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/components/credits.py DELETED
@@ -1,115 +0,0 @@
1
- """
2
- مكون عرض معلومات فريق التطوير
3
- """
4
-
5
- import streamlit as st
6
-
7
- # تصدير واضح للدالة لجعلها متاحة للاستيراد
8
- __all__ = ['render_credits', 'display_credits']
9
-
10
-
11
- def render_credits():
12
- """
13
- عرض معلومات فريق التطوير (واجهة للاستخدام مع وحدات النظام المختلفة)
14
- """
15
- display_credits()
16
-
17
-
18
- def display_credits():
19
- """
20
- عرض معلومات فريق التطوير
21
- """
22
- # تعريف بيانات الفريق
23
- team_members = [
24
- {
25
- "name": "م بدر وهبي",
26
- "role": "مدير المشروع",
27
- "image": "badr.jpg",
28
- "bio": "مهندس برمجيات ذو خبرة 15 عامًا في تطوير أنظمة المقاولات والتسعير"
29
- },
30
- {
31
- "name": "م تامر الجوهري",
32
- "role": "مهندسة الذكاء الاصطناعي",
33
- "image": "tamer.jpg",
34
- "bio": "متخصصة في معالجة اللغة العربية الطبيعية وتحليل البيانات"
35
- },
36
- {
37
- "name": "م اسلام عيسي",
38
- "role": "أخصائي تطوير التسعير",
39
- "image": "Islam.jpg",
40
- "bio": "خبير في أنظمة التسعير المتقدمة والتحليل المالي للمشاريع"
41
- }
42
- ]
43
-
44
- # عرض معلومات الفريق في صفوف
45
- # كل صف يحتوي على 3 أعضاء
46
- cols_per_row = 3
47
-
48
- for i in range(0, len(team_members), cols_per_row):
49
- # إنشاء الأعمدة
50
- cols = st.columns(cols_per_row)
51
-
52
- # عرض الأعضاء في هذا الصف
53
- for j in range(cols_per_row):
54
- idx = i + j
55
- if idx < len(team_members):
56
- member = team_members[idx]
57
-
58
- with cols[j]:
59
- # عرض صورة العضو (استخدام صورة افتراضية إذا لم تكن متوفرة)
60
- try:
61
- st.image(f"static/images/team/{member['image']}", width=150)
62
- except:
63
- # استخدام الأحرف الأولى من الاسم كصورة افتراضية
64
- initials = ''.join([name[0] for name in member['name'].split() if name.startswith('م.') == False])
65
- st.markdown(f"""
66
- <div class="avatar">
67
- <span>{initials}</span>
68
- </div>
69
- """, unsafe_allow_html=True)
70
-
71
- # عرض معلومات العضو
72
- st.markdown(f"""
73
- <div class="team-member">
74
- <h3>{member['name']}</h3>
75
- <h4>{member['role']}</h4>
76
- <p>{member['bio']}</p>
77
- </div>
78
- """, unsafe_allow_html=True)
79
-
80
- # إضافة أسلوب CSS للعرض
81
- st.markdown("""
82
- <style>
83
- .team-member {
84
- text-align: center;
85
- margin-bottom: 20px;
86
- }
87
- .team-member h3 {
88
- color: #333;
89
- margin-bottom: 5px;
90
- font-size: 18px;
91
- }
92
- .team-member h4 {
93
- color: #ff9a3c;
94
- margin-top: 0;
95
- margin-bottom: 10px;
96
- font-size: 14px;
97
- }
98
- .team-member p {
99
- color: #666;
100
- font-size: 12px;
101
- }
102
- .avatar {
103
- background-color: #ff9a3c;
104
- color: white;
105
- width: 100px;
106
- height: 100px;
107
- border-radius: 50%;
108
- display: flex;
109
- justify-content: center;
110
- align-items: center;
111
- margin: 0 auto 15px auto;
112
- font-size: 36px;
113
- }
114
- </style>
115
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/components/header.py DELETED
@@ -1,146 +0,0 @@
1
- """
2
- مكون ترويسة الصفحة المطور 2025
3
- """
4
-
5
- import streamlit as st
6
- from datetime import datetime
7
- import config
8
-
9
-
10
- def render_header(page_title=None):
11
- """
12
- عرض ترويسة الصفحة المحسنة
13
-
14
- الوسيطات:
15
- page_title: عنوان الصفحة المعروضة (اختياري)
16
- """
17
- # إنشاء مكون الترويسة باستخدام HTML
18
- title_display = "نظام تحليل العقود والمناقصات"
19
- # إذا تم تمرير عنوان للصفحة، قم بإضافته للعنوان الرئيسي
20
- if page_title:
21
- title_display = f"نظام تحليل العقود والمناقصات: {page_title}"
22
-
23
- header_html = """
24
- <div class="header-container">
25
- <div class="header-title">
26
- <div class="logo">
27
- <span class="logo-text">WAHBi AI</span>
28
- </div>
29
- <h1>{title}</h1>
30
- <p>الحلول الشاملة للتسعير والتحليل بالذكاء الاصطناعي - شركة شبه الجزيرة للمقاولات</p>
31
- </div>
32
- <div class="header-info">
33
- <div class="date-box">
34
- <div class="date-day">{day}</div>
35
- <div class="date-info">
36
- <div class="date-month">{month}</div>
37
- <div class="date-year">{year}</div>
38
- </div>
39
- </div>
40
- </div>
41
- </div>
42
- """
43
-
44
- # الحصول على معلومات التاريخ الحالي
45
- today = datetime.now()
46
- day = today.day
47
- month_names = [
48
- "يناير", "فبراير", "مارس", "إبريل", "مايو", "يونيو",
49
- "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"
50
- ]
51
- month = month_names[today.month - 1]
52
- year = today.year
53
-
54
- # استبدال القيم في قالب HTML
55
- header_html = header_html.format(title=title_display, day=day, month=month, year=year)
56
-
57
- # عرض الترويسة
58
- st.markdown(header_html, unsafe_allow_html=True)
59
-
60
- # إضافة شريط التنقل الرئيسي
61
- if 'is_authenticated' in st.session_state and st.session_state.is_authenticated:
62
- render_navigation_menu()
63
- else:
64
- # عرض قائمة التنقل المختصرة للمستخدمين غير المسجلين
65
- render_basic_navigation_menu()
66
-
67
- # إضافة خط فاصل
68
- st.markdown("<hr>", unsafe_allow_html=True)
69
-
70
-
71
- def render_navigation_menu():
72
- """
73
- عرض قائمة التنقل الرئيسية المحسنة
74
- """
75
- # إنشاء قائمة التنقل المختصرة
76
- menu_items = [
77
- {"icon": "🏠", "label": "الرئيسية", "url": "/?page=home"},
78
- {"icon": "📊", "label": "لوحة المعلومات", "url": "/?page=dashboard"},
79
- {"icon": "📝", "label": "المناقصات", "url": "/?page=tenders"},
80
- {"icon": "💰", "label": "التسعير", "url": "/?page=pricing"},
81
- {"icon": "📈", "label": "التقارير", "url": "/?page=reports"},
82
- {"icon": "⚙️", "label": "الإعدادات", "url": "/?page=settings"},
83
- {"icon": "❓", "label": "المساعدة", "url": "/?page=help"},
84
- {"icon": "ℹ️", "label": "حول النظام", "url": "/?page=about"},
85
- ]
86
-
87
- # إنشاء قائمة HTML
88
- menu_html = """
89
- <div class="nav-menu">
90
- <ul>
91
- """
92
-
93
- for item in menu_items:
94
- menu_html += f"""
95
- <li>
96
- <a href="{item['url']}">
97
- <span class="nav-icon">{item['icon']}</span>
98
- <span class="nav-label">{item['label']}</span>
99
- </a>
100
- </li>
101
- """
102
-
103
- menu_html += """
104
- </ul>
105
- </div>
106
- """
107
-
108
- # عرض قائمة التنقل
109
- st.markdown(menu_html, unsafe_allow_html=True)
110
-
111
-
112
- def render_basic_navigation_menu():
113
- """
114
- عرض قائمة تنقل بسيطة للمستخدمين غير المسجلين
115
- """
116
- # إنشاء قائمة التنقل المختصرة
117
- menu_items = [
118
- {"icon": "🏠", "label": "الرئيسية", "url": "/?page=home"},
119
- {"icon": "🔐", "label": "تسجيل الدخول", "url": "/?page=login"},
120
- {"icon": "❓", "label": "المساعدة", "url": "/?page=help"},
121
- {"icon": "ℹ️", "label": "حول النظام", "url": "/?page=about"},
122
- ]
123
-
124
- # إنشاء قائمة HTML
125
- menu_html = """
126
- <div class="nav-menu">
127
- <ul>
128
- """
129
-
130
- for item in menu_items:
131
- menu_html += f"""
132
- <li>
133
- <a href="{item['url']}">
134
- <span class="nav-icon">{item['icon']}</span>
135
- <span class="nav-label">{item['label']}</span>
136
- </a>
137
- </li>
138
- """
139
-
140
- menu_html += """
141
- </ul>
142
- </div>
143
- """
144
-
145
- # عرض قائمة التنقل
146
- st.markdown(menu_html, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/components/sidebar.py DELETED
@@ -1,171 +0,0 @@
1
- """
2
- مكون الشريط الجانبي - تصميم محسن مع دعم كامل للغة العربية
3
- """
4
-
5
- import streamlit as st
6
- from datetime import datetime
7
- import os
8
- import config
9
- from streamlit_option_menu import option_menu
10
-
11
-
12
- def render_sidebar():
13
- """
14
- عرض وإدارة الشريط الجانبي
15
-
16
- الإرجاع:
17
- اسم الوحدة المحددة
18
- """
19
- with st.sidebar:
20
- # تطبيق الأنماط المحسنة على الشريط الجانبي
21
- st.markdown("""
22
- <style>
23
- .sidebar .sidebar-content {
24
- background: linear-gradient(135deg, #0B6E4F 0%, #08603a 100%);
25
- }
26
- /* المزيد من التنسيقات المخصصة للشريط الجانبي */
27
- .sidebar .block-container {
28
- padding-top: 0rem;
29
- }
30
- div[data-testid="stSidebarNav"] > div:first-child {
31
- margin-top: -2rem;
32
- }
33
- </style>
34
- """, unsafe_allow_html=True)
35
-
36
- # عرض الشعار المحسن
37
- logo_path = "utils/assets/logo.svg"
38
- if os.path.exists(logo_path):
39
- st.image(logo_path, width=180, use_column_width=False, output_format="SVG", clamp=False)
40
- else:
41
- # احتياطي إذا لم يتم العثور على الشعار
42
- st.markdown("""
43
- <div style="text-align: center; margin-bottom: 2rem;">
44
- <div style="font-size: 2rem; font-weight: bold; color: white; margin-bottom: 0.5rem;">WAHBI</div>
45
- <div style="font-size: 1rem; color: rgba(255,255,255,0.8);">نظام العقود الذكي</div>
46
- </div>
47
- """, unsafe_allow_html=True)
48
-
49
- # إضافة العنوان قبل القائمة
50
- st.markdown("""
51
- <div class="app-title" style="text-align: center; margin-bottom: 1.5rem; color: white; font-size: 1.3rem; font-weight: bold;">
52
- نظام تحليل العقود والمناقصات
53
- </div>
54
- """, unsafe_allow_html=True)
55
-
56
- # إنشاء قائمة الخيارات باستخدام مكتبة streamlit_option_menu بتصميم محسن
57
- selected_module = option_menu(
58
- "", # إزالة العنوان لأننا أضفناه فوق القائمة
59
- [
60
- "الرئيسية",
61
- "إدارة المشاريع",
62
- "التسعير المتكاملة",
63
- "الموارد والتكاليف",
64
- "تحليل المستندات",
65
- "مقارنة المستندات",
66
- "تقييم مخاطر العقود",
67
- "التقارير والتحليلات",
68
- "متتبع حالة المشروع",
69
- "خريطة المشاريع",
70
- "نظام الإشعارات",
71
- "الترجمة الصوتية",
72
- "نظام الإنجازات",
73
- "المساعد الذكي",
74
- "ضبط نماذج الذكاء الاصطناعي"
75
- ],
76
- icons=[
77
- 'house-fill',
78
- 'folder-fill',
79
- 'calculator-fill',
80
- 'tools',
81
- 'file-earmark-text-fill',
82
- 'files',
83
- 'exclamation-triangle-fill',
84
- 'bar-chart-fill',
85
- 'kanban-fill',
86
- 'geo-alt-fill',
87
- 'bell-fill',
88
- 'mic-fill',
89
- 'trophy-fill',
90
- 'robot',
91
- 'gear-fill'
92
- ],
93
- menu_icon="cast",
94
- default_index=0,
95
- styles={
96
- "container": {
97
- "padding": "0px",
98
- "background-color": "transparent",
99
- "direction": "rtl",
100
- "border-radius": "10px",
101
- "margin-bottom": "20px"
102
- },
103
- "icon": {
104
- "color": "#FFB100",
105
- "font-size": "16px",
106
- "margin-left": "10px",
107
- "border-radius": "5px"
108
- },
109
- "nav-link": {
110
- "font-size": "15px",
111
- "text-align": "right",
112
- "margin": "0px 0px 5px 0px",
113
- "padding": "10px 15px",
114
- "direction": "rtl",
115
- "justify-content": "flex-end",
116
- "border-radius": "7px",
117
- "color": "rgba(255, 255, 255, 0.8)",
118
- "font-weight": "500",
119
- "transition": "all 0.3s ease"
120
- },
121
- "nav-link-selected": {
122
- "background-color": "rgba(255, 255, 255, 0.1)",
123
- "color": "#ffffff",
124
- "font-weight": "700",
125
- "border-right": "3px solid #FFB100"
126
- },
127
- "separator": {
128
- "background-color": "rgba(255, 255, 255, 0.1)"
129
- }
130
- }
131
- )
132
-
133
- # إضافة فاصل
134
- st.markdown("---")
135
-
136
- # إضافة معلومات المشروع الحالي
137
- if 'current_project' in st.session_state and st.session_state.current_project:
138
- project = st.session_state.current_project
139
-
140
- st.markdown("### المشروع الحالي")
141
- st.markdown(f"**{project['name']}**")
142
- st.markdown(f"رقم المناقصة: {project['number']}")
143
- st.markdown(f"الجهة المالكة: {project['client']}")
144
-
145
- # إضافة زر للتبديل بين المشاريع
146
- if st.button("تبديل المشروع"):
147
- # لتنفيذ في مرحلة لاحقة
148
- pass
149
-
150
- # إضافة معلومات المستخدم
151
- if 'user_info' in st.session_state and st.session_state.user_info:
152
- user = st.session_state.user_info
153
-
154
- st.markdown("---")
155
- st.markdown("### معلومات المستخدم")
156
- st.markdown(f"**{user['full_name']}**")
157
- st.markdown(f"الدور: {user['role']}")
158
-
159
- # إضافة زر لتسجيل الخروج
160
- if st.button("تسجيل الخروج"):
161
- st.session_state.is_authenticated = False
162
- st.session_state.user_info = None
163
- st.rerun()
164
-
165
- # إضافة معلومات النسخة
166
- st.markdown("---")
167
- st.markdown(f"الإصدار: 1.0.0")
168
- st.markdown(f"تاريخ الإصدار: 2025-03-15")
169
- st.markdown(f"© 2025 شركة شبه الجزيرة للمقاولات")
170
-
171
- return selected_module
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/components/system_innovation.py DELETED
@@ -1,81 +0,0 @@
1
- """
2
- مكون عرض ابتكارات النظام
3
- """
4
-
5
- import streamlit as st
6
-
7
-
8
- def display_innovations():
9
- """
10
- عرض ابتكارات النظام
11
- """
12
- # تعريف الابتكارات الرئيسية
13
- innovations = [
14
- {
15
- "title": "تحليل المستندات بالذكاء الاصطناعي",
16
- "description": "استخراج البنود والكميات والمخاطر من المستندات تلقائيًا باستخدام تقنيات الذكاء الاصطناعي ومعالجة اللغة الطبيعية، مما يوفر الوقت والجهد ويقلل من الأخطاء البشرية.",
17
- "icon": "📄"
18
- },
19
- {
20
- "title": "تقنية التسعير غير المتزن",
21
- "description": "آلية متطورة لتحليل وتطبيق استراتيجيات التسعير غير المتزن، مع ضمان الحفاظ على القيمة الإجمالية للعرض، وزيادة فرص الربحية وتحسين التدفق النقدي.",
22
- "icon": "💰"
23
- },
24
- {
25
- "title": "حاسبة المحتوى المحلي الذكية",
26
- "description": "حساب وتحسين نسبة المحتوى المحلي في المشاريع بطريقة آلية، مع اقتراح بدائل محلية للمنتجات والخدمات المستوردة لتحقيق متطلبات المحتوى المحلي.",
27
- "icon": "🏭"
28
- },
29
- {
30
- "title": "نظام التنبؤ بالأسعار",
31
- "description": "التنبؤ بأسعار المواد والخدمات باستخدام خوارزميات التعلم الآلي والبيانات التاريخية، مما يساعد في اتخاذ قرارات التسعير بدقة أكبر.",
32
- "icon": "📊"
33
- },
34
- {
35
- "title": "تحليل المخاطر الاستباقي",
36
- "description": "تحديد وتحليل المخاطر المحتملة في المشاريع بشكل استباقي، مع توفير استراتيجيات المعالجة المناسبة لكل مخاطرة وتقدير تأثيرها على التكلفة.",
37
- "icon": "⚠️"
38
- }
39
- ]
40
-
41
- # عرض الابتكارات في صفوف
42
- col1, col2 = st.columns(2)
43
-
44
- for i, innovation in enumerate(innovations):
45
- # توزيع الابتكارات على عمودين
46
- current_col = col1 if i % 2 == 0 else col2
47
-
48
- with current_col:
49
- st.markdown(f"""
50
- <div class="innovation-card">
51
- <div class="innovation-icon">{innovation["icon"]}</div>
52
- <h3>{innovation["title"]}</h3>
53
- <p>{innovation["description"]}</p>
54
- </div>
55
- """, unsafe_allow_html=True)
56
-
57
- # إضافة أسلوب CSS للبطاقات
58
- st.markdown("""
59
- <style>
60
- .innovation-card {
61
- background-color: #f8f9fa;
62
- border-radius: 10px;
63
- padding: 15px;
64
- margin-bottom: 20px;
65
- border-right: 5px solid #ff9a3c;
66
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
67
- }
68
- .innovation-icon {
69
- font-size: 24px;
70
- margin-bottom: 10px;
71
- }
72
- .innovation-card h3 {
73
- color: #333;
74
- margin-bottom: 10px;
75
- }
76
- .innovation-card p {
77
- color: #666;
78
- font-size: 14px;
79
- }
80
- </style>
81
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/css/enhanced.css DELETED
@@ -1,400 +0,0 @@
1
- /*
2
- enhanced.css - نظام تحليل المناقصات والعقود
3
- تحسينات متقدمة وتأثيرات خاصة للواجهة
4
- */
5
-
6
- /* ===== تأثيرات حركية ===== */
7
-
8
- /* تأثير التلاشي عند إضافة أي عنصر */
9
- @keyframes fadeIn {
10
- from {
11
- opacity: 0;
12
- transform: translateY(10px);
13
- }
14
- to {
15
- opacity: 1;
16
- transform: translateY(0);
17
- }
18
- }
19
-
20
- /* تأثير النبض للتنبيهات */
21
- @keyframes pulse {
22
- 0% {
23
- box-shadow: 0 0 0 0 rgba(255, 82, 82, 0.4);
24
- }
25
- 70% {
26
- box-shadow: 0 0 0 10px rgba(255, 82, 82, 0);
27
- }
28
- 100% {
29
- box-shadow: 0 0 0 0 rgba(255, 82, 82, 0);
30
- }
31
- }
32
-
33
- /* تأثير الظهور التدريجي */
34
- @keyframes slideIn {
35
- from {
36
- transform: translateX(30px);
37
- opacity: 0;
38
- }
39
- to {
40
- transform: translateX(0);
41
- opacity: 1;
42
- }
43
- }
44
-
45
- /* ===== تطبيق التأثيرات ===== */
46
-
47
- /* تطبيق تأثير التلاشي على البطاقات الرئيسية */
48
- .card,
49
- .section-card,
50
- .dashboard-card {
51
- animation: fadeIn 0.5s ease-out;
52
- }
53
-
54
- /* تطبيق تأثير النبض على التنبيهات المهمة */
55
- .alert-critical {
56
- animation: pulse 2s infinite;
57
- }
58
-
59
- /* تطبيق تأثير الظهور التدريجي على القوائم */
60
- .menu-item {
61
- animation: slideIn 0.3s ease-out;
62
- }
63
-
64
- /* ===== تحسينات متقدمة للمكونات ===== */
65
-
66
- /* تحسين تفاعلية البطاقات */
67
- .card {
68
- transition: all 0.3s ease;
69
- border-right: 4px solid transparent;
70
- }
71
-
72
- .card:hover {
73
- transform: translateY(-5px);
74
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.08);
75
- border-right: 4px solid var(--primary-color);
76
- }
77
-
78
- /* تحسينات العناوين مع تأثيرات التدرجات */
79
- .special-heading {
80
- font-size: 2rem;
81
- font-weight: 700;
82
- background: linear-gradient(120deg, var(--primary-color), var(--accent-color));
83
- -webkit-background-clip: text;
84
- -webkit-text-fill-color: transparent;
85
- position: relative;
86
- display: inline-block;
87
- margin-bottom: 1.5rem;
88
- }
89
-
90
- .special-heading::after {
91
- content: '';
92
- position: absolute;
93
- bottom: -10px;
94
- left: 0;
95
- width: 60px;
96
- height: 4px;
97
- background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
98
- border-radius: 2px;
99
- }
100
-
101
- /* قسم مع خلفية تدرج */
102
- .gradient-section {
103
- background: linear-gradient(120deg, rgba(11, 110, 79, 0.05), rgba(87, 84, 255, 0.05));
104
- border-radius: var(--border-radius-lg);
105
- padding: var(--spacing-lg);
106
- margin-bottom: var(--spacing-lg);
107
- }
108
-
109
- /* أزرار بتأثيرات متقدمة */
110
- .enhanced-button {
111
- border: none;
112
- padding: 10px 20px;
113
- border-radius: var(--border-radius-md);
114
- font-weight: bold;
115
- transition: all 0.3s ease;
116
- position: relative;
117
- overflow: hidden;
118
- z-index: 1;
119
- color: var(--text-light);
120
- background-color: var(--primary-color);
121
- }
122
-
123
- .enhanced-button::before {
124
- content: '';
125
- position: absolute;
126
- top: 0;
127
- left: 0;
128
- width: 0;
129
- height: 100%;
130
- background-color: rgba(255, 255, 255, 0.1);
131
- transition: width 0.3s ease;
132
- z-index: -1;
133
- }
134
-
135
- .enhanced-button:hover::before {
136
- width: 100%;
137
- }
138
-
139
- .enhanced-button:hover {
140
- transform: translateY(-2px);
141
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
142
- }
143
-
144
- /* ===== مؤشرات وأشرطة التقدم المتقدمة ===== */
145
-
146
- /* مؤشر تقدم دائري */
147
- .circle-progress {
148
- position: relative;
149
- width: 100px;
150
- height: 100px;
151
- border-radius: 50%;
152
- background: conic-gradient(var(--primary-color) 0%, var(--primary-light) var(--progress, 75%), #f0f0f0 var(--progress, 75%), #f0f0f0 100%);
153
- display: flex;
154
- align-items: center;
155
- justify-content: center;
156
- }
157
-
158
- .circle-progress::before {
159
- content: '';
160
- position: absolute;
161
- width: 70px;
162
- height: 70px;
163
- border-radius: 50%;
164
- background-color: white;
165
- }
166
-
167
- .circle-progress-value {
168
- position: relative;
169
- z-index: 10;
170
- font-weight: bold;
171
- font-size: 1.2rem;
172
- color: var(--primary-color);
173
- }
174
-
175
- /* مؤشر شريطي بتأثيرات */
176
- .enhanced-progress {
177
- height: 10px;
178
- background-color: #f0f0f0;
179
- border-radius: 5px;
180
- overflow: hidden;
181
- margin: 10px 0;
182
- }
183
-
184
- .enhanced-progress-bar {
185
- height: 100%;
186
- background: linear-gradient(90deg, var(--primary-color), var(--primary-light));
187
- border-radius: 5px;
188
- transition: width 0.5s ease;
189
- position: relative;
190
- overflow: hidden;
191
- }
192
-
193
- .enhanced-progress-bar::after {
194
- content: '';
195
- position: absolute;
196
- top: 0;
197
- left: 0;
198
- right: 0;
199
- bottom: 0;
200
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
201
- animation: shimmer 1.5s infinite;
202
- }
203
-
204
- @keyframes shimmer {
205
- 0% {
206
- transform: translateX(-100%);
207
- }
208
- 100% {
209
- transform: translateX(100%);
210
- }
211
- }
212
-
213
- /* ===== بطاقات المؤشرات الإحصائية المطورة ===== */
214
-
215
- .stat-card {
216
- border-radius: var(--border-radius-md);
217
- background-color: white;
218
- padding: 1.5rem;
219
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
220
- display: flex;
221
- align-items: center;
222
- transition: all 0.3s ease;
223
- position: relative;
224
- overflow: hidden;
225
- }
226
-
227
- .stat-card:hover {
228
- transform: translateY(-5px);
229
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
230
- }
231
-
232
- .stat-card::before {
233
- content: '';
234
- position: absolute;
235
- top: 0;
236
- left: 0;
237
- width: 6px;
238
- height: 100%;
239
- background-color: var(--primary-color);
240
- }
241
-
242
- .stat-card-icon {
243
- width: 50px;
244
- height: 50px;
245
- display: flex;
246
- align-items: center;
247
- justify-content: center;
248
- border-radius: 12px;
249
- background-color: rgba(11, 110, 79, 0.1);
250
- color: var(--primary-color);
251
- margin-right: 1rem;
252
- }
253
-
254
- .stat-card-content {
255
- flex-grow: 1;
256
- }
257
-
258
- .stat-card-value {
259
- font-size: 2rem;
260
- font-weight: bold;
261
- color: var(--text-primary);
262
- line-height: 1;
263
- margin-bottom: 0.3rem;
264
- }
265
-
266
- .stat-card-label {
267
- color: var(--text-muted);
268
- font-size: 0.9rem;
269
- }
270
-
271
- .stat-card-trend {
272
- display: flex;
273
- align-items: center;
274
- font-size: 0.8rem;
275
- margin-top: 0.5rem;
276
- }
277
-
278
- .stat-card-trend.up {
279
- color: var(--success-color);
280
- }
281
-
282
- .stat-card-trend.down {
283
- color: var(--error-color);
284
- }
285
-
286
- /* ===== أشكال الخلفيات المحسنة ===== */
287
-
288
- .dots-background {
289
- background-color: white;
290
- background-image: radial-gradient(rgba(11, 110, 79, 0.1) 1px, transparent 1px);
291
- background-size: 20px 20px;
292
- background-position: 0 0;
293
- }
294
-
295
- .geometric-pattern {
296
- position: relative;
297
- overflow: hidden;
298
- z-index: 1;
299
- }
300
-
301
- .geometric-pattern::before {
302
- content: "";
303
- position: absolute;
304
- top: -50%;
305
- left: -50%;
306
- width: 200%;
307
- height: 200%;
308
- background: repeating-linear-gradient(
309
- 45deg,
310
- rgba(11, 110, 79, 0.03),
311
- rgba(11, 110, 79, 0.03) 10px,
312
- rgba(11, 110, 79, 0.05) 10px,
313
- rgba(11, 110, 79, 0.05) 20px
314
- );
315
- transform: rotate(-45deg);
316
- z-index: -1;
317
- }
318
-
319
- /* ===== تأثيرات تكميلية ===== */
320
-
321
- /* تأثير الومضة على الأزرار عند النقر */
322
- .button-flash {
323
- position: relative;
324
- overflow: hidden;
325
- }
326
-
327
- .button-flash::after {
328
- content: '';
329
- position: absolute;
330
- top: 50%;
331
- left: 50%;
332
- width: 5px;
333
- height: 5px;
334
- background: rgba(255, 255, 255, 0.5);
335
- opacity: 0;
336
- border-radius: 100%;
337
- transform: scale(1, 1) translate(-50%, -50%);
338
- transform-origin: 50% 50%;
339
- }
340
-
341
- .button-flash:active::after {
342
- animation: flash 0.6s ease-out;
343
- }
344
-
345
- @keyframes flash {
346
- 0% {
347
- opacity: 1;
348
- transform: scale(0, 0) translate(-50%, -50%);
349
- }
350
- 100% {
351
- opacity: 0;
352
- transform: scale(50, 50) translate(-50%, -50%);
353
- }
354
- }
355
-
356
- /* ===== تحسينات المسافات والهوامش لسهولة القراءة ===== */
357
-
358
- /* إضافة مسافات أكبر بين الفقرات */
359
- p {
360
- margin-bottom: 1.2rem;
361
- line-height: 1.6;
362
- }
363
-
364
- /* تحسين النقاط والقوائم */
365
- li {
366
- margin-bottom: 0.5rem;
367
- }
368
-
369
- /* ===== تعديلات خاصة للهواتف المحمولة ===== */
370
-
371
- @media (max-width: 768px) {
372
- .dashboard-grid {
373
- grid-template-columns: 1fr;
374
- }
375
-
376
- .stat-card {
377
- padding: 1rem;
378
- }
379
-
380
- .stat-card-icon {
381
- width: 40px;
382
- height: 40px;
383
- }
384
-
385
- .stat-card-value {
386
- font-size: 1.5rem;
387
- }
388
-
389
- h1 {
390
- font-size: 1.8rem;
391
- }
392
-
393
- h2 {
394
- font-size: 1.5rem;
395
- }
396
-
397
- .indicator-value {
398
- font-size: 1.5rem;
399
- }
400
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/css/main.css DELETED
@@ -1,405 +0,0 @@
1
- /*
2
- main.css - نظام تحليل المناقصات والعقود
3
- الأنماط الرئيسية للتطبيق
4
- */
5
-
6
- /* ===== المتغيرات العامة ===== */
7
- :root {
8
- /* الألوان الأساسية */
9
- --primary-color: #0B6E4F;
10
- --primary-light: #08A45C;
11
- --primary-dark: #074D37;
12
- --secondary-color: #FFB100;
13
- --secondary-light: #FFCA4D;
14
- --secondary-dark: #D99000;
15
- --accent-color: #5754FF;
16
- --accent-light: #6F6CFF;
17
- --accent-dark: #4240DD;
18
-
19
- /* ألوان محايدة */
20
- --background-color: #F8F9FA;
21
- --surface-color: #FFFFFF;
22
- --card-color: #FAFAFA;
23
- --border-color: #E0E0E0;
24
-
25
- /* ألوان النص */
26
- --text-primary: #222222;
27
- --text-secondary: #555555;
28
- --text-muted: #888888;
29
- --text-light: #FFFFFF;
30
-
31
- /* ألوان مؤشرات الحالة */
32
- --success-color: #43A047;
33
- --warning-color: #FB8C00;
34
- --error-color: #E53935;
35
- --info-color: #1E88E5;
36
-
37
- /* القياسات */
38
- --border-radius-sm: 4px;
39
- --border-radius-md: 8px;
40
- --border-radius-lg: 16px;
41
- --spacing-xs: 4px;
42
- --spacing-sm: 8px;
43
- --spacing-md: 16px;
44
- --spacing-lg: 24px;
45
- --spacing-xl: 32px;
46
-
47
- /* الظلال */
48
- --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);
49
- --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.1);
50
- --shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.1);
51
-
52
- /* الخطوط */
53
- --font-family: 'Cairo', 'Tajawal', 'Readex Pro', sans-serif;
54
- }
55
-
56
- /* ===== إعدادات عامة ===== */
57
- html, body {
58
- font-family: var(--font-family);
59
- color: var(--text-primary);
60
- background-color: var(--background-color);
61
- }
62
-
63
- /* ===== العناوين ===== */
64
- h1, h2, h3, h4, h5, h6 {
65
- color: var(--primary-color);
66
- font-weight: 700;
67
- margin-bottom: var(--spacing-md);
68
- }
69
-
70
- h1 {
71
- font-size: 2.2rem;
72
- background: linear-gradient(90deg, var(--primary-color), var(--primary-light));
73
- -webkit-background-clip: text;
74
- -webkit-text-fill-color: transparent;
75
- display: inline-block;
76
- margin-bottom: var(--spacing-lg);
77
- }
78
-
79
- h2 {
80
- font-size: 1.8rem;
81
- position: relative;
82
- padding-bottom: var(--spacing-sm);
83
- }
84
-
85
- h2::after {
86
- content: '';
87
- position: absolute;
88
- bottom: 0;
89
- right: 0;
90
- width: 60px;
91
- height: 3px;
92
- background: var(--secondary-color);
93
- border-radius: var(--border-radius-sm);
94
- }
95
-
96
- h3 {
97
- font-size: 1.5rem;
98
- color: var(--primary-dark);
99
- }
100
-
101
- h4 {
102
- font-size: 1.2rem;
103
- color: var(--text-primary);
104
- }
105
-
106
- /* ===== عنوان التطبيق ===== */
107
- .app-title {
108
- font-size: 2.2rem;
109
- font-weight: bold;
110
- text-align: center;
111
- color: var(--primary-color);
112
- margin-bottom: var(--spacing-lg);
113
- text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
114
- background: linear-gradient(90deg, var(--primary-color), var(--primary-light));
115
- -webkit-background-clip: text;
116
- -webkit-text-fill-color: transparent;
117
- }
118
-
119
- /* ===== الأقسام والبطاقات ===== */
120
- .section-card {
121
- background-color: var(--surface-color);
122
- border-radius: var(--border-radius-lg);
123
- padding: var(--spacing-lg);
124
- box-shadow: var(--shadow-md);
125
- margin-bottom: var(--spacing-lg);
126
- border-right: 4px solid var(--primary-color);
127
- }
128
-
129
- .card {
130
- background-color: var(--surface-color);
131
- border-radius: var(--border-radius-md);
132
- padding: var(--spacing-md);
133
- box-shadow: var(--shadow-sm);
134
- margin-bottom: var(--spacing-md);
135
- border-right: 3px solid var(--primary-color);
136
- transition: all 0.3s ease;
137
- }
138
-
139
- .card:hover {
140
- box-shadow: var(--shadow-md);
141
- transform: translateY(-2px);
142
- }
143
-
144
- .card-title {
145
- font-weight: bold;
146
- color: var(--primary-color);
147
- margin-bottom: var(--spacing-sm);
148
- font-size: 1.2rem;
149
- }
150
-
151
- .card-metrics {
152
- display: flex;
153
- justify-content: space-between;
154
- margin-top: var(--spacing-sm);
155
- }
156
-
157
- .card-metric {
158
- text-align: center;
159
- }
160
-
161
- .card-metric-value {
162
- font-weight: bold;
163
- font-size: 1.5rem;
164
- }
165
-
166
- .card-metric-label {
167
- font-size: 0.8rem;
168
- color: var(--text-muted);
169
- }
170
-
171
- /* ===== المؤشرات ===== */
172
- .indicator {
173
- padding: var(--spacing-md);
174
- border-radius: var(--border-radius-md);
175
- background-color: var(--surface-color);
176
- text-align: center;
177
- box-shadow: var(--shadow-sm);
178
- border-top: 3px solid var(--primary-color);
179
- height: 100%;
180
- }
181
-
182
- .indicator-value {
183
- font-size: 2rem;
184
- font-weight: bold;
185
- margin-bottom: var(--spacing-xs);
186
- }
187
-
188
- .indicator-label {
189
- font-size: 1rem;
190
- color: var(--text-muted);
191
- }
192
-
193
- /* ===== الأزرار ===== */
194
- .stButton > button {
195
- border-radius: var(--border-radius-md);
196
- background-color: var(--primary-color);
197
- color: var(--text-light);
198
- font-weight: bold;
199
- border: none;
200
- padding: 0.5rem 1rem;
201
- transition: all 0.3s ease;
202
- }
203
-
204
- .stButton > button:hover {
205
- background-color: var(--primary-dark);
206
- box-shadow: var(--shadow-sm);
207
- }
208
-
209
- /* زر ثانوي */
210
- .stButton.secondary > button {
211
- background-color: var(--secondary-color);
212
- color: var(--text-primary);
213
- }
214
-
215
- .stButton.secondary > button:hover {
216
- background-color: var(--secondary-dark);
217
- }
218
-
219
- /* زر محايد */
220
- .stButton.neutral > button {
221
- background-color: var(--text-muted);
222
- color: var(--text-light);
223
- }
224
-
225
- .stButton.neutral > button:hover {
226
- background-color: var(--text-secondary);
227
- }
228
-
229
- /* زر خطر */
230
- .stButton.danger > button {
231
- background-color: var(--error-color);
232
- }
233
-
234
- .stButton.danger > button:hover {
235
- background-color: #C62828;
236
- }
237
-
238
- /* ===== النماذج ===== */
239
- .stTextInput > div > div > input,
240
- .stTextArea > div > div > textarea {
241
- border-radius: var(--border-radius-md);
242
- border: 1px solid var(--border-color);
243
- padding: var(--spacing-sm);
244
- transition: all 0.3s ease;
245
- }
246
-
247
- .stTextInput > div > div > input:focus,
248
- .stTextArea > div > div > textarea:focus {
249
- border-color: var(--primary-color);
250
- box-shadow: 0 0 0 2px rgba(11, 110, 79, 0.2);
251
- }
252
-
253
- .stSelectbox > div > div > div {
254
- border-radius: var(--border-radius-md);
255
- border: 1px solid var(--border-color);
256
- }
257
-
258
- /* ===== الفواصل ===== */
259
- hr {
260
- border: none;
261
- height: 1px;
262
- background-color: var(--border-color);
263
- margin: var(--spacing-lg) 0;
264
- }
265
-
266
- /* ===== الرسوم البيانية ===== */
267
- .stPlot {
268
- background-color: var(--surface-color);
269
- border-radius: var(--border-radius-md);
270
- padding: var(--spacing-md);
271
- box-shadow: var(--shadow-sm);
272
- }
273
-
274
- /* ===== التنبيهات ===== */
275
- .stAlert {
276
- border-radius: var(--border-radius-md);
277
- box-shadow: var(--shadow-sm);
278
- }
279
-
280
- /* ===== الصور ===== */
281
- .stImage > img {
282
- border-radius: var(--border-radius-md);
283
- box-shadow: var(--shadow-sm);
284
- }
285
-
286
- /* ===== تعديلات خاصة بالواجهة ===== */
287
- .main-content {
288
- padding: var(--spacing-md);
289
- }
290
-
291
- /* ===== أنماط القائمة ===== */
292
- .menu-container {
293
- background-color: var(--surface-color);
294
- border-radius: var(--border-radius-md);
295
- box-shadow: var(--shadow-sm);
296
- padding: var(--spacing-sm);
297
- margin-bottom: var(--spacing-md);
298
- }
299
-
300
- .menu-item {
301
- padding: var(--spacing-sm);
302
- border-radius: var(--border-radius-sm);
303
- cursor: pointer;
304
- transition: all 0.3s ease;
305
- }
306
-
307
- .menu-item:hover {
308
- background-color: rgba(11, 110, 79, 0.1);
309
- }
310
-
311
- .menu-item.active {
312
- background-color: var(--primary-color);
313
- color: var(--text-light);
314
- }
315
-
316
- /* ===== لوحات المعلومات ===== */
317
- .dashboard-grid {
318
- display: grid;
319
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
320
- gap: var(--spacing-md);
321
- margin-bottom: var(--spacing-lg);
322
- }
323
-
324
- .dashboard-card {
325
- background-color: var(--surface-color);
326
- border-radius: var(--border-radius-md);
327
- padding: var(--spacing-md);
328
- box-shadow: var(--shadow-sm);
329
- display: flex;
330
- flex-direction: column;
331
- height: 100%;
332
- }
333
-
334
- .dashboard-card-header {
335
- display: flex;
336
- justify-content: space-between;
337
- align-items: center;
338
- margin-bottom: var(--spacing-sm);
339
- padding-bottom: var(--spacing-sm);
340
- border-bottom: 1px solid var(--border-color);
341
- }
342
-
343
- .dashboard-card-title {
344
- font-weight: bold;
345
- color: var(--primary-color);
346
- }
347
-
348
- .dashboard-card-icon {
349
- color: var(--accent-color);
350
- background-color: rgba(87, 84, 255, 0.1);
351
- width: 40px;
352
- height: 40px;
353
- display: flex;
354
- align-items: center;
355
- justify-content: center;
356
- border-radius: 50%;
357
- }
358
-
359
- .dashboard-card-content {
360
- flex-grow: 1;
361
- }
362
-
363
- .dashboard-card-footer {
364
- margin-top: var(--spacing-sm);
365
- padding-top: var(--spacing-sm);
366
- border-top: 1px solid var(--border-color);
367
- display: flex;
368
- justify-content: space-between;
369
- align-items: center;
370
- }
371
-
372
- /* ===== المحتوى المشروط ===== */
373
- .conditional-section {
374
- transition: all 0.3s ease;
375
- animation: fadeIn 0.3s ease-in-out;
376
- }
377
-
378
- @keyframes fadeIn {
379
- from {
380
- opacity: 0;
381
- transform: translateY(10px);
382
- }
383
- to {
384
- opacity: 1;
385
- transform: translateY(0);
386
- }
387
- }
388
-
389
- /* ===== نمط تذييل الصفحة ===== */
390
- .footer {
391
- text-align: center;
392
- padding: var(--spacing-lg) 0;
393
- color: var(--text-muted);
394
- font-size: 0.9rem;
395
- margin-top: var(--spacing-xl);
396
- }
397
-
398
- .footer a {
399
- color: var(--primary-color);
400
- text-decoration: none;
401
- }
402
-
403
- .footer a:hover {
404
- text-decoration: underline;
405
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/css/rtl.css DELETED
@@ -1,223 +0,0 @@
1
- /*
2
- rtl.css - نظام تحليل المناقصات والعقود
3
- تخصيصات الواجهة للغة العربية ودعم RTL
4
- */
5
-
6
- /* ===== تعديلات عامة لتنسيق RTL ===== */
7
-
8
- /* توجيه النص العام والمكونات */
9
- .css-18e3th9,
10
- .css-1d391kg,
11
- .stMarkdown,
12
- .stTextArea,
13
- .stButton,
14
- .stTextInput,
15
- .stSelectbox,
16
- .stRadio,
17
- .stCheckbox,
18
- .stTabs,
19
- .stWidgetLabel,
20
- .stPlotlyChart,
21
- .stDataFrame,
22
- .stTable {
23
- direction: rtl;
24
- text-align: right;
25
- }
26
-
27
- /* توجيه صحيح للرسوم البيانية */
28
- .plotly-graph-div,
29
- .stPlot {
30
- direction: ltr;
31
- }
32
-
33
- /* تعديل على عناصر القوائم */
34
- .stSelectbox > div > div > div {
35
- text-align: right;
36
- }
37
-
38
- /* إصلاح اتجاه المدخلات الرقمية */
39
- input[type="number"] {
40
- direction: ltr;
41
- text-align: right;
42
- }
43
-
44
- /* تعديل نص الأزرار للتوجيه الصحيح */
45
- .stButton > button {
46
- direction: rtl;
47
- display: flex;
48
- align-items: center;
49
- justify-content: center;
50
- }
51
-
52
- /* تعديل الشاشة الرئيسية */
53
- .main .block-container {
54
- direction: rtl;
55
- text-align: right;
56
- }
57
-
58
- /* ===== تعديلات أيقونات وعلامات خاصة ===== */
59
-
60
- /* عكس اتجاه أيقونات السهم والأيقونات الموجهة الأخرى */
61
- .fa-arrow-left,
62
- .fa-chevron-left {
63
- transform: rotate(180deg);
64
- }
65
-
66
- .fa-arrow-right,
67
- .fa-chevron-right {
68
- transform: rotate(180deg);
69
- }
70
-
71
- /* تعديل اتجاه علامات التبويب */
72
- .stTabs [role="tablist"] {
73
- flex-direction: row-reverse;
74
- }
75
-
76
- /* تعديل على مؤشرات التحميل */
77
- .stProgress > div > div > div {
78
- flex-direction: row-reverse;
79
- }
80
-
81
- /* ===== تعديلات خاصة للجداول والبيانات ===== */
82
-
83
- /* إصلاح اتجاه الجداول */
84
- .stTable {
85
- direction: rtl;
86
- }
87
-
88
- .stTable th {
89
- text-align: right;
90
- }
91
-
92
- /* رؤوس الجداول */
93
- .stTable thead {
94
- text-align: right;
95
- }
96
-
97
- /* صفوف البيانات */
98
- .stTable tbody {
99
- text-align: right;
100
- }
101
-
102
- /* تعديل على مكون التاريخ */
103
- .stDateInput > div {
104
- flex-direction: row-reverse;
105
- }
106
-
107
- /* ===== تحسينات إضافية ===== */
108
-
109
- /* تعديل مكونات التفاعل */
110
- .stWidgetLabel p {
111
- text-align: right;
112
- }
113
-
114
- /* تعديل على الهوامش - تبديل الهوامش اليمنى واليسرى */
115
- .stButton,
116
- .stTextInput,
117
- .stNumberInput,
118
- .stSelectbox,
119
- .stTextArea {
120
- margin-right: 0;
121
- margin-left: auto;
122
- }
123
-
124
- /* تعديل على الحدود - تبديل الحدود اليمنى واليسرى */
125
- .stMarkdown blockquote {
126
- border-right: 4px solid var(--primary-color);
127
- border-left: none;
128
- padding-right: 20px;
129
- padding-left: 0;
130
- }
131
-
132
- /* إضافة دعم أفضل للخطوط العربية */
133
- body {
134
- font-family: 'Cairo', 'Tajawal', 'Readex Pro', -apple-system, BlinkMacSystemFont, sans-serif;
135
- }
136
-
137
- /* تعديل صناديق الإدخال */
138
- textarea, input {
139
- font-family: 'Cairo', 'Tajawal', 'Readex Pro', -apple-system, BlinkMacSystemFont, sans-serif;
140
- }
141
-
142
- /* تعديل القوائم ذات التعداد النقطي للاتجاه RTL */
143
- ul, ol {
144
- padding-right: 20px;
145
- padding-left: 0;
146
- }
147
-
148
- /* ===== تصحيحات محددة للمكونات ===== */
149
-
150
- /* تصحيح التنسيق للأعمدة */
151
- div.row-widget.stRadio > div {
152
- flex-direction: row-reverse;
153
- }
154
-
155
- /* تصحيح مربعات الاختيار */
156
- .stCheckbox > div {
157
- flex-direction: row-reverse;
158
- }
159
-
160
- /* تحسين مظهر وتجربة المستخدم العربية */
161
- .stExpander > div > div > div {
162
- text-align: right;
163
- }
164
-
165
- .stFileUploader > div {
166
- direction: rtl;
167
- text-align: right;
168
- }
169
-
170
- /* تصحيح ظهور القوائم المنسدلة لتلائم RTL */
171
- .stSelectbox > div[data-baseweb="select"] > div {
172
- direction: rtl;
173
- }
174
-
175
- /* تصحيح اتجاه النص في التنبيهات */
176
- .stAlert > div {
177
- flex-direction: row-reverse;
178
- text-align: right;
179
- }
180
-
181
- /* تصحيح اتجاه صناديق النص القابلة للتحرير */
182
- .css-1hynsf2 .stTextArea label,
183
- .css-1hynsf2 .stTextInput label {
184
- text-align: right;
185
- }
186
-
187
- /* تصحيح مربعات الاختيار المتعددة */
188
- div.row-widget.stMultiselect > div {
189
- direction: rtl;
190
- }
191
-
192
- /* تصحيح محاذاة محاذاة المحتوى وترتيب الأيقونات */
193
- .stAlert > div > div:first-child {
194
- margin-left: 10px;
195
- margin-right: 0;
196
- }
197
-
198
- /* تصحيحات عامة للتأكد من عدم انعكاس أي شيء بشكل غير صحيح */
199
- .stMarkdown img {
200
- direction: ltr;
201
- }
202
-
203
- /* ===== تصحيحات لحالات خاصة ===== */
204
-
205
- /* تصحيح اتجاه مكونات التاريخ والوقت */
206
- .stDateInput, .stTimeInput {
207
- direction: rtl;
208
- }
209
-
210
- /* تصحيح محدد اللون */
211
- .stColorPicker > div {
212
- direction: ltr;
213
- }
214
-
215
- /* تصحيح مكون التصفية بالنوع */
216
- .stType > div {
217
- direction: rtl;
218
- }
219
-
220
- /* تصحيح مكون الشريط الجان��ي */
221
- .css-1v3fvcr {
222
- direction: rtl;
223
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/dwg_handler.py DELETED
@@ -1,223 +0,0 @@
1
- """
2
- معالج ملفات DWG
3
- """
4
-
5
- import os
6
- import re
7
- import subprocess
8
- import shutil
9
- import tempfile
10
- import traceback
11
- import pandas as pd
12
- from pathlib import Path
13
- import config
14
- from utils.helpers import create_directory_if_not_exists
15
-
16
-
17
- class DWGHandler:
18
- """معالج ملفات DWG (AutoCAD)"""
19
-
20
- def __init__(self, converter_path=None):
21
- """
22
- تهيئة معالج ملفات DWG
23
-
24
- المعلمات:
25
- converter_path: مسار برنامج تحويل DWG (اختياري)
26
- """
27
- # محاولة تحديد مسار برنامج تحويل DWG
28
- self.converter_path = converter_path
29
-
30
- if not self.converter_path:
31
- # البحث عن المسار في إعدادات النظام
32
- if hasattr(config, 'DWG_CONVERTER_PATH') and config.DWG_CONVERTER_PATH:
33
- self.converter_path = config.DWG_CONVERTER_PATH
34
- else:
35
- # محاولة البحث عن البرامج المعروفة
36
- possible_paths = [
37
- r"C:\Program Files\Autodesk\AutoCAD 2022\accoreconsole.exe",
38
- r"C:\Program Files\Autodesk\AutoCAD 2021\accoreconsole.exe",
39
- r"C:\Program Files\Autodesk\AutoCAD 2020\accoreconsole.exe",
40
- r"C:\Program Files\ODA\ODAFileConverter\ODAFileConverter.exe"
41
- ]
42
-
43
- for path in possible_paths:
44
- if os.path.exists(path):
45
- self.converter_path = path
46
- break
47
-
48
- def convert_to_pdf(self, dwg_path, output_path=None):
49
- """
50
- تحويل ملف DWG إلى PDF
51
-
52
- المعلمات:
53
- dwg_path: مسار ملف DWG
54
- output_path: مسار ملف الإخراج (اختياري)
55
-
56
- الإرجاع:
57
- مسار ملف PDF الناتج
58
- """
59
- try:
60
- # التحقق من وجود الملف
61
- if not os.path.exists(dwg_path):
62
- raise FileNotFoundError(f"ملف DWG غير موجود: {dwg_path}")
63
-
64
- # التحقق من وجود برنامج التحويل
65
- if not self.converter_path or not os.path.exists(self.converter_path):
66
- raise FileNotFoundError("لم يتم العثور على برنامج تحويل DWG")
67
-
68
- # تحديد مسار الإخراج
69
- if not output_path:
70
- output_path = os.path.splitext(dwg_path)[0] + ".pdf"
71
-
72
- # إنشاء مجلد الإخراج إذا لم يكن موجودًا
73
- output_dir = os.path.dirname(output_path)
74
- create_directory_if_not_exists(output_dir)
75
-
76
- # تنفيذ عملية التحويل باستخدام برنامج التحويل المناسب
77
- if "accoreconsole.exe" in self.converter_path.lower():
78
- # استخدام AutoCAD Core Console لتحويل الملف
79
- script_content = f"""
80
- /i "{dwg_path}"
81
- /e
82
- /o "{output_path}"
83
- """
84
- script_path = tempfile.mktemp(suffix=".scr")
85
- with open(script_path, "w") as f:
86
- f.write(script_content)
87
-
88
- cmd = [self.converter_path, "/s", script_path]
89
- result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
90
-
91
- os.unlink(script_path) # حذف ملف النص البرمجي المؤقت
92
-
93
- if result.returncode != 0:
94
- raise RuntimeError(f"فشل في تحويل ملف DWG: {result.stderr}")
95
-
96
- elif "odafileconverter.exe" in self.converter_path.lower():
97
- # استخدام ODA File Converter لتحويل الملف
98
- input_dir = os.path.dirname(dwg_path)
99
- output_dir = os.path.dirname(output_path)
100
- input_filename = os.path.basename(dwg_path)
101
- output_format = "PDF"
102
-
103
- cmd = [
104
- self.converter_path,
105
- input_dir,
106
- output_dir,
107
- "DWG",
108
- output_format,
109
- "1",
110
- "1",
111
- input_filename
112
- ]
113
-
114
- result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
115
-
116
- if result.returncode != 0:
117
- raise RuntimeError(f"فشل في تحويل ملف DWG: {result.stderr}")
118
-
119
- else:
120
- raise NotImplementedError(f"برنامج التحويل غير مدعوم: {self.converter_path}")
121
-
122
- # التحقق من وجود ملف الإخراج
123
- if not os.path.exists(output_path):
124
- raise FileNotFoundError(f"لم يتم إنشاء ملف PDF: {output_path}")
125
-
126
- return output_path
127
-
128
- except Exception as e:
129
- error_msg = f"خطأ في تحويل ملف DWG إلى PDF: {str(e)}"
130
- print(error_msg)
131
- traceback.print_exc()
132
- raise Exception(error_msg)
133
-
134
- def extract_quantities(self, dwg_path):
135
- """
136
- استخراج الكميات من ملف DWG
137
-
138
- المعلمات:
139
- dwg_path: مسار ملف DWG
140
-
141
- الإرجاع:
142
- DataFrame يحتوي على الكميات المستخرجة
143
- """
144
- try:
145
- # تحويل ملف DWG إلى PDF أولاً
146
- pdf_path = self.convert_to_pdf(dwg_path)
147
-
148
- # استخدام معالج ملفات PDF لاستخراج الكميات
149
- from utils.pdf_handler import extract_quantities_from_pdf
150
- return extract_quantities_from_pdf(pdf_path)
151
-
152
- except Exception as e:
153
- error_msg = f"خطأ في استخراج الكميات من ملف DWG: {str(e)}"
154
- print(error_msg)
155
- traceback.print_exc()
156
- raise Exception(error_msg)
157
-
158
- def extract_text(self, dwg_path):
159
- """
160
- استخراج النص من ملف DWG
161
-
162
- المعلمات:
163
- dwg_path: مسار ملف DWG
164
-
165
- الإرجاع:
166
- النص المستخرج من ملف DWG
167
- """
168
- try:
169
- # تحويل ملف DWG إلى PDF أولاً
170
- pdf_path = self.convert_to_pdf(dwg_path)
171
-
172
- # استخدام معالج ملفات PDF لاستخراج النص
173
- from utils.pdf_handler import extract_text_from_pdf
174
- return extract_text_from_pdf(pdf_path)
175
-
176
- except Exception as e:
177
- error_msg = f"خطأ في استخراج النص من ملف DWG: {str(e)}"
178
- print(error_msg)
179
- traceback.print_exc()
180
- raise Exception(error_msg)
181
-
182
- def get_dwg_info(self, dwg_path):
183
- """
184
- الحصول على معلومات ملف DWG
185
-
186
- المعلمات:
187
- dwg_path: مسار ملف DWG
188
-
189
- الإرجاع:
190
- قاموس يحتوي على معلومات الملف
191
- """
192
- try:
193
- # التحقق من وجود الملف
194
- if not os.path.exists(dwg_path):
195
- raise FileNotFoundError(f"ملف DWG غير موجود: {dwg_path}")
196
-
197
- # الحصول على معلومات الملف الأساسية
198
- file_info = {
199
- 'filename': os.path.basename(dwg_path),
200
- 'path': dwg_path,
201
- 'size': os.path.getsize(dwg_path),
202
- 'modified_date': os.path.getmtime(dwg_path)
203
- }
204
-
205
- # محاولة استخراج معلومات إضافية من الملف
206
- # ملاحظة: هذا يتطلب مكتبات إضافية أو استخدام برامج خارجية
207
-
208
- return file_info
209
-
210
- except Exception as e:
211
- error_msg = f"خطأ في الحصول على معلومات ملف DWG: {str(e)}"
212
- print(error_msg)
213
- traceback.print_exc()
214
- raise Exception(error_msg)
215
-
216
- def is_converter_available(self):
217
- """
218
- التحقق من توفر برنامج تحويل DWG
219
-
220
- الإرجاع:
221
- True إذا كان برنامج التحويل متوفرًا، False خلاف ذلك
222
- """
223
- return self.converter_path is not None and os.path.exists(self.converter_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/excel_handler.py DELETED
@@ -1,486 +0,0 @@
1
- """
2
- معالج ملفات Excel
3
- """
4
-
5
- import pandas as pd
6
- import os
7
- import numpy as np
8
- import xlsxwriter
9
- from datetime import datetime
10
- import traceback
11
- import config
12
- from utils.helpers import create_directory_if_not_exists, get_file_extension, format_number
13
-
14
-
15
- def read_excel_file(file_path, sheet_name=0, header=0, skip_rows=None):
16
- """
17
- قراءة ملف Excel
18
-
19
- المعلمات:
20
- file_path: مسار ملف Excel
21
- sheet_name: اسم أو رقم الصفحة (افتراضي: 0)
22
- header: رقم الصف الذي يحتوي على العناوين (افتراضي: 0)
23
- skip_rows: قائمة بأرقام الصفوف للتخطي (افتراضي: None)
24
-
25
- الإرجاع:
26
- DataFrame من البيانات المقروءة
27
- """
28
- try:
29
- # التحقق من وجود الملف
30
- if not os.path.exists(file_path):
31
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
32
-
33
- # التحقق من امتداد الملف
34
- ext = get_file_extension(file_path)
35
- if ext not in ['.xlsx', '.xls', '.xlsm']:
36
- raise ValueError(f"نوع الملف غير مدعوم: {ext}. يجب أن يكون الملف بامتداد .xlsx أو .xls أو .xlsm")
37
-
38
- # قراءة الملف
39
- df = pd.read_excel(
40
- file_path,
41
- sheet_name=sheet_name,
42
- header=header,
43
- skiprows=skip_rows
44
- )
45
-
46
- return df
47
-
48
- except Exception as e:
49
- error_msg = f"خطأ في قراءة ملف Excel: {str(e)}"
50
- print(error_msg)
51
- traceback.print_exc()
52
- raise Exception(error_msg)
53
-
54
-
55
- def write_excel_file(df, file_path, sheet_name="Sheet1", index=False, freeze_panes=None, column_widths=None, formats=None):
56
- """
57
- كتابة DataFrame إلى ملف Excel
58
-
59
- المعلمات:
60
- df: DataFrame المراد كتابته
61
- file_path: مسار ملف Excel
62
- sheet_name: اسم الصفحة (افتراضي: Sheet1)
63
- index: ما إذا كان سيتم تضمين الفهرس (افتراضي: False)
64
- freeze_panes: صف وعمود لتجميد الألواح (افتراضي: None)
65
- column_widths: قاموس لعرض الأعمدة {column_name: width}
66
- formats: قاموس لتنسيقات الأعمدة {column_name: format_function}
67
-
68
- الإرجاع:
69
- True في حالة النجاح
70
- """
71
- try:
72
- # التأكد من وجود المجلد
73
- create_directory_if_not_exists(os.path.dirname(file_path))
74
-
75
- # تحديد المكتب والورقة
76
- writer = pd.ExcelWriter(file_path, engine='xlsxwriter')
77
- df.to_excel(writer, sheet_name=sheet_name, index=index)
78
-
79
- # الحصول على مرجع لورقة العمل
80
- workbook = writer.book
81
- worksheet = writer.sheets[sheet_name]
82
-
83
- # إنشاء تنسيقات مخصصة
84
- header_format = workbook.add_format({
85
- 'bold': True,
86
- 'bg_color': '#CCCCCC',
87
- 'border': 1,
88
- 'align': 'center',
89
- 'valign': 'vcenter',
90
- 'text_wrap': True
91
- })
92
-
93
- number_format = workbook.add_format({
94
- 'num_format': '#,##0.00',
95
- 'align': 'right'
96
- })
97
-
98
- currency_format = workbook.add_format({
99
- 'num_format': '_-* #,##0.00 [$ريال]_-;-* #,##0.00 [$ريال]_-;_-* "-" [$ريال]_-;_-@_-',
100
- 'align': 'right'
101
- })
102
-
103
- date_format = workbook.add_format({
104
- 'num_format': 'yyyy-mm-dd',
105
- 'align': 'center'
106
- })
107
-
108
- text_format = workbook.add_format({
109
- 'align': 'right',
110
- 'text_wrap': True
111
- })
112
-
113
- # تطبيق التنسيقات على العناوين
114
- for col_num, value in enumerate(df.columns.values):
115
- worksheet.write(0, col_num + (1 if index else 0), value, header_format)
116
-
117
- # تعيين حجم الأعمدة
118
- if column_widths:
119
- for col_name, width in column_widths.items():
120
- if col_name in df.columns:
121
- col_idx = df.columns.get_loc(col_name) + (1 if index else 0)
122
- worksheet.set_column(col_idx, col_idx, width)
123
- else:
124
- # ضبط عرض الأعمدة تلقائيًا
125
- for col_num, col_name in enumerate(df.columns):
126
- max_len = df[col_name].astype(str).map(len).max()
127
- col_len = max(max_len, len(str(col_name))) + 2
128
- worksheet.set_column(col_num + (1 if index else 0), col_num + (1 if index else 0), col_len)
129
-
130
- # تطبيق التنسيقات حسب نوع البيانات
131
- for row_num in range(len(df)):
132
- for col_num, col_name in enumerate(df.columns):
133
- cell_value = df.iloc[row_num, col_num]
134
- cell_format = text_format
135
-
136
- # تحديد التنسيق المناسب بناءً على نوع البيانات
137
- if pd.api.types.is_numeric_dtype(df[col_name].dtype):
138
- if any(curr in col_name.lower() for curr in ['سعر', 'تكلفة', 'قيمة', 'مبلغ', 'ريال']):
139
- cell_format = currency_format
140
- else:
141
- cell_format = number_format
142
- elif pd.api.types.is_datetime64_dtype(df[col_name].dtype):
143
- cell_format = date_format
144
-
145
- # استخدام تنسيق مخصص إذا تم توفيره
146
- if formats and col_name in formats:
147
- custom_format = formats[col_name]
148
- if callable(custom_format):
149
- # إذا كان دالة، استدعاها لتطبيق التنسيق
150
- cell_value = custom_format(cell_value)
151
- else:
152
- # إذا كان تنسيق، استخدمه
153
- cell_format = custom_format
154
-
155
- worksheet.write(row_num + 1, col_num + (1 if index else 0), cell_value, cell_format)
156
-
157
- # تجميد الألواح إذا تم تحديده
158
- if freeze_panes:
159
- worksheet.freeze_panes(*freeze_panes)
160
-
161
- # حفظ الملف
162
- writer.close()
163
-
164
- return True
165
-
166
- except Exception as e:
167
- error_msg = f"خطأ في كتابة ملف Excel: {str(e)}"
168
- print(error_msg)
169
- traceback.print_exc()
170
- raise Exception(error_msg)
171
-
172
-
173
- def export_to_excel(data, file_path, sheet_name="Sheet1", customize_func=None):
174
- """
175
- تصدير البيانات إلى ملف Excel مع خيارات تخصيص
176
-
177
- المعلمات:
178
- data: DataFrame أو قاموس من DataFrames للتصدير
179
- file_path: مسار ملف Excel
180
- sheet_name: اسم الصفحة (افتراضي: Sheet1)
181
- customize_func: دالة لتخصيص المصنف قبل الحفظ (افتراضي: None)
182
-
183
- الإرجاع:
184
- True في حالة النجاح
185
- """
186
- try:
187
- # التأكد من وجود المجلد
188
- create_directory_if_not_exists(os.path.dirname(file_path))
189
-
190
- # إنشاء كائن الكاتب
191
- writer = pd.ExcelWriter(file_path, engine='xlsxwriter')
192
-
193
- # تصدير البيانات
194
- if isinstance(data, pd.DataFrame):
195
- # إذا كان DataFrame واحد
196
- data.to_excel(writer, sheet_name=sheet_name, index=False)
197
- elif isinstance(data, dict):
198
- # إذا كان قاموس من DataFrames
199
- for sheet, df in data.items():
200
- if isinstance(df, pd.DataFrame):
201
- df.to_excel(writer, sheet_name=sheet, index=False)
202
- else:
203
- raise ValueError("البيانات يجب أن تكون DataFrame أو قاموس من DataFrames")
204
-
205
- # تطبيق التخصيص إذا تم توفيره
206
- if customize_func and callable(customize_func):
207
- customize_func(writer)
208
-
209
- # حفظ الملف
210
- writer.close()
211
-
212
- return True
213
-
214
- except Exception as e:
215
- error_msg = f"خطأ في تصدير البيانات إلى Excel: {str(e)}"
216
- print(error_msg)
217
- traceback.print_exc()
218
- raise Exception(error_msg)
219
-
220
-
221
- def read_sheets_from_excel(file_path):
222
- """
223
- قراءة جميع صفحات ملف Excel
224
-
225
- المعلمات:
226
- file_path: مسار ملف Excel
227
-
228
- الإرجاع:
229
- قاموس من DataFrames بأسماء الصفحات كمفاتيح
230
- """
231
- try:
232
- # التحقق من وجود الملف
233
- if not os.path.exists(file_path):
234
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
235
-
236
- # التحقق من امتداد الملف
237
- ext = get_file_extension(file_path)
238
- if ext not in ['.xlsx', '.xls', '.xlsm']:
239
- raise ValueError(f"نوع الملف غير مدعوم: {ext}. يجب أن يكون الملف بامتداد .xlsx أو .xls أو .xlsm")
240
-
241
- # قراءة جميع الصفحات من الملف
242
- excel_file = pd.ExcelFile(file_path)
243
- sheets = {}
244
-
245
- for sheet_name in excel_file.sheet_names:
246
- sheets[sheet_name] = pd.read_excel(excel_file, sheet_name=sheet_name)
247
-
248
- return sheets
249
-
250
- except Exception as e:
251
- error_msg = f"خطأ في قراءة صفحات ملف Excel: {str(e)}"
252
- print(error_msg)
253
- traceback.print_exc()
254
- raise Exception(error_msg)
255
-
256
-
257
- def create_excel_report(data_dict, file_path, formats=None, column_widths=None, title=None, subtitle=None):
258
- """
259
- إنشاء تقرير Excel متقدم
260
-
261
- المعلمات:
262
- data_dict: قاموس من DataFrames للتصدير {sheet_name: DataFrame}
263
- file_path: مسار ملف Excel
264
- formats: قاموس للتنسيقات {sheet_name: {column_name: format}}
265
- column_widths: قاموس لعرض الأعمدة {sheet_name: {column_name: width}}
266
- title: عنوان التقرير
267
- subtitle: العنوان الفرعي للتقرير
268
-
269
- الإرجاع:
270
- True في حالة النجاح
271
- """
272
- try:
273
- # التأكد من وجود المجلد
274
- create_directory_if_not_exists(os.path.dirname(file_path))
275
-
276
- # إنشاء كائن الكاتب
277
- writer = pd.ExcelWriter(file_path, engine='xlsxwriter')
278
- workbook = writer.book
279
-
280
- # إنشاء التنسيقات العامة
281
- header_format = workbook.add_format({
282
- 'bold': True,
283
- 'bg_color': '#CCCCCC',
284
- 'border': 1,
285
- 'align': 'center',
286
- 'valign': 'vcenter',
287
- 'text_wrap': True
288
- })
289
-
290
- title_format = workbook.add_format({
291
- 'bold': True,
292
- 'font_size': 16,
293
- 'align': 'center',
294
- 'valign': 'vcenter',
295
- 'bg_color': '#E0E0E0',
296
- 'border': 2
297
- })
298
-
299
- subtitle_format = workbook.add_format({
300
- 'font_size': 12,
301
- 'align': 'center',
302
- 'valign': 'vcenter',
303
- 'bg_color': '#E0E0E0',
304
- 'border': 1
305
- })
306
-
307
- date_format = workbook.add_format({
308
- 'num_format': 'yyyy-mm-dd',
309
- 'align': 'center'
310
- })
311
-
312
- number_format = workbook.add_format({
313
- 'num_format': '#,##0.00',
314
- 'align': 'right'
315
- })
316
-
317
- currency_format = workbook.add_format({
318
- 'num_format': '_-* #,##0.00 [$ريال]_-;-* #,##0.00 [$ريال]_-;_-* "-" [$ريال]_-;_-@_-',
319
- 'align': 'right'
320
- })
321
-
322
- percent_format = workbook.add_format({
323
- 'num_format': '0.00%',
324
- 'align': 'right'
325
- })
326
-
327
- text_format = workbook.add_format({
328
- 'align': 'right',
329
- 'text_wrap': True
330
- })
331
-
332
- # تصدير البيانات
333
- current_row = 0
334
-
335
- # إضافة العنوان والعنوان الفرعي إذا تم توفيرهما
336
- if title or subtitle:
337
- for sheet_name in data_dict.keys():
338
- worksheet = workbook.add_worksheet(sheet_name)
339
- current_row = 0
340
-
341
- if title:
342
- worksheet.merge_range('A1:J1', title, title_format)
343
- current_row += 1
344
-
345
- if subtitle:
346
- worksheet.merge_range(f'A{current_row + 1}:J{current_row + 1}', subtitle, subtitle_format)
347
- current_row += 1
348
-
349
- # إضافة فاصل
350
- current_row += 1
351
-
352
- # كتابة البيانات
353
- df = data_dict[sheet_name]
354
- df.to_excel(writer, sheet_name=sheet_name, startrow=current_row, index=False)
355
-
356
- # تنسيق العناوين
357
- for col_num, value in enumerate(df.columns.values):
358
- worksheet.write(current_row, col_num, value, header_format)
359
-
360
- # تطبيق التنسيقات المخصصة
361
- if formats and sheet_name in formats:
362
- sheet_formats = formats[sheet_name]
363
- for col_name, fmt in sheet_formats.items():
364
- if col_name in df.columns:
365
- col_idx = df.columns.get_loc(col_name)
366
- for row_num in range(len(df)):
367
- cell_value = df.iloc[row_num, col_idx]
368
- worksheet.write(row_num + current_row + 1, col_idx, cell_value, fmt)
369
-
370
- # تعيين عرض الأعمدة
371
- if column_widths and sheet_name in column_widths:
372
- sheet_widths = column_widths[sheet_name]
373
- for col_name, width in sheet_widths.items():
374
- if col_name in df.columns:
375
- col_idx = df.columns.get_loc(col_name)
376
- worksheet.set_column(col_idx, col_idx, width)
377
- else:
378
- # ضبط عرض الأعمدة تلقائيًا
379
- for col_num, col_name in enumerate(df.columns):
380
- max_len = df[col_name].astype(str).map(len).max()
381
- col_len = max(max_len, len(str(col_name))) + 2
382
- worksheet.set_column(col_num, col_num, col_len)
383
- else:
384
- # إذا لم يتم توفير عنوان أو عنوان فرعي، استخدم الطريقة العادية
385
- for sheet_name, df in data_dict.items():
386
- df.to_excel(writer, sheet_name=sheet_name, index=False)
387
- worksheet = writer.sheets[sheet_name]
388
-
389
- # تنسيق العناوين
390
- for col_num, value in enumerate(df.columns.values):
391
- worksheet.write(0, col_num, value, header_format)
392
-
393
- # تطبيق التنسيقات المخصصة
394
- if formats and sheet_name in formats:
395
- sheet_formats = formats[sheet_name]
396
- for col_name, fmt in sheet_formats.items():
397
- if col_name in df.columns:
398
- col_idx = df.columns.get_loc(col_name)
399
- for row_num in range(len(df)):
400
- cell_value = df.iloc[row_num, col_idx]
401
- worksheet.write(row_num + 1, col_idx, cell_value, fmt)
402
-
403
- # تعيين عرض الأعمدة
404
- if column_widths and sheet_name in column_widths:
405
- sheet_widths = column_widths[sheet_name]
406
- for col_name, width in sheet_widths.items():
407
- if col_name in df.columns:
408
- col_idx = df.columns.get_loc(col_name)
409
- worksheet.set_column(col_idx, col_idx, width)
410
- else:
411
- # ضبط عرض الأعمدة تلقائيًا
412
- for col_num, col_name in enumerate(df.columns):
413
- max_len = df[col_name].astype(str).map(len).max()
414
- col_len = max(max_len, len(str(col_name))) + 2
415
- worksheet.set_column(col_num, col_num, col_len)
416
-
417
- # حفظ الملف
418
- writer.close()
419
-
420
- return True
421
-
422
- except Exception as e:
423
- error_msg = f"خطأ في إنشاء تقرير Excel: {str(e)}"
424
- print(error_msg)
425
- traceback.print_exc()
426
- raise Exception(error_msg)
427
-
428
-
429
- def extract_data_from_excel(file_path, columns_mapping=None, sheet_name=0, header_row=0, data_start_row=1):
430
- """
431
- استخراج بيانات منظمة من ملف Excel
432
-
433
- المعلمات:
434
- file_path: مسار ملف Excel
435
- columns_mapping: قاموس لتخطيط الأعمدة {اسم_العمود_الجديد: اسم_العمود_الأصلي}
436
- sheet_name: اسم أو رقم الصفحة (افتراضي: 0)
437
- header_row: رقم صف العناوين (افتراضي: 0)
438
- data_start_row: رقم صف بداية البيانات (افتراضي: 1)
439
-
440
- الإرجاع:
441
- DataFrame من البيانات المستخرجة
442
- """
443
- try:
444
- # قراءة الملف
445
- df = pd.read_excel(
446
- file_path,
447
- sheet_name=sheet_name,
448
- header=header_row,
449
- skiprows=range(1, data_start_row) if data_start_row > 1 else None
450
- )
451
-
452
- # تنظيف العناوين (إزالة المسافات الزائدة)
453
- df.columns = df.columns.str.strip()
454
-
455
- # إعادة تسمية الأعمدة إذا تم توفير تخطيط
456
- if columns_mapping:
457
- # التحقق من وجود جميع الأعمدة المطلوبة
458
- missing_columns = [col for col in columns_mapping.values() if col not in df.columns]
459
- if missing_columns:
460
- raise ValueError(f"الأعمدة التالية غير موجودة في الملف: {', '.join(missing_columns)}")
461
-
462
- # إعادة تسمية الأعمدة
463
- df = df.rename(columns={v: k for k, v in columns_mapping.items()})
464
-
465
- # اختيار الأعمدة المطلوبة فقط
466
- df = df[list(columns_mapping.keys())]
467
-
468
- # تنظيف البيانات
469
- for col in df.columns:
470
- # تحويل الأعمدة النصية
471
- if df[col].dtype == 'object':
472
- df[col] = df[col].astype(str).str.strip()
473
-
474
- # محاولة تحويل الأعمدة الرقمية
475
- try:
476
- df[col] = pd.to_numeric(df[col], errors='ignore')
477
- except:
478
- pass
479
-
480
- return df
481
-
482
- except Exception as e:
483
- error_msg = f"خطأ في استخراج البيانات من ملف Excel: {str(e)}"
484
- print(error_msg)
485
- traceback.print_exc()
486
- raise Exception(error_msg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/helpers.py DELETED
@@ -1,332 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
-
4
- """
5
- وحدة المساعدة المركزية للنظام
6
- تحتوي على دوال مساعدة مشتركة تستخدم في جميع أنحاء التطبيق
7
- """
8
-
9
- import os
10
- import sys
11
- import streamlit as st
12
- import pandas as pd
13
- import numpy as np
14
- import json
15
- import re
16
- import time
17
- import tempfile
18
- from datetime import datetime, timedelta
19
- import random
20
- import secrets
21
- import shutil
22
- import base64
23
- import logging
24
- from pathlib import Path
25
-
26
- # تكوين التسجيلات
27
- logging.basicConfig(
28
- level=logging.INFO,
29
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
30
- )
31
- logger = logging.getLogger("wahbi-ai")
32
-
33
-
34
- def create_directory_if_not_exists(directory_path):
35
- """إنشاء مسار إذا لم يكن موجوداً"""
36
- try:
37
- if not os.path.exists(directory_path):
38
- os.makedirs(directory_path)
39
- logger.info(f"تم إنشاء المجلد: {directory_path}")
40
- return True
41
- except Exception as e:
42
- logger.error(f"خطأ في إنشاء المجلد {directory_path}: {e}")
43
- return False
44
-
45
-
46
- def get_data_folder():
47
- """الحصول على مسار مجلد البيانات الرئيسي"""
48
- # مسار بيانات النظام الرئيسي
49
- data_folder = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data")
50
- create_directory_if_not_exists(data_folder)
51
- return data_folder
52
-
53
-
54
- def load_config():
55
- """تحميل إعدادات التكوين من ملف config.json"""
56
- config_path = os.path.join(get_data_folder(), "config.json")
57
-
58
- # إذا لم يكن ملف التكوين موجوداً، قم بإنشاء ملف افتراضي
59
- if not os.path.exists(config_path):
60
- default_config = {
61
- "system": {
62
- "version": "1.0.0",
63
- "release_date": "2025-03-30",
64
- "company_name": "شركة شبه الجزيرة للمقاولات",
65
- "company_logo": "",
66
- "language": "ar",
67
- "theme": "light",
68
- "debug_mode": False
69
- },
70
- "ai_models": {
71
- "default_model": "claude-3-7-sonnet-20250219",
72
- "openai_model": "gpt-4o",
73
- "huggingface_model": "mistralai/Mistral-7B-Instruct-v0.2"
74
- },
75
- "notifications": {
76
- "enable_email": False,
77
- "enable_browser": True,
78
- "check_interval": 60
79
- }
80
- }
81
-
82
- with open(config_path, 'w', encoding='utf-8') as f:
83
- json.dump(default_config, f, ensure_ascii=False, indent=2)
84
-
85
- return default_config
86
-
87
- # تحميل ملف التكوين الموجود
88
- try:
89
- with open(config_path, 'r', encoding='utf-8') as f:
90
- return json.load(f)
91
- except Exception as e:
92
- logger.error(f"خطأ في تحميل ملف التكوين: {e}")
93
- return {}
94
-
95
-
96
- def save_config(config):
97
- """حفظ إعدادات التكوين إلى ملف config.json"""
98
- config_path = os.path.join(get_data_folder(), "config.json")
99
-
100
- try:
101
- with open(config_path, 'w', encoding='utf-8') as f:
102
- json.dump(config, f, ensure_ascii=False, indent=2)
103
- return True
104
- except Exception as e:
105
- logger.error(f"خطأ في حفظ ملف التكوين: {e}")
106
- return False
107
-
108
-
109
- def format_time(timestamp=None, format_str="%Y-%m-%d %H:%M:%S"):
110
- """تنسيق الوقت إلى صيغة معينة"""
111
- if timestamp is None:
112
- timestamp = datetime.now()
113
- elif isinstance(timestamp, (int, float)):
114
- timestamp = datetime.fromtimestamp(timestamp)
115
-
116
- return timestamp.strftime(format_str)
117
-
118
-
119
- def get_user_info():
120
- """الحصول على معلومات المستخدم الحالي"""
121
- # في التطبيق الفعلي، يمكن استرداد معلومات المستخدم من قاعدة البيانات أو من حالة الجلسة
122
- if "user_info" in st.session_state:
123
- return st.session_state.user_info
124
-
125
- # معلومات افتراضية للتطوير
126
- return {
127
- "id": 1,
128
- "username": "admin",
129
- "full_name": "مدير النظام",
130
- "email": "[email protected]",
131
- "role": "مدير",
132
- "department": "الإدارة",
133
- "last_login": format_time()
134
- }
135
-
136
-
137
- def get_current_project():
138
- """الحصول على معلومات المشروع الحالي"""
139
- if "current_project" in st.session_state:
140
- return st.session_state.current_project
141
-
142
- # في حالة عدم وجود مشروع محدد
143
- return None
144
-
145
-
146
- def load_icons():
147
- """تحميل الأيقونات المستخدمة في النظام"""
148
- icons_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "icons")
149
- icons = {}
150
-
151
- # التأكد من وجود مجلد ��لأيقونات
152
- if not os.path.exists(icons_path):
153
- create_directory_if_not_exists(icons_path)
154
- return icons
155
-
156
- # تحميل جميع الأيقونات
157
- for icon_file in os.listdir(icons_path):
158
- if icon_file.endswith(('.png', '.svg', '.jpg')):
159
- icon_name = os.path.splitext(icon_file)[0]
160
- icon_path = os.path.join(icons_path, icon_file)
161
-
162
- try:
163
- with open(icon_path, "rb") as f:
164
- icons[icon_name] = base64.b64encode(f.read()).decode()
165
- except Exception as e:
166
- logger.error(f"خطأ في تحميل الأيقونة {icon_name}: {e}")
167
-
168
- return icons
169
-
170
-
171
- def get_random_id(length=8):
172
- """إنشاء معرف عشوائي بطول محدد"""
173
- return secrets.token_hex(length // 2)
174
-
175
-
176
- def compress_text(text, max_length=10000):
177
- """اختصار النص إلى حد أقصى محدد مع الحفاظ على المعنى"""
178
- if not text or len(text) <= max_length:
179
- return text
180
-
181
- # تقسيم النص إلى جمل
182
- sentences = re.split(r'(?<=[.!?])\s+', text)
183
-
184
- # حساب متوسط طول الجملة
185
- avg_sentence_length = len(text) / len(sentences)
186
-
187
- # حساب عدد الجمل التي يمكن تضمينها
188
- num_sentences_to_keep = int(max_length / avg_sentence_length)
189
-
190
- # الاحتفاظ بالجمل الأولى والأخيرة للحفاظ على السياق
191
- keep_first = num_sentences_to_keep // 2
192
- keep_last = num_sentences_to_keep - keep_first
193
-
194
- # دمج الجمل المختارة
195
- compressed_text = ' '.join(sentences[:keep_first] + sentences[-keep_last:])
196
-
197
- # إضافة إشارة إلى أن النص تم اختصاره
198
- if len(compressed_text) < len(text):
199
- compressed_text += " [...المزيد من النص تم اختصاره...]"
200
-
201
- return compressed_text
202
-
203
-
204
- def str_to_bool(text):
205
- """تحويل النص إلى قيمة منطقية"""
206
- return text.lower() in ('yes', 'true', 'y', 't', '1', 'نعم', 'صحيح')
207
-
208
-
209
- def handle_arabic_text(text):
210
- """معالجة النص العربي للعرض بشكل صحيح"""
211
- if not text:
212
- return ""
213
-
214
- # إضافة علامة RTL لضمان عرض النص العربي بشكل صحيح
215
- return f"<div dir='rtl'>{text}</div>"
216
-
217
-
218
- def render_credits():
219
- """عرض معلومات النظام وحقوق الملكية"""
220
- st.markdown("---")
221
-
222
- config = load_config()
223
- system_info = config.get("system", {})
224
-
225
- col1, col2, col3 = st.columns([1, 2, 1])
226
-
227
- with col2:
228
- st.markdown(
229
- f"""
230
- <div style="text-align: center; color: #666;">
231
- <p>{system_info.get('company_name', 'شركة شبه الجزيرة للمقاولات')}</p>
232
- <p>الإصدار: {system_info.get('version', '1.0.0')}</p>
233
- <p>© جميع الحقوق محفوظة 2025</p>
234
- </div>
235
- """,
236
- unsafe_allow_html=True
237
- )
238
-
239
-
240
- # دالة للحصول على اتصال قاعدة البيانات
241
- def get_connection():
242
- """
243
- دالة للحصول على اتصال بقاعدة البيانات
244
-
245
- ملاحظة: ينبغي أن تكون مستبدلة بالدالة من db_connector.py في البيئة الإنتاجية
246
- """
247
- try:
248
- # استيراد المستوى الفعلي للاتصال بقاعدة البيانات
249
- from database.db_connector import get_connection as get_db_connection
250
- return get_db_connection()
251
- except ImportError:
252
- # إذا كان الاتصال بقاعدة البيانات غير متاح
253
- logger.warning("لم يتم العثور على وحدة اتصال قاعدة البيانات. استخدام مخزن بيانات مؤقت.")
254
- # إرجاع None للإشارة إلى عدم وجود اتصال
255
- return None
256
-
257
-
258
- def load_css(file_name=None):
259
- """تحميل ملف CSS مخصص"""
260
- try:
261
- if file_name:
262
- css_file = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "css", file_name)
263
-
264
- if os.path.exists(css_file):
265
- with open(css_file, "r", encoding="utf-8") as f:
266
- css_content = f.read()
267
- else:
268
- logger.warning(f"ملف CSS غير موجود: {css_file}")
269
- return
270
- else:
271
- # CSS افتراضي
272
- css_content = """
273
- .sidebar .sidebar-content {
274
- direction: rtl;
275
- text-align: right;
276
- }
277
- div[data-testid="stForm"] {
278
- border: 1px solid #ddd;
279
- padding: 10px;
280
- border-radius: 10px;
281
- }
282
- .module-title {
283
- color: #1E88E5;
284
- text-align: center;
285
- font-size: 1.8rem;
286
- margin-bottom: 1rem;
287
- }
288
- .instructions {
289
- background-color: #f8f9fa;
290
- border-right: 3px solid #4CAF50;
291
- padding: 10px;
292
- margin-bottom: 15px;
293
- }
294
- .results-container {
295
- background-color: #f5f5f5;
296
- padding: 15px;
297
- border-radius: 5px;
298
- margin-top: 20px;
299
- }
300
- .risk-high {
301
- color: #d32f2f;
302
- font-weight: bold;
303
- }
304
- .risk-medium {
305
- color: #f57c00;
306
- font-weight: bold;
307
- }
308
- .risk-low {
309
- color: #388e3c;
310
- font-weight: bold;
311
- }
312
- .form-container {
313
- background-color: #f9f9f9;
314
- padding: 20px;
315
- border-radius: 10px;
316
- margin-bottom: 20px;
317
- }
318
- .section-header {
319
- color: #2196F3;
320
- font-size: 1.2rem;
321
- font-weight: bold;
322
- margin-top: 20px;
323
- margin-bottom: 10px;
324
- border-bottom: 1px solid #eee;
325
- padding-bottom: 5px;
326
- }
327
- """
328
-
329
- st.markdown(f"<style>{css_content}</style>", unsafe_allow_html=True)
330
-
331
- except Exception as e:
332
- logger.error(f"خطأ في تحميل ملف CSS: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/helpers/__init__.py DELETED
@@ -1,70 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
-
4
- """حزمة المساعدات العامة المستخدمة في النظام"""
5
-
6
- import streamlit as st
7
- import pandas as pd
8
- import numpy as np
9
- import os
10
- import sqlite3
11
-
12
- from .utils import (
13
- create_directory_if_not_exists,
14
- get_data_folder,
15
- format_time,
16
- get_user_info,
17
- load_css,
18
- render_credits,
19
- load_icons,
20
- format_number,
21
- format_currency,
22
- styled_button,
23
- filter_dataframe,
24
- get_file_extension,
25
- extract_numbers_from_text
26
- )
27
-
28
- def get_connection():
29
- """
30
- إنشاء اتصال وهمي لقاعدة البيانات للاستخدام عند عدم توفر اتصال PostgreSQL
31
-
32
- الإرجاع:
33
- اتصال وهمي لقاعدة البيانات
34
- """
35
- # إنشاء مجلد البيانات إذا لم يكن موجوداً
36
- data_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'data')
37
- os.makedirs(data_dir, exist_ok=True)
38
-
39
- # إنشاء اتصال قاعدة بيانات SQLite محلية
40
- db_path = os.path.join(data_dir, 'local_db.sqlite')
41
- conn = sqlite3.connect(db_path)
42
-
43
- # إعادة محاكاة سلوك اتصال PostgreSQL
44
- conn.execute = conn.cursor().execute
45
-
46
- # إضافة وظيفة وهمية للاقتطاع (commit) والإغلاق
47
- original_close = conn.close
48
- def enhanced_close():
49
- conn.commit()
50
- original_close()
51
- conn.close = enhanced_close
52
-
53
- return conn
54
-
55
- __all__ = [
56
- 'create_directory_if_not_exists',
57
- 'get_data_folder',
58
- 'format_time',
59
- 'get_user_info',
60
- 'load_css',
61
- 'render_credits',
62
- 'load_icons',
63
- 'format_number',
64
- 'format_currency',
65
- 'styled_button',
66
- 'filter_dataframe',
67
- 'get_file_extension',
68
- 'extract_numbers_from_text',
69
- 'get_connection'
70
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/helpers/utils.py DELETED
@@ -1,282 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
-
4
- """
5
- وحدة المساعدات العامة
6
- توفر هذه الوحدة مجموعة من الدوال المساعدة المستخدمة في مختلف أجزاء النظام
7
- """
8
-
9
- import os
10
- import datetime
11
- import json
12
- import re
13
- import streamlit as st
14
-
15
- def create_directory_if_not_exists(directory_path):
16
- """إنشاء مجلد إذا لم يكن موجوداً بالفعل"""
17
- if not os.path.exists(directory_path):
18
- os.makedirs(directory_path)
19
- return True
20
- return False
21
-
22
- def get_data_folder():
23
- """الحصول على مسار مجلد البيانات"""
24
- data_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'data'))
25
- create_directory_if_not_exists(data_folder)
26
- return data_folder
27
-
28
- def format_time(timestamp=None):
29
- """تنسيق الوقت بصيغة قابلة للقراءة"""
30
- if timestamp is None:
31
- timestamp = datetime.datetime.now()
32
- return timestamp.strftime("%Y-%m-%d %H:%M:%S")
33
-
34
- def get_user_info():
35
- """الحصول على معلومات المستخدم الحالي"""
36
- # في الوقت الحالي، نستخدم معلومات مستخدم افتراضية
37
- # يمكن تعديل هذه الدالة لاحقاً للتكامل مع نظام المصادقة
38
- return {
39
- "id": 1,
40
- "username": "admin",
41
- "name": "مدير النظام",
42
- "role": "admin"
43
- }
44
-
45
- def load_css():
46
- """تحميل أنماط CSS المخصصة"""
47
- st.markdown("""
48
- <style>
49
- .module-title {
50
- color: #1E88E5;
51
- text-align: center;
52
- margin-bottom: 20px;
53
- font-weight: bold;
54
- }
55
-
56
- .section-title {
57
- color: #1565C0;
58
- margin-top: 20px;
59
- margin-bottom: 10px;
60
- font-weight: bold;
61
- }
62
-
63
- .info-box {
64
- background-color: #E3F2FD;
65
- padding: 15px;
66
- border-radius: 5px;
67
- margin-bottom: 15px;
68
- }
69
-
70
- .warning-box {
71
- background-color: #FFF8E1;
72
- padding: 15px;
73
- border-radius: 5px;
74
- margin-bottom: 15px;
75
- }
76
-
77
- .error-box {
78
- background-color: #FFEBEE;
79
- padding: 15px;
80
- border-radius: 5px;
81
- margin-bottom: 15px;
82
- }
83
-
84
- .success-box {
85
- background-color: #E8F5E9;
86
- padding: 15px;
87
- border-radius: 5px;
88
- margin-bottom: 15px;
89
- }
90
-
91
- .centered {
92
- text-align: center;
93
- }
94
-
95
- .footer {
96
- text-align: center;
97
- margin-top: 30px;
98
- color: #78909C;
99
- font-size: 0.8em;
100
- }
101
-
102
- /* تحسين تصميم الجداول */
103
- table {
104
- width: 100%;
105
- }
106
-
107
- thead th {
108
- background-color: #1976D2 !important;
109
- color: white !important;
110
- }
111
-
112
- tbody tr:nth-child(even) {
113
- background-color: #f2f2f2;
114
- }
115
-
116
- tbody tr:hover {
117
- background-color: #E3F2FD;
118
- }
119
-
120
- /* تحسين ظهور الفورم */
121
- input, select, textarea {
122
- border-radius: 5px !important;
123
- border: 1px solid #ddd !important;
124
- padding: 10px !important;
125
- }
126
-
127
- .stButton>button {
128
- border-radius: 5px !important;
129
- }
130
-
131
- /* زيادة حجم الخط للتوافق مع اللغة العربية */
132
- html, body, [class*="css"] {
133
- font-family: 'Tajawal', sans-serif;
134
- }
135
- </style>
136
-
137
- <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;700&display=swap">
138
- """, unsafe_allow_html=True)
139
-
140
- def render_credits():
141
- """عرض المعلومات عن حقوق الملكية وإصدار النظام"""
142
- st.markdown("""
143
- <div class="footer">
144
- <p>هذا النظام يعمل لصالح شركة شبه الجزيرة للمقاولات، جميع الحقوق محفوظة 2025</p>
145
- <p>نظام WAHBi AI - الإصدار 2.0</p>
146
- </div>
147
- """, unsafe_allow_html=True)
148
-
149
- def load_icons():
150
- """تحميل الأيقونات المستخدمة في النظام"""
151
- icons = {
152
- "project": "🏗️",
153
- "document": "📄",
154
- "analysis": "🔍",
155
- "warning": "⚠️",
156
- "success": "✅",
157
- "error": "❌",
158
- "info": "ℹ️",
159
- "settings": "⚙️",
160
- "user": "👤",
161
- "money": "💰",
162
- "time": "⏱️",
163
- "location": "📍",
164
- "notification": "🔔",
165
- "edit": "✏️",
166
- "delete": "🗑️",
167
- "upload": "📤",
168
- "download": "📥",
169
- "save": "💾",
170
- "cancel": "❌",
171
- "add": "➕",
172
- "calendar": "📅",
173
- "chat": "💬",
174
- "search": "🔎",
175
- "star": "⭐",
176
- "trophy": "🏆",
177
- "medal": "🥇",
178
- "chart": "📊",
179
- "map": "🗺️",
180
- "building": "🏢",
181
- "road": "🛣️",
182
- "bridge": "🌉",
183
- }
184
- return icons
185
-
186
- def format_number(number, decimal_places=2):
187
- """تنسيق الأرقام بطريقة أنيقة"""
188
- if isinstance(number, (int, float)):
189
- if decimal_places == 0:
190
- return "{:,.0f}".format(number)
191
- else:
192
- return "{:,.{dp}f}".format(number, dp=decimal_places)
193
- return str(number)
194
-
195
- def format_currency(amount, currency="ريال", decimal_places=2):
196
- """تنسيق المبالغ المالية"""
197
- if amount is None:
198
- return "غير محدد"
199
- formatted = format_number(amount, decimal_places)
200
- return f"{formatted} {currency}"
201
-
202
- def styled_button(label, key=None, type="primary", on_click=None, args=None, full_width=False, icon=None, is_link=False, help=None):
203
- """
204
- إنشاء زر بتنسيق معين
205
- :param label: نص الزر
206
- :param key: مفتاح الزر الفريد
207
- :param type: نوع التنسيق ('primary', 'secondary', 'success', 'warning', 'danger', 'info', 'glass', 'flat')
208
- :param on_click: الدالة التي سيتم تنفيذها عند النقر
209
- :param args: معاملات الدالة
210
- :param full_width: هل يأخذ الزر العرض كاملاً
211
- :param icon: أيقونة لعرضها قبل النص (emoji أو HTML)
212
- :param is_link: إذا كان الزر رابطاً بدلاً من زر عادي
213
- :param help: نص المساعدة للزر
214
- :return: زر مُنسّق
215
- """
216
- if is_link:
217
- btn_class = f"{type}-btn"
218
- if icon:
219
- btn_class += " action-btn"
220
- label_with_icon = f"{icon} {label}"
221
- else:
222
- label_with_icon = label
223
-
224
- button_html = f"""
225
- <div class="{btn_class}">
226
- {label_with_icon}
227
- </div>
228
- """
229
- return st.markdown(button_html, unsafe_allow_html=True)
230
- else:
231
- with st.container():
232
- btn_class = f"{type}-btn"
233
- if icon:
234
- btn_class += " action-btn"
235
- label_with_icon = f"{icon} {label}"
236
- else:
237
- label_with_icon = label
238
-
239
- st.markdown(f'<div class="{btn_class}">', unsafe_allow_html=True)
240
- clicked = st.button(label_with_icon, key=key, on_click=on_click, args=args, use_container_width=full_width, help=help)
241
- st.markdown('</div>', unsafe_allow_html=True)
242
- return clicked
243
-
244
- def filter_dataframe(df, column, value):
245
- """ترشيح إطار البيانات"""
246
- if value == "الكل":
247
- return df
248
- return df[df[column] == value]
249
-
250
- def get_file_extension(filename):
251
- """استخراج امتداد الملف"""
252
- if not filename:
253
- return ""
254
- return os.path.splitext(filename)[-1].lower()
255
-
256
- def extract_numbers_from_text(text):
257
- """استخراج الأرقام من النص
258
-
259
- Args:
260
- text (str): النص المراد استخراج الأرقام منه
261
-
262
- Returns:
263
- list: قائمة بالأرقام المستخرجة
264
- """
265
- if not text:
266
- return []
267
-
268
- # نمط للعثور على الأرقام (صحيحة أو عشرية) في النص
269
- pattern = r'[-+]?\d*\.\d+|\d+'
270
-
271
- # استخراج جميع الأرقام من النص
272
- numbers = re.findall(pattern, text)
273
-
274
- # تحويل النصوص المستخرجة إلى أرقام (صحيحة أو عشرية)
275
- converted_numbers = []
276
- for num in numbers:
277
- if '.' in num:
278
- converted_numbers.append(float(num))
279
- else:
280
- converted_numbers.append(int(num))
281
-
282
- return converted_numbers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/pdf_handler.py DELETED
@@ -1,847 +0,0 @@
1
- """
2
- معالج ملفات PDF
3
- """
4
-
5
- import os
6
- import io
7
- import re
8
- import PyPDF2
9
- import fitz # PyMuPDF
10
- import pdfplumber
11
- import numpy as np
12
- from PIL import Image
13
- import pytesseract
14
- import pandas as pd
15
- import traceback
16
- from reportlab.lib.pagesizes import A4, landscape
17
- from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image as ReportLabImage
18
- from reportlab.lib import colors
19
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
20
- from reportlab.pdfbase import pdfmetrics
21
- from reportlab.pdfbase.ttfonts import TTFont
22
- import matplotlib.pyplot as plt
23
-
24
- from utils.helpers import create_directory_if_not_exists, extract_numbers_from_text
25
-
26
- # محاولة تسجيل الخطوط العربية إذا كانت متوفرة
27
- try:
28
- font_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "fonts", "Amiri-Regular.ttf")
29
- bold_font_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "fonts", "Amiri-Bold.ttf")
30
-
31
- if os.path.exists(font_path) and os.path.exists(bold_font_path):
32
- pdfmetrics.registerFont(TTFont('Arabic', font_path))
33
- pdfmetrics.registerFont(TTFont('ArabicBold', bold_font_path))
34
- except Exception as e:
35
- print(f"تعذر تحميل الخطوط العربية: {str(e)}. سيتم استخدام الخطوط الافتراضية.")
36
-
37
-
38
- def extract_text_from_pdf(file_path, method='pymupdf'):
39
- """
40
- استخراج النص من ملف PDF
41
-
42
- المعلمات:
43
- file_path: مسار ملف PDF
44
- method: طريقة الاستخراج ('pymupdf', 'pypdf2', 'pdfplumber')
45
-
46
- الإرجاع:
47
- نص مستخرج من ملف PDF
48
- """
49
- try:
50
- # التحقق من وجود الملف
51
- if not os.path.exists(file_path):
52
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
53
-
54
- # استخراج النص حسب الطريقة المطلوبة
55
- if method.lower() == 'pymupdf':
56
- return _extract_text_with_pymupdf(file_path)
57
- elif method.lower() == 'pypdf2':
58
- return _extract_text_with_pypdf2(file_path)
59
- elif method.lower() == 'pdfplumber':
60
- return _extract_text_with_pdfplumber(file_path)
61
- else:
62
- # استخدام PyMuPDF كطريقة افتراضية
63
- return _extract_text_with_pymupdf(file_path)
64
-
65
- except Exception as e:
66
- error_msg = f"خطأ في استخراج النص من ملف PDF: {str(e)}"
67
- print(error_msg)
68
- traceback.print_exc()
69
- raise Exception(error_msg)
70
-
71
-
72
- def _extract_text_with_pymupdf(file_path):
73
- """استخراج النص باستخدام PyMuPDF"""
74
- document = fitz.open(file_path)
75
- text = ""
76
-
77
- for page_number in range(len(document)):
78
- page = document.load_page(page_number)
79
- text += page.get_text("text") + "\n\n"
80
-
81
- document.close()
82
- return text
83
-
84
-
85
- def _extract_text_with_pypdf2(file_path):
86
- """استخراج النص باستخدام PyPDF2"""
87
- with open(file_path, 'rb') as file:
88
- reader = PyPDF2.PdfReader(file)
89
- text = ""
90
-
91
- for page_number in range(len(reader.pages)):
92
- page = reader.pages[page_number]
93
- text += page.extract_text() + "\n\n"
94
-
95
- return text
96
-
97
-
98
- def _extract_text_with_pdfplumber(file_path):
99
- """استخراج النص باستخدام pdfplumber"""
100
- with pdfplumber.open(file_path) as pdf:
101
- text = ""
102
-
103
- for page in pdf.pages:
104
- text += page.extract_text() + "\n\n"
105
-
106
- return text
107
-
108
-
109
- def extract_tables_from_pdf(file_path, page_numbers=None):
110
- """
111
- استخراج الجداول من ملف PDF
112
-
113
- المعلمات:
114
- file_path: مسار ملف PDF
115
- page_numbers: قائمة بأرقام الصفحات للاستخراج منها (افتراضي: None لجميع الصفحات)
116
-
117
- الإرجاع:
118
- قائمة من DataFrames تمثل الجداول المستخرجة
119
- """
120
- try:
121
- # التحقق من وجود الملف
122
- if not os.path.exists(file_path):
123
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
124
-
125
- # استخراج الجداول باستخدام pdfplumber
126
- tables = []
127
-
128
- with pdfplumber.open(file_path) as pdf:
129
- # تحديد الصفحات المراد استخراج الجداول منها
130
- if page_numbers is None:
131
- pages_to_extract = range(len(pdf.pages))
132
- else:
133
- pages_to_extract = [p-1 for p in page_numbers if 1 <= p <= len(pdf.pages)]
134
-
135
- # استخراج الجداول من كل صفحة
136
- for page_idx in pages_to_extract:
137
- page = pdf.pages[page_idx]
138
- page_tables = page.extract_tables()
139
-
140
- if page_tables:
141
- for table in page_tables:
142
- if table: # التحقق من أن الجدول ليس فارغًا
143
- # تحويل الجدول إلى DataFrame
144
- df = pd.DataFrame(table[1:], columns=table[0])
145
-
146
- # تنظيف البيانات
147
- df = df.applymap(lambda x: x.strip() if isinstance(x, str) else x)
148
-
149
- # إضافة إلى قائمة الجداول
150
- tables.append(df)
151
-
152
- return tables
153
-
154
- except Exception as e:
155
- error_msg = f"خطأ في استخراج الجداول من ملف PDF: {str(e)}"
156
- print(error_msg)
157
- traceback.print_exc()
158
- raise Exception(error_msg)
159
-
160
-
161
- def extract_images_from_pdf(file_path, output_dir=None, prefix='image'):
162
- """
163
- استخراج الصور من ملف PDF
164
-
165
- المعلمات:
166
- file_path: مسار ملف PDF
167
- output_dir: دليل الإخراج (افتراضي: None للإرجاع كقائمة من الصور)
168
- prefix: بادئة أسماء ملفات الصور
169
-
170
- الإرجاع:
171
- قائمة من مسارات الصور المستخرجة إذا تم تحديد دليل الإخراج، وإلا قائمة من الصور
172
- """
173
- try:
174
- # التحقق من وجود الملف
175
- if not os.path.exists(file_path):
176
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
177
-
178
- # إنشاء دليل الإخراج إذا تم تحديده
179
- if output_dir:
180
- create_directory_if_not_exists(output_dir)
181
-
182
- # استخراج الصور باستخدام PyMuPDF
183
- document = fitz.open(file_path)
184
- images = []
185
- image_paths = []
186
-
187
- for page_idx in range(len(document)):
188
- page = document.load_page(page_idx)
189
-
190
- # استخراج الصور من الصفحة
191
- image_list = page.get_images(full=True)
192
-
193
- for img_idx, img_info in enumerate(image_list):
194
- xref = img_info[0]
195
- base_image = document.extract_image(xref)
196
- image_bytes = base_image["image"]
197
-
198
- # إنشاء كائن الصورة
199
- image = Image.open(io.BytesIO(image_bytes))
200
-
201
- if output_dir:
202
- # حفظ الصورة في الدليل المحدد
203
- image_filename = f"{prefix}_{page_idx+1}_{img_idx+1}.{base_image['ext']}"
204
- image_path = os.path.join(output_dir, image_filename)
205
- image.save(image_path)
206
- image_paths.append(image_path)
207
- else:
208
- # إضافة الصورة إلى القائمة
209
- images.append(image)
210
-
211
- document.close()
212
-
213
- return image_paths if output_dir else images
214
-
215
- except Exception as e:
216
- error_msg = f"خطأ في استخراج الصور من ملف PDF: {str(e)}"
217
- print(error_msg)
218
- traceback.print_exc()
219
- raise Exception(error_msg)
220
-
221
-
222
- def extract_text_from_image(image, lang='ara+eng'):
223
- """
224
- استخراج النص من صورة باستخدام OCR
225
-
226
- المعلمات:
227
- image: كائن الصورة أو مسار الصورة
228
- lang: لغة النص (افتراضي: 'ara+eng' للعربية والإنجليزية)
229
-
230
- الإرجاع:
231
- النص المستخرج من الصورة
232
- """
233
- try:
234
- # إذا كان مسار صورة، قم بفتحها
235
- if isinstance(image, str):
236
- image = Image.open(image)
237
-
238
- # استخراج النص باستخدام pytesseract
239
- text = pytesseract.image_to_string(image, lang=lang)
240
-
241
- return text
242
-
243
- except Exception as e:
244
- error_msg = f"خطأ في استخراج النص من الصورة: {str(e)}"
245
- print(error_msg)
246
- traceback.print_exc()
247
- return ""
248
-
249
-
250
- def ocr_pdf(file_path, lang='ara+eng'):
251
- """
252
- تنفيذ OCR على ملف PDF
253
-
254
- المعلمات:
255
- file_path: مسار ملف PDF
256
- lang: لغة النص (افتراضي: 'ara+eng' للعربية والإنجليزية)
257
-
258
- الإرجاع:
259
- النص المستخرج من ملف PDF
260
- """
261
- try:
262
- # التحقق من وجود الملف
263
- if not os.path.exists(file_path):
264
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
265
-
266
- # فتح ملف PDF
267
- document = fitz.open(file_path)
268
- text = ""
269
-
270
- for page_idx in range(len(document)):
271
- page = document.load_page(page_idx)
272
-
273
- # تحويل الصفحة إلى صورة
274
- pix = page.get_pixmap(matrix=fitz.Matrix(300/72, 300/72))
275
- img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
276
-
277
- # استخراج النص من الصورة
278
- page_text = extract_text_from_image(img, lang=lang)
279
- text += page_text + "\n\n"
280
-
281
- document.close()
282
-
283
- return text
284
-
285
- except Exception as e:
286
- error_msg = f"خطأ في تنفيذ OCR على ملف PDF: {str(e)}"
287
- print(error_msg)
288
- traceback.print_exc()
289
- raise Exception(error_msg)
290
-
291
-
292
- def search_in_pdf(file_path, search_text):
293
- """
294
- البحث عن نص في ملف PDF
295
-
296
- المعلمات:
297
- file_path: مسار ملف PDF
298
- search_text: النص المراد البحث عنه
299
-
300
- الإرجاع:
301
- قائمة من النتائج {page_number, text_snippet, matched_text}
302
- """
303
- try:
304
- # التحقق من وجود الملف
305
- if not os.path.exists(file_path):
306
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
307
-
308
- # استخراج النص من ملف PDF
309
- document = fitz.open(file_path)
310
- results = []
311
-
312
- for page_idx in range(len(document)):
313
- page = document.load_page(page_idx)
314
- page_text = page.get_text("text")
315
-
316
- # البحث عن النص
317
- if search_text.lower() in page_text.lower():
318
- # استخراج المقتطفات التي تحتوي على النص المطلوب
319
- lines = page_text.split('\n')
320
- for line in lines:
321
- if search_text.lower() in line.lower():
322
- results.append({
323
- 'page_number': page_idx + 1,
324
- 'text_snippet': line,
325
- 'matched_text': search_text
326
- })
327
-
328
- document.close()
329
-
330
- return results
331
-
332
- except Exception as e:
333
- error_msg = f"خطأ في البحث في ملف PDF: {str(e)}"
334
- print(error_msg)
335
- traceback.print_exc()
336
- raise Exception(error_msg)
337
-
338
-
339
- def extract_quantities_from_pdf(file_path):
340
- """
341
- استخراج الكميات من ملف PDF
342
-
343
- المعلمات:
344
- file_path: مسار ملف PDF
345
-
346
- الإرجاع:
347
- DataFrame يحتوي على البنود والكميات المستخرجة
348
- """
349
- try:
350
- # استخراج النص والجداول
351
- text = extract_text_from_pdf(file_path)
352
- tables = extract_tables_from_pdf(file_path)
353
-
354
- quantities = []
355
-
356
- # استخراج الكميات من الجداول
357
- for table in tables:
358
- # البحث عن أعمدة تحتوي على "الكمية" أو "الوحدة" أو "البند"
359
- quantity_cols = [col for col in table.columns if any(term in col.lower() for term in ['كمية', 'عدد', 'الكمية'])]
360
- unit_cols = [col for col in table.columns if any(term in col.lower() for term in ['وحدة', 'الوحدة'])]
361
- item_cols = [col for col in table.columns if any(term in col.lower() for term in ['بند', 'وصف', 'البند', 'العمل'])]
362
-
363
- if quantity_cols and (unit_cols or item_cols):
364
- quantity_col = quantity_cols[0]
365
- unit_col = unit_cols[0] if unit_cols else None
366
- item_col = item_cols[0] if item_cols else None
367
-
368
- # استخراج الكميات
369
- for _, row in table.iterrows():
370
- if pd.notna(row[quantity_col]) and (item_col is None or pd.notna(row[item_col])):
371
- quantity_value = extract_numbers_from_text(row[quantity_col])
372
- quantity = quantity_value[0] if quantity_value else None
373
-
374
- quantities.append({
375
- 'البند': row[item_col] if item_col else "غير محدد",
376
- 'الوحدة': row[unit_col] if unit_col else "غير محدد",
377
- 'الكمية': quantity
378
- })
379
-
380
- # استخراج الكميات من النص
381
- lines = text.split('\n')
382
- for line in lines:
383
- # البحث عن الخطوط التي تحتوي على أرقام ووحدات قياس
384
- if re.search(r'\d+(?:,\d+)*(?:\.\d+)?', line) and any(unit in line for unit in ['م2', 'م3', 'متر', 'طن', 'كجم', 'عدد']):
385
- numbers = extract_numbers_from_text(line)
386
- if numbers:
387
- # استخراج وحدة القياس
388
- unit_match = re.search(r'\b(م2|م3|متر مربع|متر مكعب|م\.ط|طن|كجم|عدد|قطعة)\b', line)
389
- unit = unit_match.group(1) if unit_match else "غير محدد"
390
-
391
- quantities.append({
392
- 'البند': line,
393
- 'الوحدة': unit,
394
- 'الكمية': numbers[0]
395
- })
396
-
397
- # إنشاء DataFrame
398
- if quantities:
399
- quantities_df = pd.DataFrame(quantities)
400
- return quantities_df
401
- else:
402
- return pd.DataFrame(columns=['البند', 'الوحدة', 'الكمية'])
403
-
404
- except Exception as e:
405
- error_msg = f"خطأ في استخراج الكميات من ملف PDF: {str(e)}"
406
- print(error_msg)
407
- traceback.print_exc()
408
- raise Exception(error_msg)
409
-
410
-
411
- def merge_pdfs(input_paths, output_path):
412
- """
413
- دمج ملفات PDF متعددة في ملف واحد
414
-
415
- المعلمات:
416
- input_paths: قائمة من مسارات ملفات PDF المراد دمجها
417
- output_path: مسار ملف PDF الناتج
418
-
419
- الإرجاع:
420
- True في حالة النجاح
421
- """
422
- try:
423
- # التحقق من وجود الملفات
424
- for file_path in input_paths:
425
- if not os.path.exists(file_path):
426
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
427
-
428
- # إنشاء مجلد الإخراج إذا لم يكن موجودًا
429
- output_dir = os.path.dirname(output_path)
430
- create_directory_if_not_exists(output_dir)
431
-
432
- # دمج ملفات PDF
433
- merger = PyPDF2.PdfMerger()
434
-
435
- for file_path in input_paths:
436
- merger.append(file_path)
437
-
438
- merger.write(output_path)
439
- merger.close()
440
-
441
- return True
442
-
443
- except Exception as e:
444
- error_msg = f"خطأ في دمج ملفات PDF: {str(e)}"
445
- print(error_msg)
446
- traceback.print_exc()
447
- raise Exception(error_msg)
448
-
449
-
450
- def split_pdf(input_path, output_dir, prefix='page'):
451
- """
452
- تقسيم ملف PDF إلى ملفات منفصلة لكل صفحة
453
-
454
- المعلمات:
455
- input_path: مسار ملف PDF المراد تقسيمه
456
- output_dir: دليل الإخراج
457
- prefix: بادئة أسماء ملفات الإخراج
458
-
459
- الإرجاع:
460
- قائمة من مسارات ملفات PDF الناتجة
461
- """
462
- try:
463
- # التحقق من وجود الملف
464
- if not os.path.exists(input_path):
465
- raise FileNotFoundError(f"الملف غير موجود: {input_path}")
466
-
467
- # إنشاء دليل الإخراج
468
- create_directory_if_not_exists(output_dir)
469
-
470
- # قراءة ملف PDF
471
- with open(input_path, 'rb') as file:
472
- reader = PyPDF2.PdfReader(file)
473
- output_files = []
474
-
475
- # تقسيم كل صفحة إلى ملف منفصل
476
- for page_idx in range(len(reader.pages)):
477
- writer = PyPDF2.PdfWriter()
478
- writer.add_page(reader.pages[page_idx])
479
-
480
- output_filename = f"{prefix}_{page_idx+1}.pdf"
481
- output_path = os.path.join(output_dir, output_filename)
482
-
483
- with open(output_path, 'wb') as output_file:
484
- writer.write(output_file)
485
-
486
- output_files.append(output_path)
487
-
488
- return output_files
489
-
490
- except Exception as e:
491
- error_msg = f"خطأ في تقسيم ملف PDF: {str(e)}"
492
- print(error_msg)
493
- traceback.print_exc()
494
- raise Exception(error_msg)
495
-
496
-
497
- def export_pricing_to_pdf(data, output_path, title="تحليل الأسعار", description=""):
498
- """
499
- تصدير بيانات التسعير إلى ملف PDF
500
-
501
- المعلمات:
502
- data: DataFrame يحتوي على بيانات التسعير
503
- output_path: مسار ملف PDF الناتج
504
- title: عنوان التقرير
505
- description: وصف إضافي للتقرير
506
-
507
- الإرجاع:
508
- مسار ملف PDF الناتج
509
- """
510
- try:
511
- # التحقق من أن البيانات ليست فارغة
512
- if data is None or len(data) == 0:
513
- raise ValueError("البيانات فارغة")
514
-
515
- # إنشاء دليل الإخراج إذا لم يكن موجودًا
516
- output_dir = os.path.dirname(output_path)
517
- create_directory_if_not_exists(output_dir)
518
-
519
- # إعداد أنماط النصوص
520
- styles = getSampleStyleSheet()
521
-
522
- # محاولة استخدام الخطوط العربية إذا كانت متوفرة
523
- try:
524
- # تسجيل الخطوط العربية إذا لم تكن مسجلة
525
- if 'Arabic' not in pdfmetrics.getRegisteredFontNames():
526
- pdfmetrics.registerFont(TTFont('Arabic', 'utils/fonts/Amiri-Regular.ttf'))
527
-
528
- if 'ArabicBold' not in pdfmetrics.getRegisteredFontNames():
529
- pdfmetrics.registerFont(TTFont('ArabicBold', 'utils/fonts/Amiri-Bold.ttf'))
530
-
531
- # إنشاء أنماط للنصوص العربية
532
- title_style = ParagraphStyle(
533
- name='ArabicTitleStyle',
534
- fontName='ArabicBold',
535
- fontSize=16,
536
- alignment=1, # وسط
537
- leading=18
538
- )
539
-
540
- normal_style = ParagraphStyle(
541
- name='ArabicStyle',
542
- fontName='Arabic',
543
- fontSize=12,
544
- alignment=1, # وسط
545
- leading=14
546
- )
547
-
548
- except:
549
- # استخدام الخطوط الافتراضية إذا تعذر استخدام الخطوط العربية
550
- title_style = styles['Title']
551
- normal_style = styles['Normal']
552
-
553
- # إنشاء وثيقة PDF
554
- doc = SimpleDocTemplate(
555
- output_path,
556
- pagesize=landscape(A4),
557
- rightMargin=30,
558
- leftMargin=30,
559
- topMargin=30,
560
- bottomMargin=30
561
- )
562
-
563
- # قائمة العناصر في الوثيقة
564
- elements = []
565
-
566
- # إضافة العنوان
567
- elements.append(Paragraph(title, title_style))
568
- elements.append(Spacer(1, 20))
569
-
570
- # إضافة الوصف إذا كان موجودًا
571
- if description:
572
- elements.append(Paragraph(description, normal_style))
573
- elements.append(Spacer(1, 20))
574
-
575
- # تحويل DataFrame إلى قائمة لاستخدامها في الجدول
576
- data_list = [data.columns.tolist()] + data.values.tolist()
577
-
578
- # إنشاء الجدول
579
- table = Table(data_list, repeatRows=1)
580
-
581
- # إضافة تنسيق للجدول
582
- table_style = TableStyle([
583
- ('BACKGROUND', (0, 0), (-1, 0), colors.lightblue),
584
- ('TEXTCOLOR', (0, 0), (-1, 0), colors.black),
585
- ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
586
- ('FONTNAME', (0, 0), (-1, 0), 'ArabicBold' if 'ArabicBold' in pdfmetrics.getRegisteredFontNames() else 'Helvetica-Bold'),
587
- ('FONTSIZE', (0, 0), (-1, 0), 12),
588
- ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
589
- ('BACKGROUND', (0, 1), (-1, -1), colors.white),
590
- ('GRID', (0, 0), (-1, -1), 1, colors.black),
591
- ('FONTNAME', (0, 1), (-1, -1), 'Arabic' if 'Arabic' in pdfmetrics.getRegisteredFontNames() else 'Helvetica'),
592
- ('FONTSIZE', (0, 1), (-1, -1), 10),
593
- ])
594
-
595
- # إضافة تلوين للصفوف المتناوبة
596
- for i in range(1, len(data_list)):
597
- if i % 2 == 0:
598
- table_style.add('BACKGROUND', (0, i), (-1, i), colors.lightgrey)
599
-
600
- table.setStyle(table_style)
601
- elements.append(table)
602
-
603
- # إضافة ملخص إحصائي إذا كانت البيانات تحتوي على أعمدة رقمية
604
- numeric_columns = data.select_dtypes(include=['number']).columns
605
- if len(numeric_columns) > 0:
606
- elements.append(Spacer(1, 30))
607
- elements.append(Paragraph("ملخص إحصائي", title_style))
608
- elements.append(Spacer(1, 10))
609
-
610
- # إنشاء ملخص إحصائي
611
- summary = data[numeric_columns].describe().reset_index()
612
- summary_list = [['الإحصاءات'] + list(numeric_columns)] + summary.values.tolist()
613
-
614
- # إنشاء جدول الملخص
615
- summary_table = Table(summary_list, repeatRows=1)
616
- summary_table.setStyle(TableStyle([
617
- ('BACKGROUND', (0, 0), (-1, 0), colors.lightblue),
618
- ('TEXTCOLOR', (0, 0), (-1, 0), colors.black),
619
- ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
620
- ('FONTNAME', (0, 0), (-1, 0), 'ArabicBold' if 'ArabicBold' in pdfmetrics.getRegisteredFontNames() else 'Helvetica-Bold'),
621
- ('FONTSIZE', (0, 0), (-1, 0), 12),
622
- ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
623
- ('BACKGROUND', (0, 1), (-1, -1), colors.white),
624
- ('GRID', (0, 0), (-1, -1), 1, colors.black),
625
- ('FONTNAME', (0, 1), (-1, -1), 'Arabic' if 'Arabic' in pdfmetrics.getRegisteredFontNames() else 'Helvetica'),
626
- ('FONTSIZE', (0, 1), (-1, -1), 10),
627
- ]))
628
- elements.append(summary_table)
629
-
630
- # إنشاء مخططات للأعمدة الرقمية الرئيسية
631
- if len(numeric_columns) > 0 and len(data) > 1:
632
- for col in numeric_columns[:2]: # أخذ أول عمودين رقميين فقط
633
- plt.figure(figsize=(8, 4))
634
- plt.barh(range(len(data)), data[col])
635
- plt.yticks(range(len(data)), data.iloc[:, 0] if len(data.columns) > 0 else range(len(data)))
636
- plt.xlabel(col)
637
- plt.title(f"مخطط {col}")
638
- plt.tight_layout()
639
-
640
- # حفظ المخطط كصورة في الذاكرة
641
- img_data = io.BytesIO()
642
- plt.savefig(img_data, format='png')
643
- img_data.seek(0)
644
- plt.close()
645
-
646
- # إضافة المخطط إلى الوثيقة
647
- elements.append(Spacer(1, 20))
648
- img = ReportLabImage(img_data, width=500, height=250)
649
- elements.append(img)
650
-
651
- # بناء الوثيقة
652
- doc.build(elements)
653
-
654
- return output_path
655
-
656
- except Exception as e:
657
- error_msg = f"خطأ في تصدير بيانات التسعير إلى PDF: {str(e)}"
658
- print(error_msg)
659
- traceback.print_exc()
660
- raise Exception(error_msg)
661
-
662
-
663
- def export_pricing_with_analysis_to_pdf(items_data, analysis_data, output_path, title="تحليل الأسعار مع تحليل المكونات", project_info=None):
664
- """
665
- تصدير بيانات التسعير مع تحليل المكونات إلى ملف PDF
666
-
667
- المعلمات:
668
- items_data: DataFrame يحتوي على بيانات البنود
669
- analysis_data: قاموس يحتوي على تحليل أسعار البنود {item_id: DataFrame}
670
- output_path: مسار ملف PDF الناتج
671
- title: عنوان التقرير
672
- project_info: معلومات المشروع (قاموس)
673
-
674
- الإرجاع:
675
- مسار ملف PDF الناتج
676
- """
677
- try:
678
- # التحقق من أن البيانات ليست فارغة
679
- if items_data is None or len(items_data) == 0:
680
- raise ValueError("بيانات البنود فارغة")
681
-
682
- # إنشاء دليل الإخراج إذا لم يكن موجودًا
683
- output_dir = os.path.dirname(output_path)
684
- create_directory_if_not_exists(output_dir)
685
-
686
- # إعداد أنماط النصوص
687
- styles = getSampleStyleSheet()
688
-
689
- # محاولة استخدام الخطوط العربية إذا كانت متوفرة
690
- try:
691
- # تسجيل الخطوط العربية إذا لم تكن مسجلة
692
- if 'Arabic' not in pdfmetrics.getRegisteredFontNames():
693
- pdfmetrics.registerFont(TTFont('Arabic', 'utils/fonts/Amiri-Regular.ttf'))
694
-
695
- if 'ArabicBold' not in pdfmetrics.getRegisteredFontNames():
696
- pdfmetrics.registerFont(TTFont('ArabicBold', 'utils/fonts/Amiri-Bold.ttf'))
697
-
698
- # إنشاء أنماط للنصوص العربية
699
- title_style = ParagraphStyle(
700
- name='ArabicTitleStyle',
701
- fontName='ArabicBold',
702
- fontSize=16,
703
- alignment=1, # وسط
704
- leading=18
705
- )
706
-
707
- normal_style = ParagraphStyle(
708
- name='ArabicStyle',
709
- fontName='Arabic',
710
- fontSize=12,
711
- alignment=1, # وسط
712
- leading=14
713
- )
714
-
715
- subtitle_style = ParagraphStyle(
716
- name='ArabicSubtitleStyle',
717
- fontName='ArabicBold',
718
- fontSize=14,
719
- alignment=1, # وسط
720
- leading=16
721
- )
722
-
723
- except:
724
- # استخدام الخطوط الافتراضية إذا تعذر استخدام الخطوط العربية
725
- title_style = styles['Title']
726
- normal_style = styles['Normal']
727
- subtitle_style = styles['Heading2']
728
-
729
- # إنشاء وثيقة PDF
730
- doc = SimpleDocTemplate(
731
- output_path,
732
- pagesize=landscape(A4),
733
- rightMargin=30,
734
- leftMargin=30,
735
- topMargin=30,
736
- bottomMargin=30
737
- )
738
-
739
- # قائمة العناصر في الوثيقة
740
- elements = []
741
-
742
- # إضافة العنوان
743
- elements.append(Paragraph(title, title_style))
744
- elements.append(Spacer(1, 20))
745
-
746
- # إضافة معلومات المشروع إذا كانت موجودة
747
- if project_info:
748
- project_info_text = f"اسم المشروع: {project_info.get('اسم_المشروع', '')} | "
749
- project_info_text += f"وصف المشروع: {project_info.get('وصف_المشروع', '')}"
750
- elements.append(Paragraph(project_info_text, normal_style))
751
- elements.append(Spacer(1, 20))
752
-
753
- # إضافة جدول البنود
754
- elements.append(Paragraph("جدول البنود", subtitle_style))
755
- elements.append(Spacer(1, 10))
756
-
757
- # تحويل DataFrame إلى قائمة لاستخدامها في الجدول
758
- items_list = [items_data.columns.tolist()] + items_data.values.tolist()
759
-
760
- # إنشاء الجدول
761
- items_table = Table(items_list, repeatRows=1)
762
-
763
- # إضافة تنسيق للجدول
764
- items_table_style = TableStyle([
765
- ('BACKGROUND', (0, 0), (-1, 0), colors.lightblue),
766
- ('TEXTCOLOR', (0, 0), (-1, 0), colors.black),
767
- ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
768
- ('FONTNAME', (0, 0), (-1, 0), 'ArabicBold' if 'ArabicBold' in pdfmetrics.getRegisteredFontNames() else 'Helvetica-Bold'),
769
- ('FONTSIZE', (0, 0), (-1, 0), 12),
770
- ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
771
- ('BACKGROUND', (0, 1), (-1, -1), colors.white),
772
- ('GRID', (0, 0), (-1, -1), 1, colors.black),
773
- ('FONTNAME', (0, 1), (-1, -1), 'Arabic' if 'Arabic' in pdfmetrics.getRegisteredFontNames() else 'Helvetica'),
774
- ('FONTSIZE', (0, 1), (-1, -1), 10),
775
- ])
776
-
777
- # إضافة تلوين للصفوف المتناوبة
778
- for i in range(1, len(items_list)):
779
- if i % 2 == 0:
780
- items_table_style.add('BACKGROUND', (0, i), (-1, i), colors.lightgrey)
781
-
782
- items_table.setStyle(items_table_style)
783
- elements.append(items_table)
784
-
785
- # إضافة تحليل الأسعار لكل بند
786
- if analysis_data:
787
- elements.append(Spacer(1, 30))
788
- elements.append(Paragraph("تحليل مكونات الأسعار", subtitle_style))
789
-
790
- for item_id, analysis_df in analysis_data.items():
791
- # الحصول على معلومات البند
792
- item_info = items_data[items_data['رقم البند'] == item_id].iloc[0] if 'رقم البند' in items_data.columns and len(items_data[items_data['رقم البند'] == item_id]) > 0 else None
793
-
794
- if item_info is not None:
795
- item_title = f"تحليل مكونات البند: {item_id} - {item_info.get('وصف البند', '')}"
796
- else:
797
- item_title = f"تحليل مكونات البند: {item_id}"
798
-
799
- elements.append(Spacer(1, 20))
800
- elements.append(Paragraph(item_title, subtitle_style))
801
- elements.append(Spacer(1, 10))
802
-
803
- # تحويل DataFrame إلى قائمة لاستخدامها في الجدول
804
- analysis_list = [analysis_df.columns.tolist()] + analysis_df.values.tolist()
805
-
806
- # إنشاء الجدول
807
- analysis_table = Table(analysis_list, repeatRows=1)
808
-
809
- # إضافة تنسيق للجدول
810
- analysis_table_style = TableStyle([
811
- ('BACKGROUND', (0, 0), (-1, 0), colors.lightblue),
812
- ('TEXTCOLOR', (0, 0), (-1, 0), colors.black),
813
- ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
814
- ('FONTNAME', (0, 0), (-1, 0), 'ArabicBold' if 'ArabicBold' in pdfmetrics.getRegisteredFontNames() else 'Helvetica-Bold'),
815
- ('FONTSIZE', (0, 0), (-1, 0), 12),
816
- ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
817
- ('BACKGROUND', (0, 1), (-1, -1), colors.white),
818
- ('GRID', (0, 0), (-1, -1), 1, colors.black),
819
- ('FONTNAME', (0, 1), (-1, -1), 'Arabic' if 'Arabic' in pdfmetrics.getRegisteredFontNames() else 'Helvetica'),
820
- ('FONTSIZE', (0, 1), (-1, -1), 10),
821
- ])
822
-
823
- # إضافة تلوين للصفوف المتناوبة
824
- for i in range(1, len(analysis_list)):
825
- if i % 2 == 0:
826
- analysis_table_style.add('BACKGROUND', (0, i), (-1, i), colors.lightgrey)
827
-
828
- analysis_table.setStyle(analysis_table_style)
829
- elements.append(analysis_table)
830
-
831
- # حساب وعرض إجمالي تحليل السعر
832
- if 'الإجمالي' in analysis_df.columns and len(analysis_df) > 0:
833
- total_analysis_price = analysis_df['الإجمالي'].sum()
834
- total_text = f"إجمالي تكلفة البند من التحليل: {total_analysis_price:,.2f} ريال"
835
- elements.append(Spacer(1, 10))
836
- elements.append(Paragraph(total_text, normal_style))
837
-
838
- # بناء الوثيقة
839
- doc.build(elements)
840
-
841
- return output_path
842
-
843
- except Exception as e:
844
- error_msg = f"خطأ في تصدير تحليل الأسعار إلى PDF: {str(e)}"
845
- print(error_msg)
846
- traceback.print_exc()
847
- raise Exception(error_msg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/pdf_handler.py.bak DELETED
@@ -1,476 +0,0 @@
1
- """
2
- معالج ملفات PDF
3
- """
4
-
5
- import os
6
- import io
7
- import re
8
- import PyPDF2
9
- import fitz # PyMuPDF
10
- import pdfplumber
11
- import numpy as np
12
- from PIL import Image
13
- import pytesseract
14
- import pandas as pd
15
- import traceback
16
-
17
- from utils.helpers import create_directory_if_not_exists, extract_numbers_from_text
18
-
19
-
20
- def extract_text_from_pdf(file_path, method='pymupdf'):
21
- """
22
- استخراج النص من ملف PDF
23
-
24
- المعلمات:
25
- file_path: مسار ملف PDF
26
- method: طريقة الاستخراج ('pymupdf', 'pypdf2', 'pdfplumber')
27
-
28
- الإرجاع:
29
- نص مستخرج من ملف PDF
30
- """
31
- try:
32
- # التحقق من وجود الملف
33
- if not os.path.exists(file_path):
34
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
35
-
36
- # استخراج النص حسب الطريقة المطلوبة
37
- if method.lower() == 'pymupdf':
38
- return _extract_text_with_pymupdf(file_path)
39
- elif method.lower() == 'pypdf2':
40
- return _extract_text_with_pypdf2(file_path)
41
- elif method.lower() == 'pdfplumber':
42
- return _extract_text_with_pdfplumber(file_path)
43
- else:
44
- # استخدام PyMuPDF كطريقة افتراضية
45
- return _extract_text_with_pymupdf(file_path)
46
-
47
- except Exception as e:
48
- error_msg = f"خطأ في استخراج النص من ملف PDF: {str(e)}"
49
- print(error_msg)
50
- traceback.print_exc()
51
- raise Exception(error_msg)
52
-
53
-
54
- def _extract_text_with_pymupdf(file_path):
55
- """استخراج النص باستخدام PyMuPDF"""
56
- document = fitz.open(file_path)
57
- text = ""
58
-
59
- for page_number in range(len(document)):
60
- page = document.load_page(page_number)
61
- text += page.get_text("text") + "\n\n"
62
-
63
- document.close()
64
- return text
65
-
66
-
67
- def _extract_text_with_pypdf2(file_path):
68
- """استخراج النص باستخدام PyPDF2"""
69
- with open(file_path, 'rb') as file:
70
- reader = PyPDF2.PdfReader(file)
71
- text = ""
72
-
73
- for page_number in range(len(reader.pages)):
74
- page = reader.pages[page_number]
75
- text += page.extract_text() + "\n\n"
76
-
77
- return text
78
-
79
-
80
- def _extract_text_with_pdfplumber(file_path):
81
- """استخراج النص باستخدام pdfplumber"""
82
- with pdfplumber.open(file_path) as pdf:
83
- text = ""
84
-
85
- for page in pdf.pages:
86
- text += page.extract_text() + "\n\n"
87
-
88
- return text
89
-
90
-
91
- def extract_tables_from_pdf(file_path, page_numbers=None):
92
- """
93
- استخراج الجداول من ملف PDF
94
-
95
- المعلمات:
96
- file_path: مسار ملف PDF
97
- page_numbers: قائمة بأرقام الصفحات للاستخراج منها (افتراضي: None لجميع الصفحات)
98
-
99
- الإرجاع:
100
- قائمة من DataFrames تمثل الجداول المستخرجة
101
- """
102
- try:
103
- # التحقق من وجود الملف
104
- if not os.path.exists(file_path):
105
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
106
-
107
- # استخراج الجداول باستخدام pdfplumber
108
- tables = []
109
-
110
- with pdfplumber.open(file_path) as pdf:
111
- # تحديد الصفحات المراد استخراج الجداول منها
112
- if page_numbers is None:
113
- pages_to_extract = range(len(pdf.pages))
114
- else:
115
- pages_to_extract = [p-1 for p in page_numbers if 1 <= p <= len(pdf.pages)]
116
-
117
- # استخراج الجداول من كل صفحة
118
- for page_idx in pages_to_extract:
119
- page = pdf.pages[page_idx]
120
- page_tables = page.extract_tables()
121
-
122
- if page_tables:
123
- for table in page_tables:
124
- if table: # التحقق من أن الجدول ليس فارغًا
125
- # تحويل الجدول إلى DataFrame
126
- df = pd.DataFrame(table[1:], columns=table[0])
127
-
128
- # تنظيف البيانات
129
- df = df.applymap(lambda x: x.strip() if isinstance(x, str) else x)
130
-
131
- # إضافة إلى قائمة الجداول
132
- tables.append(df)
133
-
134
- return tables
135
-
136
- except Exception as e:
137
- error_msg = f"خطأ في استخراج الجداول من ملف PDF: {str(e)}"
138
- print(error_msg)
139
- traceback.print_exc()
140
- raise Exception(error_msg)
141
-
142
-
143
- def extract_images_from_pdf(file_path, output_dir=None, prefix='image'):
144
- """
145
- استخراج الصور من ملف PDF
146
-
147
- المعلمات:
148
- file_path: مسار ملف PDF
149
- output_dir: دليل الإخراج (افتراضي: None للإرجاع كقائمة من الصور)
150
- prefix: بادئة أسماء ملفات الصور
151
-
152
- الإرجاع:
153
- قائمة من مسارات الصور المستخرجة إذا تم تحديد دليل الإخراج، وإلا قائمة من الصور
154
- """
155
- try:
156
- # التحقق من وجود الملف
157
- if not os.path.exists(file_path):
158
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
159
-
160
- # إنشاء دليل الإخراج إذا تم تحديده
161
- if output_dir:
162
- create_directory_if_not_exists(output_dir)
163
-
164
- # استخراج الصور باستخدام PyMuPDF
165
- document = fitz.open(file_path)
166
- images = []
167
- image_paths = []
168
-
169
- for page_idx in range(len(document)):
170
- page = document.load_page(page_idx)
171
-
172
- # استخراج الصور من الصفحة
173
- image_list = page.get_images(full=True)
174
-
175
- for img_idx, img_info in enumerate(image_list):
176
- xref = img_info[0]
177
- base_image = document.extract_image(xref)
178
- image_bytes = base_image["image"]
179
-
180
- # إنشاء كائن الصورة
181
- image = Image.open(io.BytesIO(image_bytes))
182
-
183
- if output_dir:
184
- # حفظ الصورة في الدليل المحدد
185
- image_filename = f"{prefix}_{page_idx+1}_{img_idx+1}.{base_image['ext']}"
186
- image_path = os.path.join(output_dir, image_filename)
187
- image.save(image_path)
188
- image_paths.append(image_path)
189
- else:
190
- # إضافة الصورة إلى القائمة
191
- images.append(image)
192
-
193
- document.close()
194
-
195
- return image_paths if output_dir else images
196
-
197
- except Exception as e:
198
- error_msg = f"خطأ في استخراج الصور من ملف PDF: {str(e)}"
199
- print(error_msg)
200
- traceback.print_exc()
201
- raise Exception(error_msg)
202
-
203
-
204
- def extract_text_from_image(image, lang='ara+eng'):
205
- """
206
- استخراج النص من صورة باستخدام OCR
207
-
208
- المعلمات:
209
- image: كائن الصورة أو مسار الصورة
210
- lang: لغة النص (افتراضي: 'ara+eng' للعربية والإنجليزية)
211
-
212
- الإرجاع:
213
- النص المستخرج من الصورة
214
- """
215
- try:
216
- # إذا كان مسار صورة، قم بفتحها
217
- if isinstance(image, str):
218
- image = Image.open(image)
219
-
220
- # استخراج النص باستخدام pytesseract
221
- text = pytesseract.image_to_string(image, lang=lang)
222
-
223
- return text
224
-
225
- except Exception as e:
226
- error_msg = f"خطأ في استخراج النص من الصورة: {str(e)}"
227
- print(error_msg)
228
- traceback.print_exc()
229
- return ""
230
-
231
-
232
- def ocr_pdf(file_path, lang='ara+eng'):
233
- """
234
- تنفيذ OCR على ملف PDF
235
-
236
- المعلمات:
237
- file_path: مسار ملف PDF
238
- lang: لغة النص (افتراضي: 'ara+eng' للعربية والإنجليزية)
239
-
240
- الإرجاع:
241
- النص المستخرج من ملف PDF
242
- """
243
- try:
244
- # التحقق من وجود الملف
245
- if not os.path.exists(file_path):
246
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
247
-
248
- # فتح ملف PDF
249
- document = fitz.open(file_path)
250
- text = ""
251
-
252
- for page_idx in range(len(document)):
253
- page = document.load_page(page_idx)
254
-
255
- # تحويل الصفحة إلى صورة
256
- pix = page.get_pixmap(matrix=fitz.Matrix(300/72, 300/72))
257
- img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
258
-
259
- # استخراج النص من الصورة
260
- page_text = extract_text_from_image(img, lang=lang)
261
- text += page_text + "\n\n"
262
-
263
- document.close()
264
-
265
- return text
266
-
267
- except Exception as e:
268
- error_msg = f"خطأ في تنفيذ OCR على ملف PDF: {str(e)}"
269
- print(error_msg)
270
- traceback.print_exc()
271
- raise Exception(error_msg)
272
-
273
-
274
- def search_in_pdf(file_path, search_text):
275
- """
276
- البحث عن نص في ملف PDF
277
-
278
- المعلمات:
279
- file_path: مسار ملف PDF
280
- search_text: النص المراد البحث عنه
281
-
282
- الإرجاع:
283
- قائمة من النتائج {page_number, text_snippet, matched_text}
284
- """
285
- try:
286
- # التحقق من وجود الملف
287
- if not os.path.exists(file_path):
288
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
289
-
290
- # استخراج النص من ملف PDF
291
- document = fitz.open(file_path)
292
- results = []
293
-
294
- for page_idx in range(len(document)):
295
- page = document.load_page(page_idx)
296
- page_text = page.get_text("text")
297
-
298
- # البحث عن النص
299
- if search_text.lower() in page_text.lower():
300
- # استخراج المقتطفات التي تحتوي على النص المطلوب
301
- lines = page_text.split('\n')
302
- for line in lines:
303
- if search_text.lower() in line.lower():
304
- results.append({
305
- 'page_number': page_idx + 1,
306
- 'text_snippet': line,
307
- 'matched_text': search_text
308
- })
309
-
310
- document.close()
311
-
312
- return results
313
-
314
- except Exception as e:
315
- error_msg = f"خطأ في البحث في ملف PDF: {str(e)}"
316
- print(error_msg)
317
- traceback.print_exc()
318
- raise Exception(error_msg)
319
-
320
-
321
- def extract_quantities_from_pdf(file_path):
322
- """
323
- استخراج الكميات من ملف PDF
324
-
325
- المعلمات:
326
- file_path: مسار ملف PDF
327
-
328
- الإرجاع:
329
- DataFrame يحتوي على البنود والكميات المستخرجة
330
- """
331
- try:
332
- # استخراج النص والجداول
333
- text = extract_text_from_pdf(file_path)
334
- tables = extract_tables_from_pdf(file_path)
335
-
336
- quantities = []
337
-
338
- # استخراج الكميات من الجداول
339
- for table in tables:
340
- # البحث عن أعمدة تحتوي على "الكمية" أو "الوحدة" أو "البند"
341
- quantity_cols = [col for col in table.columns if any(term in col.lower() for term in ['كمية', 'عدد', 'الكمية'])]
342
- unit_cols = [col for col in table.columns if any(term in col.lower() for term in ['وحدة', 'الوحدة'])]
343
- item_cols = [col for col in table.columns if any(term in col.lower() for term in ['بند', 'وصف', 'البند', 'العمل'])]
344
-
345
- if quantity_cols and (unit_cols or item_cols):
346
- quantity_col = quantity_cols[0]
347
- unit_col = unit_cols[0] if unit_cols else None
348
- item_col = item_cols[0] if item_cols else None
349
-
350
- # استخراج الكميات
351
- for _, row in table.iterrows():
352
- if pd.notna(row[quantity_col]) and (item_col is None or pd.notna(row[item_col])):
353
- quantity_value = extract_numbers_from_text(row[quantity_col])
354
- quantity = quantity_value[0] if quantity_value else None
355
-
356
- quantities.append({
357
- 'البند': row[item_col] if item_col else "غير محدد",
358
- 'الوحدة': row[unit_col] if unit_col else "غير محدد",
359
- 'الكمية': quantity
360
- })
361
-
362
- # استخراج الكميات من النص
363
- lines = text.split('\n')
364
- for line in lines:
365
- # البحث عن الخطوط التي تحتوي على أرقام ووحدات قياس
366
- if re.search(r'\d+(?:,\d+)*(?:\.\d+)?', line) and any(unit in line for unit in ['م2', 'م3', 'متر', 'طن', 'كجم', 'عدد']):
367
- numbers = extract_numbers_from_text(line)
368
- if numbers:
369
- # استخراج وحدة القياس
370
- unit_match = re.search(r'\b(م2|م3|متر مربع|متر مكعب|م\.ط|طن|كجم|عدد|قطعة)\b', line)
371
- unit = unit_match.group(1) if unit_match else "غير محدد"
372
-
373
- quantities.append({
374
- 'البند': line,
375
- 'الوحدة': unit,
376
- 'الكمية': numbers[0]
377
- })
378
-
379
- # إنشاء DataFrame
380
- if quantities:
381
- quantities_df = pd.DataFrame(quantities)
382
- return quantities_df
383
- else:
384
- return pd.DataFrame(columns=['البند', 'الوحدة', 'الكمية'])
385
-
386
- except Exception as e:
387
- error_msg = f"خطأ في استخراج الكميات من ملف PDF: {str(e)}"
388
- print(error_msg)
389
- traceback.print_exc()
390
- raise Exception(error_msg)
391
-
392
-
393
- def merge_pdfs(input_paths, output_path):
394
- """
395
- دمج ملفات PDF متعددة في ملف واحد
396
-
397
- المعلمات:
398
- input_paths: قائمة من مسارات ملفات PDF المراد دمجها
399
- output_path: مسار ملف PDF الناتج
400
-
401
- الإرجاع:
402
- True في حالة النجاح
403
- """
404
- try:
405
- # التحقق من وجود الملفات
406
- for file_path in input_paths:
407
- if not os.path.exists(file_path):
408
- raise FileNotFoundError(f"الملف غير موجود: {file_path}")
409
-
410
- # إنشاء مجلد الإخراج إذا لم يكن موجودًا
411
- output_dir = os.path.dirname(output_path)
412
- create_directory_if_not_exists(output_dir)
413
-
414
- # دمج ملفات PDF
415
- merger = PyPDF2.PdfMerger()
416
-
417
- for file_path in input_paths:
418
- merger.append(file_path)
419
-
420
- merger.write(output_path)
421
- merger.close()
422
-
423
- return True
424
-
425
- except Exception as e:
426
- error_msg = f"خطأ في دمج ملفات PDF: {str(e)}"
427
- print(error_msg)
428
- traceback.print_exc()
429
- raise Exception(error_msg)
430
-
431
-
432
- def split_pdf(input_path, output_dir, prefix='page'):
433
- """
434
- تقسيم ملف PDF إلى ملفات منفصلة لكل صفحة
435
-
436
- المعلمات:
437
- input_path: مسار ملف PDF المراد تقسيمه
438
- output_dir: دليل الإخراج
439
- prefix: بادئة أسماء ملفات الإخراج
440
-
441
- الإرجاع:
442
- قائمة من مسارات ملفات PDF الناتجة
443
- """
444
- try:
445
- # التحقق من وجود الملف
446
- if not os.path.exists(input_path):
447
- raise FileNotFoundError(f"الملف غير موجود: {input_path}")
448
-
449
- # إنشاء دليل الإخراج
450
- create_directory_if_not_exists(output_dir)
451
-
452
- # قراءة ملف PDF
453
- with open(input_path, 'rb') as file:
454
- reader = PyPDF2.PdfReader(file)
455
- output_files = []
456
-
457
- # تقسيم كل صفحة إلى ملف منفصل
458
- for page_idx in range(len(reader.pages)):
459
- writer = PyPDF2.PdfWriter()
460
- writer.add_page(reader.pages[page_idx])
461
-
462
- output_filename = f"{prefix}_{page_idx+1}.pdf"
463
- output_path = os.path.join(output_dir, output_filename)
464
-
465
- with open(output_path, 'wb') as output_file:
466
- writer.write(output_file)
467
-
468
- output_files.append(output_path)
469
-
470
- return output_files
471
-
472
- except Exception as e:
473
- error_msg = f"خطأ في تقسيم ملف PDF: {str(e)}"
474
- print(error_msg)
475
- traceback.print_exc()
476
- raise Exception(error_msg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/session_state.py DELETED
@@ -1,247 +0,0 @@
1
- """
2
- وحدة إدارة حالة الجلسة
3
- """
4
-
5
- import streamlit as st
6
- from datetime import datetime
7
- import pandas as pd
8
- import config
9
-
10
-
11
- def initialize_session_state():
12
- """
13
- تهيئة متغيرات حالة الجلسة
14
- """
15
- # المتغيرات الرئيسية لحالة المستخدم
16
- if 'is_authenticated' not in st.session_state:
17
- st.session_state.is_authenticated = False
18
-
19
- if 'user_info' not in st.session_state:
20
- st.session_state.user_info = None
21
-
22
- # المتغيرات المتعلقة بالمناقصات والمشاريع
23
- if 'current_project' not in st.session_state:
24
- st.session_state.current_project = None
25
-
26
- if 'current_pricing' not in st.session_state:
27
- st.session_state.current_pricing = None
28
-
29
- if 'pricing_history' not in st.session_state:
30
- st.session_state.pricing_history = []
31
-
32
- # المتغيرات المتعلقة بالمحتوى المحلي
33
- if 'local_content_products' not in st.session_state:
34
- st.session_state.local_content_products = pd.DataFrame({
35
- 'المنتج': [],
36
- 'الكمية': [],
37
- 'سعر_الوحدة': [],
38
- 'التكلفة_الإجمالية': [],
39
- 'نسبة_المحتوى_المحلي': []
40
- })
41
-
42
- if 'local_content_services' not in st.session_state:
43
- st.session_state.local_content_services = pd.DataFrame({
44
- 'الخدمة': [],
45
- 'التكلفة': [],
46
- 'نسبة_المحتوى_المحلي': []
47
- })
48
-
49
- if 'local_content_labor' not in st.session_state:
50
- st.session_state.local_content_labor = pd.DataFrame({
51
- 'فئة_العمالة': [],
52
- 'العدد': [],
53
- 'الراتب_الشهري': [],
54
- 'المدة_بالأشهر': [],
55
- 'نسبة_المحتوى_المحلي': []
56
- })
57
-
58
- # المتغيرات المتعلقة بتحليل المستندات
59
- if 'current_document' not in st.session_state:
60
- st.session_state.current_document = None
61
-
62
- if 'analyzed_documents' not in st.session_state:
63
- st.session_state.analyzed_documents = []
64
-
65
- # المتغيرات المتعلقة بالتسعير
66
- if 'manual_items' not in st.session_state:
67
- st.session_state.manual_items = pd.DataFrame({
68
- 'رقم البند': [],
69
- 'وصف البند': [],
70
- 'الوحدة': [],
71
- 'الكمية': [],
72
- 'سعر الوحدة': [],
73
- 'الإجمالي': []
74
- })
75
-
76
- # المتغيرات المتعلقة بالمصادر
77
- if 'resources' not in st.session_state:
78
- st.session_state.resources = []
79
-
80
- # المتغيرات المتعلقة بالإعدادات
81
- if 'settings' not in st.session_state:
82
- st.session_state.settings = {
83
- 'ui_theme': config.UI_THEME,
84
- 'locale': config.LOCALE,
85
- 'enable_animations': config.ENABLE_ANIMATIONS
86
- }
87
-
88
-
89
- def save_current_pricing():
90
- """
91
- حفظ التسعير الحالي في سجل التسعير
92
-
93
- الإرجاع:
94
- True في حالة النجاح، False في حالة الفشل
95
- """
96
- try:
97
- if st.session_state.current_pricing:
98
- # إضافة معلومات إضافية
99
- pricing_entry = st.session_state.current_pricing.copy()
100
- pricing_entry['timestamp'] = datetime.now()
101
-
102
- # إضافة إلى سجل التسعير
103
- st.session_state.pricing_history.append(pricing_entry)
104
-
105
- return True
106
- return False
107
- except Exception as e:
108
- print(f"خطأ في حفظ التسعير الحالي: {str(e)}")
109
- return False
110
-
111
-
112
- def set_current_project(project_data):
113
- """
114
- تعيين المشروع الحالي
115
-
116
- المعلمات:
117
- project_data: بيانات المشروع
118
- """
119
- st.session_state.current_project = project_data
120
-
121
-
122
- def set_current_pricing(pricing_data):
123
- """
124
- تعيين التسعير الحالي
125
-
126
- المعلمات:
127
- pricing_data: بيانات التسعير
128
- """
129
- st.session_state.current_pricing = pricing_data
130
-
131
-
132
- def set_current_document(document_data):
133
- """
134
- تعيين المستند الحالي
135
-
136
- المعلمات:
137
- document_data: بيانات المستند
138
- """
139
- st.session_state.current_document = document_data
140
-
141
-
142
- def clear_session():
143
- """
144
- مسح بيانات الجلسة الحالية
145
- """
146
- # الاحتفاظ بحالة المصادقة والمستخدم
147
- is_authenticated = st.session_state.is_authenticated
148
- user_info = st.session_state.user_info
149
- settings = st.session_state.settings
150
-
151
- # م��ح المتغيرات
152
- st.session_state.clear()
153
-
154
- # إعادة تعيين حالة المصادقة والمستخدم
155
- st.session_state.is_authenticated = is_authenticated
156
- st.session_state.user_info = user_info
157
- st.session_state.settings = settings
158
-
159
- # إعادة تهيئة متغيرات الجلسة
160
- initialize_session_state()
161
-
162
-
163
- def export_session_state():
164
- """
165
- تصدير حالة الجلسة الحالية
166
-
167
- الإرجاع:
168
- قاموس يحتوي على حالة الجلسة
169
- """
170
- # إنشاء نسخة من حالة الجلسة
171
- session_data = {}
172
-
173
- # تخزين البيانات الرئيسية
174
- if st.session_state.current_project:
175
- session_data['current_project'] = st.session_state.current_project
176
-
177
- if st.session_state.current_pricing:
178
- session_data['current_pricing'] = st.session_state.current_pricing
179
-
180
- if st.session_state.pricing_history:
181
- session_data['pricing_history'] = st.session_state.pricing_history
182
-
183
- # تحويل DataFrames إلى قوائم من القواميس
184
- if 'local_content_products' in st.session_state and not st.session_state.local_content_products.empty:
185
- session_data['local_content_products'] = st.session_state.local_content_products.to_dict('records')
186
-
187
- if 'local_content_services' in st.session_state and not st.session_state.local_content_services.empty:
188
- session_data['local_content_services'] = st.session_state.local_content_services.to_dict('records')
189
-
190
- if 'local_content_labor' in st.session_state and not st.session_state.local_content_labor.empty:
191
- session_data['local_content_labor'] = st.session_state.local_content_labor.to_dict('records')
192
-
193
- # تخزين البيانات الأخرى
194
- if 'manual_items' in st.session_state and not st.session_state.manual_items.empty:
195
- session_data['manual_items'] = st.session_state.manual_items.to_dict('records')
196
-
197
- if st.session_state.resources:
198
- session_data['resources'] = st.session_state.resources
199
-
200
- # إضافة بيانات الوقت
201
- session_data['exported_at'] = datetime.now().isoformat()
202
-
203
- return session_data
204
-
205
-
206
- def import_session_state(session_data):
207
- """
208
- استيراد حالة الجلسة
209
-
210
- المعلمات:
211
- session_data: قاموس يحتوي على حالة الجلسة
212
-
213
- الإرجاع:
214
- True في حالة النجاح، False في حالة الفشل
215
- """
216
- try:
217
- # استيراد البيانات الرئيسية
218
- if 'current_project' in session_data:
219
- st.session_state.current_project = session_data['current_project']
220
-
221
- if 'current_pricing' in session_data:
222
- st.session_state.current_pricing = session_data['current_pricing']
223
-
224
- if 'pricing_history' in session_data:
225
- st.session_state.pricing_history = session_data['pricing_history']
226
-
227
- # استيراد DataFrames
228
- if 'local_content_products' in session_data:
229
- st.session_state.local_content_products = pd.DataFrame(session_data['local_content_products'])
230
-
231
- if 'local_content_services' in session_data:
232
- st.session_state.local_content_services = pd.DataFrame(session_data['local_content_services'])
233
-
234
- if 'local_content_labor' in session_data:
235
- st.session_state.local_content_labor = pd.DataFrame(session_data['local_content_labor'])
236
-
237
- # استيراد البيانات الأخرى
238
- if 'manual_items' in session_data:
239
- st.session_state.manual_items = pd.DataFrame(session_data['manual_items'])
240
-
241
- if 'resources' in session_data:
242
- st.session_state.resources = session_data['resources']
243
-
244
- return True
245
- except Exception as e:
246
- print(f"خطأ في استيراد حالة الجلسة: {str(e)}")
247
- return False