From 56cfbad80eee23e63a91a74320cbd322cbc6320a Mon Sep 17 00:00:00 2001 From: Marwan Alwali Date: Thu, 29 May 2025 21:42:27 +0300 Subject: [PATCH] update --- .DS_Store | Bin 18436 -> 22532 bytes .gitignore | 1 + haikalbot/.DS_Store | Bin 0 -> 10244 bytes ...c Language Support in Django AI Analyst.md | 204 --- haikalbot/README.md | 163 -- haikalbot/ai_agent.py | 788 ++++++++++ haikalbot/analysis_utils.py | 231 --- haikalbot/chatbot_logic.py | 62 - haikalbot/haikal_kb.yaml | 38 + .../langchain_ollama_integration_guide.md | 312 ---- haikalbot/management/.DS_Store | Bin 0 -> 6148 bytes haikalbot/management/__init__.py | 0 haikalbot/management/commands/__init__.py | 0 .../commands/generate_support_yaml.py | 67 + haikalbot/model_analyzer_refactored.zip | Bin 0 -> 14613 bytes haikalbot/ollama_model_recommendations.md | 76 - haikalbot/run_haikal_qa.py | 19 + haikalbot/services/analysis_service.py | 227 --- haikalbot/services/cache_service.py | 61 - haikalbot/services/llm_service.py | 150 -- haikalbot/temp.txt | 80 - haikalbot/training_prompt.md | 161 -- haikalbot/training_prompt_arabic.md | 161 -- haikalbot/urls.py | 4 +- haikalbot/utils/export.py | 66 + haikalbot/views.py | 270 +--- inventory/services.py | 8 +- inventory/utils.py | 4 +- inventory/views.py | 65 +- locale/ar/LC_MESSAGES/django.mo | Bin 238301 -> 240496 bytes locale/ar/LC_MESSAGES/django.po | 1112 ++++++++------ static/.DS_Store | Bin 18436 -> 18436 bytes static/icons/HaikalAi.ai | 1356 +++++++++++++++++ static/images/.DS_Store | Bin 22532 -> 22532 bytes templates/.DS_Store | Bin 14340 -> 14340 bytes templates/haikalbot/.DS_Store | Bin 0 -> 6148 bytes templates/haikalbot/chat.html | 188 +++ templates/haikalbot/chatbot.html | 2 - templates/items/service/service_list.html | 10 +- 39 files changed, 3224 insertions(+), 2662 deletions(-) create mode 100644 haikalbot/.DS_Store delete mode 100644 haikalbot/Optimizing Qwen3-8B for Arabic Language Support in Django AI Analyst.md delete mode 100644 haikalbot/README.md create mode 100644 haikalbot/ai_agent.py delete mode 100644 haikalbot/analysis_utils.py delete mode 100644 haikalbot/chatbot_logic.py create mode 100644 haikalbot/haikal_kb.yaml delete mode 100644 haikalbot/langchain_ollama_integration_guide.md create mode 100644 haikalbot/management/.DS_Store create mode 100644 haikalbot/management/__init__.py create mode 100644 haikalbot/management/commands/__init__.py create mode 100644 haikalbot/management/commands/generate_support_yaml.py create mode 100644 haikalbot/model_analyzer_refactored.zip delete mode 100644 haikalbot/ollama_model_recommendations.md create mode 100644 haikalbot/run_haikal_qa.py delete mode 100644 haikalbot/services/analysis_service.py delete mode 100644 haikalbot/services/cache_service.py delete mode 100644 haikalbot/services/llm_service.py delete mode 100644 haikalbot/temp.txt delete mode 100644 haikalbot/training_prompt.md delete mode 100644 haikalbot/training_prompt_arabic.md create mode 100644 haikalbot/utils/export.py create mode 100644 static/icons/HaikalAi.ai create mode 100644 templates/haikalbot/.DS_Store create mode 100644 templates/haikalbot/chat.html diff --git a/.DS_Store b/.DS_Store index 848cd14efca421b711b32fd6fc24ad911e3d35d0..a786423445c4e06e6642829796f438a0292161df 100644 GIT binary patch delta 2637 zcmbW3du&r>6u{59eeB*#*>2go?J_zB9qZn7?7=2u?gbkNjK^>TakE{oTf<$u^>&+c z!TOjaI5jMvMl?z!sQ;;8qeOf}|1m}lMuJAgXpBY@K_rGq&_D3p+ip}GU})0Yd%pAj z9>1@X`*HFJrUL*rkN7J9Hq$Fl-_6tM^^QmB#R4D!1YkFKK?NU#Vs`$piFsPhjV4l? z=_|khDU(bfqH3cpJ~iY6FlG9N)PNT8c)mu?WVTpSctM;e-`UkIvM(hpap!Yvcs$ODM5cA z*uQ-&;ExRK4EU8`Byev)2@8Uqo9GOB2bEKqS=o}jP+r8&*Jej~$;Ee%_yWTtfyj6~ zpq#fv;H5Wr`MnWkW5}<&`wfjO=Pl#;yY%svutLxHuqYQ5lY_TYqdF5cl`j_rN$cDl z2qs*`$|_!v{M%KpPf_F1#M(MODoA&9jVfU+5DF$nS2ys29ZS3WLW6@;y_QJb+`{wH zhdp}o4PGtMF{A`3ztKyc+{W`ebWM}rPlHlccILYEvb<9p@P?I+v7i!`PV&4lzKO~NLog1H!wYZ_ zUWK>defStoz&G$MoQCh=CpZTe;V(3!1yhhmJ1)c=T!gt;f>l_JHQ0=I;0D}?9oUI? zV-NPC7w^Rob`0S#YB-J$<0CkUd+|wp1`pt~_&mOhui@)>1mD09@e}+MKf|x^2mBS! z;cs{zFL44Va`U(h&dn8aMO-mg$<=aAtYFn*OEYy~VeeE`qm`94GV!J)ixj6<5b> zd2d?;zt*SvLLn8|RDLcyTQ)y-a+w*IFM>Yu3sas}so8WIr3#bdjjDv#Dc=L^virx`)*^<};LUP95c|CF zZ)69G?Ka<-HWbGZ9Am%d4;N%jOn6H3Dyplm?ufdYx;=XY-r-ulYHROqbubv($I9)C z*FHv5I|t!rR!`qTbEwt+ifweRhjcaI@v5W4Ib40tb!@fKM0MQoT(ESmZEL?iEN@$2 z)9Gkmke=blbrqJas9Dv>u6R14L=1VMxX8Jk(4^dVEiq?EDy8TQ;&r~ri^U~Q$wKgo zi}_-KQ?e4b;$o4r#3^ya%eYwX^oWv)Xeb!ImCh1TLOw366zjyOWF|1i<$AF}lt2e_ zTyAo z9oG|vI`K~I!X3C1`%obW4PXGn7{L)7#V9^RB$~uWi9`Ew8lS?a@gTm4hww1IWDqHa z?;5KKT!I_0J}%EZ@xA2rjnV-$E=-6FhFV(_+mAofFm*ZSkB^#?O_MGC|I>14AZde{ z8uc3fy$zSsT@8`ZZQ-CA3igv)uX_G}uWADQ{W@k?MNNA2{1xM)M+1>Yg9gs@MpUxX zU|^jZ>Pxgje+zW(V6u<>8lPSrfOK5P=oy?d0&($b1a_%7Gj_VzWnvdg(qjeYb@7*_ zo>xqm!yc-1xo;W}iBDWl?7d3F>5;vJ^WUmQ1c3;p(>O@9l-*zI7DXKA4MJF0nLyH;70u(B8CfUm2*^zSYil#P-_C{yD9#APK)}JhnNdodWwScZ6J|!C z$sPioli#V7v74FdC>U87P0m)epS)Q>m@}m~IVUMUKZjxR1wrx62L+YcH!DcKW}Dbh z$!M{eM`0)H<~OQ>jFUsvEhgVqx0;-)VKe!&M#JR8n%6cz*ZROXSP4`vNySRBhcw262k}pe9{dxtNlce+c4IbaD5a3Q zf@eQ~_zeV=Ui$$Ay%#@$JqYSSfAgp9?q*w?g3!uLn0Y(1?>BGem-i;j3lXt=rO887rvrk(OXa+O`ngPv#Wwiw{&gMrFrPRX8>;^;slt^klj7#0Q3xCaP~p=3_Uo|FQLIpytvDJWA{ z3?|`tZ*Xlr*sY(qnm4AU^&zWcw&=l(8?lzlPjn5EJ_hnYcA@9w(u5E5Um(BZDty@Z;yzV;fvgOoT0$^2ZP?HCSz8hG*E1jh=^RM|nGs-D4* z5HwtckBf9XIy@bJL5*d&"], # Stop sequences for JSON generation - "repeat_penalty": 1.1 # Slight penalty to avoid repetition - } - ) - except Exception as e: - logger.error(f"Error initializing Ollama LLM: {str(e)}") - return None -``` - -## Prompt Template Optimization for Qwen3-8B - -Qwen3-8B responds well to clear, structured prompts. For Arabic analysis, use this optimized template: - -```python -def create_prompt_analyzer_chain(language='ar'): - """ - Create a LangChain for analyzing prompts in Arabic with Qwen3-8B. - """ - llm = get_ollama_llm() - if not llm: - return None - - # Define the prompt template optimized for Qwen3-8B - if language == 'ar': - template = """ - أنت مساعد ذكي متخصص في تحليل نماذج Django. مهمتك هي تحليل الاستعلام التالي وتحديد: - 1. نوع التحليل المطلوب - 2. نماذج البيانات المستهدفة - 3. أي معلمات استعلام - - الاستعلام: {prompt} - - قم بتقديم إجابتك بتنسيق JSON فقط، بدون أي نص إضافي، كما يلي: - ```json - {{ - "analysis_type": "count" أو "relationship" أو "performance" أو "statistics" أو "general", - "target_models": ["ModelName1", "ModelName2"], - "query_params": {{"field1": "value1", "field2": "value2"}} - }} - ``` - """ - else: - template = """ - You are an intelligent assistant specialized in analyzing Django models. Your task is to analyze the following prompt and determine: - 1. The type of analysis required - 2. Target data models - 3. Any query parameters - - Prompt: {prompt} - - Provide your answer in JSON format only, without any additional text, as follows: - ```json - { - "analysis_type": "count" or "relationship" or "performance" or "statistics" or "general", - "target_models": ["ModelName1", "ModelName2"], - "query_params": {"field1": "value1", "field2": "value2"} - } - ``` - """ - - # Create the prompt template - prompt_template = PromptTemplate( - input_variables=["prompt"], - template=template - ) - - # Create and return the LLM chain - return LLMChain(llm=llm, prompt=prompt_template) -``` - -## Improved JSON Parsing for Qwen3-8B Responses - -Qwen3-8B sometimes includes markdown formatting in its JSON responses. Use this improved parsing function: - -```python -def _parse_llm_json_response(result): - """ - Parse JSON from Qwen3-8B response, handling markdown formatting. - """ - try: - # First try to extract JSON from markdown code blocks - json_match = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', result) - if json_match: - json_str = json_match.group(1).strip() - return json.loads(json_str) - - # If no markdown blocks, try to find JSON object directly - json_match = re.search(r'({[\s\S]*})', result) - if json_match: - json_str = json_match.group(1).strip() - return json.loads(json_str) - - # If still no match, try to parse the entire response as JSON - return json.loads(result.strip()) - except Exception as e: - logger.warning(f"Failed to parse JSON from LLM response: {str(e)}") - return None -``` - -## Performance Considerations for Qwen3-8B - -- **Memory Usage**: Qwen3-8B typically requires 8-16GB of RAM when running on Ollama -- **First Request Latency**: The first request may take 5-10 seconds as the model loads -- **Subsequent Requests**: Typically respond within 1-3 seconds -- **Batch Processing**: Consider batching multiple analyses for efficiency - -## Handling Arabic-Specific Challenges with Qwen3-8B - -1. **Diacritics**: Qwen3-8B handles Arabic diacritics well, but for consistency, consider normalizing input by removing diacritics - -2. **Text Direction**: When displaying results in frontend, ensure proper RTL (right-to-left) support - -3. **Dialectal Variations**: Qwen3-8B performs best with Modern Standard Arabic (MSA), but has reasonable support for major dialects - -4. **Technical Terms**: For Django-specific technical terms, consider providing a glossary in both English and Arabic - -## Example Arabic Prompts Optimized for Qwen3-8B - -``` -# Count query -كم عدد السيارات المتوفرة في النظام؟ - -# Relationship analysis -ما هي العلاقة بين نموذج المستخدم ونموذج الطلب؟ - -# Performance analysis -حدد مشاكل الأداء المحتملة في نموذج المنتج - -# Statistical analysis -ما هو متوسط سعر السيارات المتوفرة؟ -``` - -## Troubleshooting Qwen3-8B Specific Issues - -1. **Incomplete JSON**: If Qwen3-8B returns incomplete JSON, try: - - Reducing the complexity of your prompt - - Lowering the temperature parameter to 0.1 - - Adding explicit JSON formatting instructions - -2. **Arabic Character Encoding**: If you see garbled Arabic text, ensure: - - Your database uses UTF-8 encoding - - All HTTP responses include proper content-type headers - - Frontend properly handles Arabic character rendering - -3. **Slow Response Times**: If responses are slow: - - Consider using the quantized version: `qwen3:8b-q4_0` - - Reduce context window size if full 4096 context isn't needed - - Implement more aggressive caching - -## Conclusion - -Qwen3-8B is an excellent choice for Arabic language support in your Django AI Analyst application. With these optimized settings and techniques, you'll get reliable performance for analyzing Django models through Arabic natural language prompts. diff --git a/haikalbot/README.md b/haikalbot/README.md deleted file mode 100644 index c670987d..00000000 --- a/haikalbot/README.md +++ /dev/null @@ -1,163 +0,0 @@ -# Django AI Analyst - README - -This package provides a Django application that enables AI-powered analysis of Django models through natural language prompts. The AI agent can analyze model structures, relationships, and data to provide insights in JSON format. - -## Features - -- Natural language prompt processing for model analysis -- Support for various types of insights: - - Count queries (e.g., "How many cars do we have?") - - Relationship analysis between models - - Performance optimization suggestions - - Statistical analysis of model fields - - General model structure analysis -- Dealer-specific data access controls -- Caching mechanism for improved performance -- Visualization data generation for frontend display -- Comprehensive test suite - -## Installation - -1. Add 'ai_analyst' to your INSTALLED_APPS setting: - -```python -INSTALLED_APPS = [ - ... - 'ai_analyst', -] -``` - -2. Include the ai_analyst URLconf in your project urls.py: - -```python -path('api/ai/', include('ai_analyst.urls')), -``` - -3. Run migrations to create the AnalysisCache model: - -```bash -python manage.py makemigrations ai_analyst -python manage.py migrate -``` - -## Usage - -Send POST requests to the `/api/ai/analyze/` endpoint with a JSON body containing: - -```json -{ - "prompt": "How many cars do we have?", - "dealer_id": 1 // Optional, for dealer-specific queries -} -``` - -The response will be a JSON object with insights based on the prompt: - -```json -{ - "status": "success", - "request_id": "a1b2c3d4", - "timestamp": "2025-05-25T23:21:56Z", - "prompt": "How many cars do we have?", - "insights": [ - { - "type": "count_analysis", - "results": [ - { - "model": "Car", - "count": 42, - "filters_applied": {} - } - ], - "visualization_data": { - "chart_type": "bar", - "labels": ["Car"], - "data": [42] - } - } - ] -} -``` - -## Customization - -### Cache Duration - -You can customize the cache duration by setting the `CACHE_DURATION` class variable in the `ModelAnalystView` class: - -```python -# In your settings.py -AI_ANALYST_CACHE_DURATION = 7200 # 2 hours in seconds - -# Then in views.py -class ModelAnalystView(View): - CACHE_DURATION = getattr(settings, 'AI_ANALYST_CACHE_DURATION', 3600) - # ... -``` - -### Permission Logic - -The `_check_permissions` method in `ModelAnalystView` can be customized to match your application's permission model: - -```python -def _check_permissions(self, user, dealer_id): - # Your custom permission logic here - return user.has_perm('ai_analyst.can_analyze_models') -``` - -## Example Prompts - -- "How many cars do we have?" -- "Show relationship between User and Order" -- "What is the average price of products?" -- "Count active users" -- "Identify performance issues in the Order model" -- "Show maximum age of customers" - -## Frontend Integration - -The JSON responses include visualization_data that can be used with charting libraries like Chart.js: - -```javascript -// Example with Chart.js -fetch('/api/ai/analyze/', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - prompt: 'How many cars do we have?', - dealer_id: 1 - }), -}) -.then(response => response.json()) -.then(data => { - if (data.status === 'success' && data.insights.length > 0) { - const insight = data.insights[0]; - const vizData = insight.visualization_data; - - const ctx = document.getElementById('insightChart').getContext('2d'); - new Chart(ctx, { - type: vizData.chart_type, - data: { - labels: vizData.labels, - datasets: [{ - label: insight.type, - data: vizData.data, - backgroundColor: [ - 'rgba(255, 99, 132, 0.2)', - 'rgba(54, 162, 235, 0.2)', - 'rgba(255, 206, 86, 0.2)' - ], - borderColor: [ - 'rgba(255, 99, 132, 1)', - 'rgba(54, 162, 235, 1)', - 'rgba(255, 206, 86, 1)' - ], - borderWidth: 1 - }] - } - }); - } -}); -``` diff --git a/haikalbot/ai_agent.py b/haikalbot/ai_agent.py new file mode 100644 index 00000000..331c8016 --- /dev/null +++ b/haikalbot/ai_agent.py @@ -0,0 +1,788 @@ +from dataclasses import dataclass +from typing import List, Dict, Optional, Any, Union +from django.apps import apps +from django.db import models +from django.db.models import QuerySet, Q, F, Value, CharField, Sum, Avg, Count, Max, Min +from django.db.models.functions import Concat, Cast +from django.core.exceptions import FieldDoesNotExist +from django.core.serializers import serialize +from django.conf import settings +from langchain_ollama import ChatOllama +from langchain_core.messages import SystemMessage, HumanMessage +import json +import re +import logging +from functools import reduce +import operator + +from sqlalchemy.orm import relationship + +logger = logging.getLogger(__name__) + +# Configuration settings +LLM_MODEL = getattr(settings, 'MODEL_ANALYZER_LLM_MODEL', 'qwen:7b-chat') +LLM_TEMPERATURE = getattr(settings, 'MODEL_ANALYZER_LLM_TEMPERATURE', 0.3) +LLM_MAX_TOKENS = getattr(settings, 'MODEL_ANALYZER_LLM_MAX_TOKENS', 2048) +CACHE_TIMEOUT = getattr(settings, 'MODEL_ANALYZER_CACHE_TIMEOUT', 3600) + +system_instruction = """ +You are a specialized AI agent designed to analyze Django models and extract relevant information based on user input in Arabic or English. You must: + +1. Model Analysis: + - Parse the user's natural language prompt to understand the analysis requirements + - Identify the relevant Django model(s) from the provided model structure + - Extract only the fields needed for the specific analysis + - Handle both direct fields and relationship fields appropriately + +2. Field Selection: + - Determine relevant fields based on: + * Analysis type (count, average, sum, etc.) + * Explicit field mentions in the prompt + * Related fields needed for joins + * Common fields for the requested analysis type + +3. Return Structure: + Return a JSON response with: + { + "status": "success", + "analysis_requirements": { + "app_label": "", + "model_name": "", + "fields": ["field1", "field2", ...], + "relationships": [{"field": "related_field", "type": "relation_type", "to": "related_model"}] + }, + "language": "" + } + +4. Analysis Types: + - COUNT queries: Return id field + - AGGREGATE queries (avg, sum): Return numeric fields + - DATE queries: Return date/timestamp fields + - RELATIONSHIP queries: Return foreign key and related fields + - TEXT queries: Return relevant text fields + +5. Special Considerations: + - Handle both Arabic and English inputs + - Consider model relationships for joined queries + - Include only fields necessary for the requested analysis + - Support filtering and grouping requirements +""" + + +@dataclass +class FieldAnalysis: + name: str + field_type: str + is_required: bool + is_relation: bool + related_model: Optional[str] = None + analysis_relevance: float = 0.0 + + +@dataclass +class ModelAnalysis: + app_label: str + model_name: str + relevant_fields: List[FieldAnalysis] + relationships: List[Dict[str, str]] + confidence_score: float + + +class DjangoModelAnalyzer: + def __init__(self): + self.analysis_patterns = { + 'count': { + 'patterns': [r'\b(count|number|how many)\b'], + 'fields': ['id'], + 'weight': 1.0 + }, + 'aggregate': { + 'patterns': [r'\b(average|avg|mean|sum|total)\b'], + 'fields': ['price', 'amount', 'value', 'cost', 'quantity'], + 'weight': 0.8 + }, + 'temporal': { + 'patterns': [r'\b(date|time|when|period)\b'], + 'fields': ['created_at', 'updated_at', 'date', 'timestamp'], + 'weight': 0.7 + } + } + + def analyze_prompt(self, prompt: str, model_structure: List) -> ModelAnalysis: + # Initialize LLM + llm = ChatOllama( + model=LLM_MODEL, + temperature=LLM_TEMPERATURE + ) + + # Get model analysis from LLM + messages = [ + SystemMessage(content=system_instruction), + HumanMessage(content=prompt) + ] + + try: + response = llm.invoke(messages) + + if not response or not hasattr(response, 'content') or response.content is None: + raise ValueError("Empty response from LLM") + + analysis_requirements = self._parse_llm_response(response.content) + except Exception as e: + logger.error(f"Error in LLM analysis: {e}") + analysis_requirements = self._pattern_based_analysis(prompt, model_structure) + + return self._enhance_analysis(analysis_requirements, model_structure) + + def _parse_llm_response(self, response: str) -> Dict: + try: + json_match = re.search(r'({.*})', response.replace('\n', ' '), re.DOTALL) + if json_match: + return json.loads(json_match.group(1)) + return {} + except Exception as e: + logger.error(f"Error parsing LLM response: {e}") + return {} + + def _pattern_based_analysis(self, prompt: str, model_structure: List) -> Dict: + analysis_type = None + relevant_fields = [] + + for analysis_name, config in self.analysis_patterns.items(): + for pattern in config['patterns']: + if re.search(pattern, prompt.lower()): + relevant_fields.extend(config['fields']) + analysis_type = analysis_name + break + if analysis_type: + break + + return { + 'analysis_type': analysis_type or 'basic', + 'fields': list(set(relevant_fields)) or ['id', 'name'] + } + + def _enhance_analysis(self, requirements: Dict, model_structure: List) -> ModelAnalysis: + app_label = requirements.get("analysis_requirements", {}).get("app_label") + model_name = requirements.get("analysis_requirements", {}).get("model_name") + fields = requirements.get("analysis_requirements", {}).get("fields") or [] + + if not isinstance(fields, list): + raise ValueError(f"Invalid fields in analysis requirements: {fields}") + + try: + model = apps.get_model(app_label, model_name) + except LookupError as e: + logger.error(f"Model lookup error: {e}") + return None + + relevant_fields = [] + relationships = [] + + for field_name in fields: + try: + field = model._meta.get_field(field_name) + field_analysis = FieldAnalysis( + name=field_name, + field_type=field.get_internal_type(), + is_required=not field.null if hasattr(field, 'null') else True, + is_relation=field.is_relation, + related_model=field.related_model.__name__ if field.is_relation and hasattr(field, + 'related_model') and field.related_model else None + ) + + field_analysis.analysis_relevance = self._calculate_field_relevance( + field_analysis, + requirements.get('analysis_type', 'basic') + ) + + relevant_fields.append(field_analysis) + + if field.is_relation: + relationships.append({ + 'field': field_name, + 'type': field.get_internal_type(), + 'to': field.related_model.__name__ if hasattr(field, + 'related_model') and field.related_model else '' + }) + + except FieldDoesNotExist: + logger.warning(f"Field {field_name} not found in {model_name}") + + return ModelAnalysis( + app_label=app_label, + model_name=model_name, + relevant_fields=sorted(relevant_fields, key=lambda x: x.analysis_relevance, reverse=True), + relationships=relationships, + confidence_score=self._calculate_confidence_score(relevant_fields) + ) + + def _calculate_field_relevance(self, field: FieldAnalysis, analysis_type: str) -> float: + base_score = 0.5 + if analysis_type in self.analysis_patterns: + if field.name in self.analysis_patterns[analysis_type]['fields']: + base_score += self.analysis_patterns[analysis_type]['weight'] + if field.is_required: + base_score += 0.2 + if field.is_relation: + base_score += 0.1 + return min(base_score, 1.0) + + def _calculate_confidence_score(self, fields: List[FieldAnalysis]) -> float: + if not fields: + return 0.0 + return sum(field.analysis_relevance for field in fields) / len(fields) + + +def get_all_model_structures(filtered_apps: Optional[List[str]] = None) -> List[Dict]: + """ + Retrieve structure information for all Django models, optionally filtered by app names. + + Args: + filtered_apps: Optional list of app names to filter models by + + Returns: + List of dictionaries containing model structure information + """ + structures = [] + for model in apps.get_models(): + app_label = model._meta.app_label + if filtered_apps and app_label not in filtered_apps: + continue + + fields = {} + relationships = [] + + for field in model._meta.get_fields(): + if field.is_relation: + # Get related model name safely + related_model_name = None + if hasattr(field, 'related_model') and field.related_model: + related_model_name = field.related_model.__name__ + elif hasattr(field, 'model') and field.model: + related_model_name = field.model.__name__ + + if related_model_name: # Only add relationship if we have a valid related model + relationships.append({ + "field": field.name, + "type": field.get_internal_type(), + "to": related_model_name + }) + else: + fields[field.name] = field.get_internal_type() + + structures.append({ + "app_label": app_label, + "model_name": model.__name__, + "fields": fields, + "relationships": relationships + }) + + return structures + + +def apply_joins(queryset: QuerySet, joins: List[Dict[str, str]]) -> QuerySet: + """ + Apply joins to the queryset based on the provided join specifications. + + Args: + queryset: The base queryset to apply joins to + joins: List of join specifications with path and type + + Returns: + Updated queryset with joins applied + """ + if not joins: + return queryset + + for join in joins: + path = join.get("path") + join_type = join.get("type", "LEFT").upper() + + if not path: + continue + + try: + if join_type == "LEFT": + queryset = queryset.select_related(path) + else: + queryset = queryset.prefetch_related(path) + except Exception as e: + logger.warning(f"Failed to apply join for {path}: {e}") + + return queryset + + +def apply_filters(queryset: QuerySet, filters: Dict[str, Any]) -> QuerySet: + """ + Apply filters to queryset with advanced filter operations. + + Args: + queryset: The base queryset to apply filters to + filters: Dictionary of field:value pairs or complex filter operations + + Returns: + Filtered queryset + """ + if not filters: + return queryset + + q_objects = [] + + for key, value in filters.items(): + if isinstance(value, dict): + # Handle complex filters + operation = value.get('operation', 'exact') + filter_value = value.get('value') + + if not filter_value and operation != 'isnull': + continue + + if operation == 'contains': + q_objects.append(Q(**{f"{key}__icontains": filter_value})) + elif operation == 'in': + if isinstance(filter_value, list) and filter_value: + q_objects.append(Q(**{f"{key}__in": filter_value})) + elif operation in ['gt', 'gte', 'lt', 'lte', 'exact', 'iexact', 'startswith', 'endswith']: + q_objects.append(Q(**{f"{key}__{operation}": filter_value})) + elif operation == 'between' and isinstance(filter_value, list) and len(filter_value) >= 2: + q_objects.append(Q(**{ + f"{key}__gte": filter_value[0], + f"{key}__lte": filter_value[1] + })) + elif operation == 'isnull': + q_objects.append(Q(**{f"{key}__isnull": bool(filter_value)})) + else: + # Simple exact match + q_objects.append(Q(**{key: value})) + + if not q_objects: + return queryset + + return queryset.filter(reduce(operator.and_, q_objects)) + + +def process_aggregation( + queryset: QuerySet, + aggregation: str, + fields: List[str], + group_by: Optional[List[str]] = None +) -> Union[Dict[str, Any], List[Dict[str, Any]]]: + """ + Process aggregation queries with support for grouping. + + Args: + queryset: The base queryset to aggregate + aggregation: Aggregation type (sum, avg, count, max, min) + fields: Fields to aggregate + group_by: Optional fields to group by + + Returns: + Dictionary of aggregation results or list of grouped results + """ + if not fields: + return {"error": "No fields specified for aggregation"} + + agg_func_map = { + "sum": Sum, + "avg": Avg, + "count": Count, + "max": Max, + "min": Min + } + + agg_func = agg_func_map.get(aggregation.lower()) + if not agg_func: + return {"error": f"Unsupported aggregation: {aggregation}"} + + try: + if group_by: + # Create aggregation dictionary for valid fields + agg_dict = {} + for field in fields: + if field not in group_by: + agg_dict[f"{aggregation}_{field}"] = agg_func(field) + + if not agg_dict: + return {"error": "No valid fields for aggregation after excluding group_by fields"} + + # Apply group_by and aggregation + return list(queryset.values(*group_by).annotate(**agg_dict)) + else: + # Simple aggregation without grouping + return queryset.aggregate(**{ + f"{aggregation}_{field}": agg_func(field) + for field in fields + }) + except Exception as e: + logger.error(f"Aggregation error: {e}") + return {"error": f"Aggregation failed: {str(e)}"} + + +def prepare_chart_data(data: List[Dict], fields: List[str], chart_type: str) -> Optional[Dict[str, Any]]: + """ + Prepare data for chart visualization. + + Args: + data: List of data dictionaries + fields: Fields to include in the chart + chart_type: Type of chart (pie, bar, line) + + Returns: + Dictionary with chart configuration + """ + if not data or not fields or len(fields) < 1 or not chart_type: + return None + + # Validate chart type + chart_type = chart_type.lower() + if chart_type not in ["pie", "bar", "line", "doughnut", "radar", "scatter"]: + chart_type = "bar" # Default to bar chart for unsupported types + + try: + # For aggregation results that come as a dictionary + if isinstance(data, dict): + # Convert single dict to list for chart processing + labels = list(data.keys()) + values = list(data.values()) + + return { + "type": chart_type, + "labels": [str(label).replace(f"{fields[0]}_", "") for label in labels], + "data": [float(value) if isinstance(value, (int, float)) else 0 for value in values], + "backgroundColor": [ + "rgba(54, 162, 235, 0.6)", + "rgba(255, 99, 132, 0.6)", + "rgba(255, 206, 86, 0.6)", + "rgba(75, 192, 192, 0.6)", + "rgba(153, 102, 255, 0.6)", + "rgba(255, 159, 64, 0.6)" + ] + } + + # For regular query results as list of dictionaries + # Create labels from first field values + labels = [str(item.get(fields[0], "")) for item in data] + + if chart_type == "pie" or chart_type == "doughnut": + # For pie charts, we need just one data series + data_values = [] + for item in data: + # Use second field for values if available, otherwise use 1 + if len(fields) > 1: + try: + value = float(item.get(fields[1], 0)) + except (ValueError, TypeError): + value = 0 + data_values.append(value) + else: + data_values.append(1) # Default count if no value field + + return { + "type": chart_type, + "labels": labels, + "data": data_values, + "backgroundColor": [ + "rgba(54, 162, 235, 0.6)", + "rgba(255, 99, 132, 0.6)", + "rgba(255, 206, 86, 0.6)", + "rgba(75, 192, 192, 0.6)", + "rgba(153, 102, 255, 0.6)", + "rgba(255, 159, 64, 0.6)" + ] * (len(data_values) // 6 + 1) # Repeat colors as needed + } + else: + # For other charts, create dataset for each field after the first + datasets = [] + for i, field in enumerate(fields[1:], 1): + try: + dataset = { + "label": field, + "data": [float(item.get(field, 0) or 0) for item in data], + "backgroundColor": f"rgba({50 + i * 50}, {100 + i * 40}, 235, 0.6)", + "borderColor": f"rgba({50 + i * 50}, {100 + i * 40}, 235, 1.0)", + "borderWidth": 1 + } + datasets.append(dataset) + except (ValueError, TypeError) as e: + logger.warning(f"Error processing field {field} for chart: {e}") + + return { + "type": chart_type, + "labels": labels, + "datasets": datasets + } + except Exception as e: + logger.error(f"Error preparing chart data: {e}") + return None + + +def query_django_model(parsed: Dict[str, Any]) -> Dict[str, Any]: + """ + Execute Django model queries based on parsed analysis requirements. + + Args: + parsed: Dictionary containing query parameters: + - app_label: Django app label + - model: Model name + - fields: List of fields to query + - filters: Query filters + - aggregation: Aggregation type + - chart: Chart type for visualization + - joins: List of joins to apply + - group_by: Fields to group by + - order_by: Fields to order by + - limit: Maximum number of results + + Returns: + Dictionary with query results + """ + try: + # Extract parameters with defaults + app_label = parsed.get("app_label") + model_name = parsed.get("model_name") + fields = parsed.get("fields", []) + filters = parsed.get("filters", {}) + aggregation = parsed.get("aggregation") + chart = parsed.get("chart") + joins = parsed.get("joins", []) + group_by = parsed.get("group_by", []) + order_by = parsed.get("order_by", []) + limit = int(parsed.get("limit", 1000)) + language = parsed.get("language", "en") + + # Validate required parameters + if not app_label or not model_name: + return { + "status": "error", + "error": "app_label and model are required", + "language": language + } + + # Get model class + try: + model = apps.get_model(app_label=app_label, model_name=model_name) + except LookupError: + return { + "status": "error", + "error": f"Model '{model_name}' not found in app '{app_label}'", + "language": language + } + + # Validate fields against model + if fields: + model_fields = [f.name for f in model._meta.fields] + invalid_fields = [f for f in fields if f not in model_fields] + if invalid_fields: + logger.warning(f"Invalid fields requested: {invalid_fields}") + fields = [f for f in fields if f in model_fields] + + # Build queryset + queryset = model.objects.all() + + # Apply joins + queryset = apply_joins(queryset, joins) + + # Apply filters + if filters: + try: + queryset = apply_filters(queryset, filters) + except Exception as e: + logger.error(f"Error applying filters: {e}") + return { + "status": "error", + "error": f"Invalid filters: {str(e)}", + "language": language + } + + # Handle aggregations + if aggregation: + result = process_aggregation(queryset, aggregation, fields, group_by) + + if isinstance(result, dict) and "error" in result: + return { + "status": "error", + "error": result["error"], + "language": language + } + + chart_data = None + if chart: + chart_data = prepare_chart_data(result, fields, chart) + + return { + "status": "success", + "data": result, + "chart": chart_data, + "language": language + } + + # Handle regular queries + try: + # Apply field selection + if fields: + queryset = queryset.values(*fields) + + # Apply ordering + if order_by: + queryset = queryset.order_by(*order_by) + + # Apply limit (with safety check) + if limit <= 0: + limit = 1000 + queryset = queryset[:limit] + + # Convert queryset to list + data = list(queryset) + + # Prepare chart data if needed + chart_data = None + if chart and data and fields: + chart_data = prepare_chart_data(data, fields, chart) + + return { + "status": "success", + "data": data, + "count": len(data), + "chart": chart_data, + "metadata": { + "total_count": len(data), + "fields": fields, + "model": model_name, + "app": app_label + }, + "language": language + } + + except Exception as e: + logger.error(f"Error executing query: {e}") + return { + "status": "error", + "error": f"Query execution failed: {str(e)}", + "language": language + } + + except Exception as e: + logger.error(f"Unexpected error in query_django_model: {e}") + return { + "status": "error", + "error": f"Unexpected error: {str(e)}", + "language": parsed.get("language", "en") + } + + +def determine_aggregation_type(prompt: str, fields: List[FieldAnalysis]) -> Optional[str]: + """ + Determine the appropriate aggregation type based on the prompt and fields. + + Args: + prompt: User prompt text + fields: List of field analysis objects + + Returns: + Aggregation type or None + """ + if any(pattern in prompt.lower() for pattern in ['average', 'avg', 'mean', 'معدل', 'متوسط']): + return 'avg' + elif any(pattern in prompt.lower() for pattern in ['sum', 'total', 'مجموع', 'إجمالي']): + return 'sum' + elif any(pattern in prompt.lower() for pattern in ['count', 'number', 'how many', 'عدد', 'كم']): + return 'count' + elif any(pattern in prompt.lower() for pattern in ['maximum', 'max', 'highest', 'أقصى', 'أعلى']): + return 'max' + elif any(pattern in prompt.lower() for pattern in ['minimum', 'min', 'lowest', 'أدنى', 'أقل']): + return 'min' + + # Check field types for numeric fields to determine default aggregation + numeric_fields = [field for field in fields if field.field_type in ['DecimalField', 'FloatField', 'IntegerField']] + if numeric_fields: + return 'sum' # Default to sum for numeric fields + + return None + + +def determine_chart_type(prompt: str, fields: List[FieldAnalysis]) -> Optional[str]: + """ + Determine the appropriate chart type based on the prompt and fields. + + Args: + prompt: User prompt text + fields: List of field analysis objects + + Returns: + Chart type or None + """ + # Check for explicit chart type mentions in prompt + if any(term in prompt.lower() for term in ['line chart', 'time series', 'trend', 'رسم خطي', 'اتجاه']): + return 'line' + elif any(term in prompt.lower() for term in ['bar chart', 'histogram', 'column', 'رسم شريطي', 'أعمدة']): + return 'bar' + elif any(term in prompt.lower() for term in ['pie chart', 'circle chart', 'رسم دائري', 'فطيرة']): + return 'pie' + elif any(term in prompt.lower() for term in ['doughnut', 'دونات']): + return 'doughnut' + elif any(term in prompt.lower() for term in ['radar', 'spider', 'رادار']): + return 'radar' + + # Determine chart type based on field types and count + date_fields = [field for field in fields if field.field_type in ['DateField', 'DateTimeField']] + numeric_fields = [field for field in fields if field.field_type in ['DecimalField', 'FloatField', 'IntegerField']] + + if date_fields and numeric_fields: + return 'line' # Time series data + elif len(fields) == 2 and len(numeric_fields) >= 1: + return 'bar' # Category and value + elif len(fields) == 1 or (len(fields) == 2 and len(numeric_fields) == 1): + return 'pie' # Single dimension data + elif len(fields) > 2: + return 'bar' # Multi-dimensional data + + # Default + return 'bar' + + +def analyze_prompt(prompt: str) -> Dict[str, Any]: + """ + Analyze a natural language prompt and execute the appropriate Django model query. + + Args: + prompt: Natural language prompt from user + + Returns: + Dictionary with query results + """ + # Detect language + language = "ar" if bool(re.search(r'[\u0600-\u06FF]', prompt)) else "en" + filtered_apps = ['inventory', 'django_ledger', 'appointments', 'plans'] + try: + analyzer = DjangoModelAnalyzer() + model_structure = get_all_model_structures(filtered_apps=filtered_apps) + + analysis = analyzer.analyze_prompt(prompt, model_structure) + + if not analysis or not analysis.app_label or not analysis.model_name: + return { + "status": "error", + "message": "تعذر العثور على النموذج المطلوب" if language == "ar" else "Missing model information", + "language": language + } + + query_params = { + "app_label": analysis.app_label, + "model_name": analysis.model_name, + "fields": [field.name for field in analysis.relevant_fields], + "joins": [{"path": rel["field"], "type": rel["type"]} for rel in analysis.relationships], + "filters": {}, + "aggregation": determine_aggregation_type(prompt, analysis.relevant_fields), + "chart": determine_chart_type(prompt, analysis.relevant_fields), + "language": language + } + + return query_django_model(query_params) + except Exception as e: + logger.error(f"Error analyzing prompt: {e}") + return { + "status": "error", + "error": "حدث خطأ أثناء تحليل الاستعلام" if language == "ar" else f"Error analyzing prompt: {str(e)}", + "language": language + } \ No newline at end of file diff --git a/haikalbot/analysis_utils.py b/haikalbot/analysis_utils.py deleted file mode 100644 index b3a0a8d4..00000000 --- a/haikalbot/analysis_utils.py +++ /dev/null @@ -1,231 +0,0 @@ -from django.db.models import Avg, Sum, Max, Min, ForeignKey, OneToOneField -import inspect -from django.db import models -from django.utils.translation import gettext_lazy as _ - - -def _localized_keys(language): - if language == 'ar': - return { - 'type': 'نوع', 'model': 'النموذج', 'count': 'العدد', 'filters': 'الفلاتر_المطبقة', - 'error': 'خطأ', 'chart_type': 'نوع_الرسم_البياني', 'labels': 'التسميات', 'data': 'البيانات', - 'visualization_data': 'بيانات_الرسم_البياني', 'field': 'الحقل', 'value': 'القيمة', - 'statistic_type': 'نوع_الإحصاء', 'results': 'النتائج', 'title': 'العنوان' - } - else: - return { - 'type': 'type', 'model': 'model', 'count': 'count', 'filters': 'filters_applied', - 'error': 'error', 'chart_type': 'chart_type', 'labels': 'labels', 'data': 'data', - 'visualization_data': 'visualization_data', 'field': 'field', 'value': 'value', - 'statistic_type': 'statistic_type', 'results': 'results', 'title': 'title' - } - - -def generate_count_insight(models, query_params, dealer_id=None, language='ar'): - keys = _localized_keys(language) - results = [] - - for model in models: - try: - queryset = model.objects.all() - - if dealer_id: - if hasattr(model, 'dealer_id'): - queryset = queryset.filter(dealer_id=dealer_id) - elif hasattr(model, 'dealer'): - queryset = queryset.filter(dealer=dealer_id) - - filters = {} - for key, value in query_params.items(): - if key not in ['field', 'operation'] and hasattr(model, key): - try: - field = model._meta.get_field(key) - if isinstance(field, models.IntegerField): - value = int(value) - elif isinstance(field, models.BooleanField): - value = value.lower() in ('true', '1', 'yes') - except Exception: - pass - filters[key] = value - - if filters: - queryset = queryset.filter(**filters) - - results.append({ - keys['model']: model.__name__, - keys['count']: queryset.count(), - keys['filters']: filters - }) - - except Exception as e: - results.append({ - keys['model']: model.__name__, - keys['error']: str(e) - }) - - return { - 'type': keys['type'] + '_analysis', - keys['results']: results, - keys['visualization_data']: { - keys['chart_type']: 'bar', - keys['labels']: [r[keys['model']] for r in results if keys['count'] in r], - keys['data']: [r[keys['count']] for r in results if keys['count'] in r] - } - } - - -def generate_statistics_insight(models, query_params, dealer_id=None, language='ar'): - keys = _localized_keys(language) - results = [] - field = query_params.get('field') - operation = query_params.get('operation', 'average') - - for model in models: - try: - if not field or not hasattr(model, field): - continue - - queryset = model.objects.all() - if dealer_id: - if hasattr(model, 'dealer_id'): - queryset = queryset.filter(dealer_id=dealer_id) - elif hasattr(model, 'dealer'): - queryset = queryset.filter(dealer=dealer_id) - - filters = {} - for k, v in query_params.items(): - if k not in ['field', 'operation'] and hasattr(model, k): - filters[k] = v - - if filters: - queryset = queryset.filter(**filters) - - stat_map = { - 'average': Avg, - 'sum': Sum, - 'max': Max, - 'min': Min - } - - if operation in stat_map: - agg = queryset.aggregate(val=stat_map[operation](field))['val'] - value = agg - else: - value = queryset.count() - - results.append({ - keys['model']: model.__name__, - keys['field']: field, - keys['statistic_type']: operation, - keys['value']: value, - keys['filters']: filters - }) - - except Exception as e: - results.append({keys['model']: model.__name__, keys['error']: str(e)}) - - return { - 'type': keys['type'] + '_analysis', - keys['results']: results, - keys['visualization_data']: { - keys['chart_type']: 'bar', - keys['labels']: [f"{r[keys['model']]}.{r[keys['field']]}" for r in results if keys['value'] in r], - keys['data']: [r[keys['value']] for r in results if keys['value'] in r], - keys['title']: f"{operation} of {field}" if language != 'ar' else f"{field} ({operation})" - } - } - - -def generate_recommendations(model_classes, analysis_type, language='ar'): - recs = [] - for model in model_classes: - for field in model._meta.fields: - if isinstance(field, ForeignKey) and not field.db_index: - msg = f"أضف db_index=True إلى {model.__name__}.{field.name}" if language == 'ar' else f"Add db_index=True to {model.__name__}.{field.name}" - recs.append(msg) - if isinstance(field, models.CharField) and not field.db_index and field.name in ['name', 'title', 'description', 'text']: - msg = f"فكر في فهرسة الحقل النصي {model.__name__}.{field.name}" if language == 'ar' else f"Consider indexing the text field {model.__name__}.{field.name}" - recs.append(msg) - return recs[:5] - - -def generate_model_insight(model, dealer_id=None, language='ar'): - keys = _localized_keys(language) - fields_info = [ - { - 'name': f.name, - 'type': f.__class__.__name__, - 'null': f.null, - 'blank': f.blank, - 'unique': f.unique, - 'pk': f.primary_key - } for f in model._meta.fields - ] - - try: - qs = model.objects.all() - if dealer_id: - if hasattr(model, 'dealer_id'): - qs = qs.filter(dealer_id=dealer_id) - elif hasattr(model, 'dealer'): - qs = qs.filter(dealer=dealer_id) - count = qs.count() - except Exception: - count = "error" - - return { - 'type': keys['type'] + '_analysis', - keys['model']: model.__name__, - 'fields': fields_info, - 'count': count - } - - -def generate_relationship_insight(models, query_params=None, dealer_id=None, language='ar'): - from_ = "من" if language == 'ar' else "from" - to_ = "إلى" if language == 'ar' else "to" - rel_type = "نوع" if language == 'ar' else "type" - relationships = [] - - for model in models: - for field in model._meta.fields: - if isinstance(field, (ForeignKey, OneToOneField)): - relationships.append({ - from_: model.__name__, - to_: field.related_model.__name__, - rel_type: field.__class__.__name__ - }) - for field in model._meta.many_to_many: - relationships.append({ - from_: model.__name__, - to_: field.related_model.__name__, - rel_type: 'ManyToManyField' - }) - - return { - 'type': 'تحليل_العلاقات' if language == 'ar' else 'relationship_analysis', - 'relationships': relationships - } - - -def generate_performance_insight(models, query_params=None, dealer_id=None, language='ar'): - issues = [] - for model in models: - for field in model._meta.fields: - if isinstance(field, ForeignKey) and not field.db_index: - issues.append({ - 'model': model.__name__, - 'field': field.name, - 'issue': 'Missing index on ForeignKey' - }) - if isinstance(field, models.CharField) and not field.db_index and field.name in ['name', 'title']: - issues.append({ - 'model': model.__name__, - 'field': field.name, - 'issue': 'Unindexed CharField used in filtering' - }) - - return { - 'type': 'تحليل_الأداء' if language == 'ar' else 'performance_analysis', - 'issues': issues - } diff --git a/haikalbot/chatbot_logic.py b/haikalbot/chatbot_logic.py deleted file mode 100644 index f493274f..00000000 --- a/haikalbot/chatbot_logic.py +++ /dev/null @@ -1,62 +0,0 @@ -from openai import OpenAI -from inventory import models -from car_inventory import settings - -def fetch_data(dealer): - - try: - # Annotate total cars by make, model, and trim - cars = models.Car.objects.filter(dealer=dealer).count() - print(cars) - if cars: - return f"إجمالي عدد السيارات في المخزون الخاص بـ {dealer.get_local_name}) هو {cars}" - # return f"The total cars in {dealer} inventory is ( {cars} )." - else: - return "No cars found in the inventory." - except Exception as e: - return f"An error occurred while fetching car data: {str(e)}" - - -def get_gpt4_response(user_input, dealer): - """ - Generates a response from the GPT-4 model based on the provided user input - and the dealer's information. The function is tailored to assist with car - inventory management, including queries about inventory, sales processes, - car transfers, invoices, and other features specific to the Haikal system. - - :param user_input: The text input or query provided by the user. - :type user_input: str - :param dealer: Dealer information or identifier used to fetch related car data - or contextual information. - :type dealer: Any - :return: The generated response from the GPT-4 model as a string. - :rtype: str - :raises Exception: In case of an error during the API call to generate the - GPT-4 response. - """ - dealer = dealer - client = OpenAI(api_key=settings.OPENAI_API_KEY) - - # if "سيارة في المخزون" in user_input.lower(): - # # cars = user_input.split("how many cars")[-1].strip() - # car_data = fetch_data(dealer) - # user_input += f" Relevant car data: {car_data}" - try: - completion = client.chat.completions.create( - model="gpt-4o", - messages=[ - { - "role": "system", - "content": ( - "You are an assistant specialized in car inventory management for the Haikal system. " - "You can answer questions about the inventory, sales process, car transfers, invoices, " - "and other application-specific functionalities. Always provide responses aligned " - "with the Haikal system's structure and features." - ) - }, - {"role": "user", "content": user_input}, - ], - ) - return completion.choices[0].message.content.strip() - except Exception as e: - return f"An error occurred while generating the response: {str(e)}" diff --git a/haikalbot/haikal_kb.yaml b/haikalbot/haikal_kb.yaml new file mode 100644 index 00000000..00b23b7a --- /dev/null +++ b/haikalbot/haikal_kb.yaml @@ -0,0 +1,38 @@ +metadata: + system_name: Haikal + version: 1.0 + language: bilingual + roles: + - admin + - dealer + - branch + - supplier + +features: + add_car: + description: Add a new car to inventory + steps: + - Navigate to the "Inventory" section + - Click "Add New Car" + - Enter required fields: VIN, Make, Model, Year + - Optional: Upload custom card, add warranty or insurance + - Click "Save" + permissions: ["admin", "dealer"] + related_terms: ["chassis", "هيكل", "السيارة"] + + create_invoice: + description: Create a sale or purchase invoice + steps: + - Go to the "Invoices" page + - Click "New Invoice" + - Choose Type: Sale or Purchase + - Select invoice_from and invoice_to + - Link existing order(s) + - Confirm and save + permissions: ["admin", "dealer"] + notes: Use sale for customer transactions, purchase for supplier buys + +glossary: + VIN: Vehicle Identification Number or chassis number (هيكل السيارة) + custom_card: Official car registration document (استمارة) + adjustment: Any cost added to or subtracted from an order \ No newline at end of file diff --git a/haikalbot/langchain_ollama_integration_guide.md b/haikalbot/langchain_ollama_integration_guide.md deleted file mode 100644 index 5631e8f9..00000000 --- a/haikalbot/langchain_ollama_integration_guide.md +++ /dev/null @@ -1,312 +0,0 @@ -# Integrating Ollama with LangChain for Django AI Analyst - -This guide provides step-by-step instructions for integrating Ollama with LangChain in your Django AI Analyst application, with specific focus on Arabic language support. - -## Prerequisites - -1. Ollama installed on your system -2. An Ollama model with Arabic support (preferably Jais-13B as recommended) -3. Django project with the AI Analyst application - -## Installation Steps - -### 1. Install Required Python Packages - -```bash -pip install langchain langchain-community -``` - -### 2. Configure Django Settings - -Add the following to your Django settings.py file: - -```python -# Ollama and LangChain settings -OLLAMA_BASE_URL = "http://10.10.1.132:11434" # Default Ollama API URL -OLLAMA_MODEL = "qwen3:6b" # Or your preferred model -OLLAMA_TIMEOUT = 120 # Seconds -``` - -### 3. Create a LangChain Utility Module - -Create a new file `ai_analyst/langchain_utils.py`: - -```python -from langchain.llms import Ollama -from langchain.chains import LLMChain -from langchain.prompts import PromptTemplate -from django.conf import settings -import logging - -logger = logging.getLogger(__name__) - -def get_ollama_llm(): - """ - Initialize and return an Ollama LLM instance configured for Arabic support. - """ - try: - # Get settings from Django settings or use defaults - base_url = getattr(settings, 'OLLAMA_BASE_URL', 'http://10.10.1.132:11434') - model = getattr(settings, 'OLLAMA_MODEL', 'qwen3:8b') - timeout = getattr(settings, 'OLLAMA_TIMEOUT', 120) - - # Configure Ollama with appropriate parameters for Arabic - return Ollama( - base_url=base_url, - model=model, - timeout=timeout, - # Parameters to improve Arabic language generation - parameters={ - "temperature": 0.7, - "top_p": 0.9, - "top_k": 40, - "num_ctx": 2048, # Context window size - } - ) - except Exception as e: - logger.error(f"Error initializing Ollama LLM: {str(e)}") - return None - -def create_prompt_analyzer_chain(language='ar'): - """ - Create a LangChain for analyzing prompts in Arabic or English. - """ - llm = get_ollama_llm() - if not llm: - return None - - # Define the prompt template based on language - if language == 'ar': - template = """ - قم بتحليل الاستعلام التالي وتحديد نوع التحليل المطلوب ونماذج البيانات المستهدفة وأي معلمات استعلام. - - الاستعلام: {prompt} - - قم بتقديم إجابتك بتنسيق JSON كما يلي: - {{ - "analysis_type": "count" أو "relationship" أو "performance" أو "statistics" أو "general", - "target_models": ["ModelName1", "ModelName2"], - "query_params": {{"field1": "value1", "field2": "value2"}} - }} - """ - else: - template = """ - Analyze the following prompt and determine the type of analysis required, target data models, and any query parameters. - - Prompt: {prompt} - - Provide your answer in JSON format as follows: - { - "analysis_type": "count" or "relationship" or "performance" or "statistics" or "general", - "target_models": ["ModelName1", "ModelName2"], - "query_params": {"field1": "value1", "field2": "value2"} - } - """ - - # Create the prompt template - prompt_template = PromptTemplate( - input_variables=["prompt"], - template=template - ) - - # Create and return the LLM chain - return LLMChain(llm=llm, prompt=prompt_template) -``` - -### 4. Update Your View to Use LangChain - -Modify your `ModelAnalystView` class to use the LangChain utilities: - -```python -from .langchain_utils import create_prompt_analyzer_chain -import json -import re - -class ModelAnalystView(View): - # ... existing code ... - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # We'll initialize chains on demand to avoid startup issues - self.prompt_analyzer_chains = {} - - def _get_prompt_analyzer_chain(self, language='ar'): - """ - Get or create a prompt analyzer chain for the specified language. - """ - if language not in self.prompt_analyzer_chains: - self.prompt_analyzer_chains[language] = create_prompt_analyzer_chain(language) - return self.prompt_analyzer_chains[language] - - def _analyze_prompt_with_llm(self, prompt, language='ar'): - """ - Use LangChain and Ollama to analyze the prompt. - """ - try: - # Get the appropriate chain for the language - chain = self._get_prompt_analyzer_chain(language) - if not chain: - # Fallback to rule-based analysis if chain creation failed - return self._analyze_prompt_rule_based(prompt, language) - - # Run the chain - result = chain.run(prompt=prompt) - - # Parse the JSON result - # Find JSON content within the response (in case the LLM adds extra text) - json_match = re.search(r'({.*})', result.replace('\n', ' '), re.DOTALL) - if json_match: - json_str = json_match.group(1) - return json.loads(json_str) - else: - # Fallback to rule-based analysis - return self._analyze_prompt_rule_based(prompt, language) - - except Exception as e: - logger.error(f"Error in LLM prompt analysis: {str(e)}") - # Fallback to rule-based analysis - return self._analyze_prompt_rule_based(prompt, language) - - def _analyze_prompt_rule_based(self, prompt, language='ar'): - """ - Rule-based fallback for prompt analysis. - """ - analysis_type, target_models, query_params = self._analyze_prompt(prompt, language) - return { - "analysis_type": analysis_type, - "target_models": target_models, - "query_params": query_params - } - - def _process_prompt(self, prompt, user, dealer_id, language='ar'): - """ - Process the natural language prompt and generate insights. - """ - # ... existing code ... - - # Use LLM for prompt analysis - analysis_result = self._analyze_prompt_with_llm(prompt, language) - analysis_type = analysis_result.get('analysis_type', 'general') - target_models = analysis_result.get('target_models', []) - query_params = analysis_result.get('query_params', {}) - - # ... rest of the method ... -``` - -## Testing the Integration - -Create a test script to verify the Ollama and LangChain integration: - -```python -# test_ollama.py -import os -import sys -import django - -# Set up Django environment -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings') -django.setup() - -from ai_analyst.langchain_utils import get_ollama_llm, create_prompt_analyzer_chain - -def test_ollama_connection(): - """Test basic Ollama connection and response.""" - llm = get_ollama_llm() - if not llm: - print("Failed to initialize Ollama LLM") - return - - # Test with Arabic prompt - arabic_prompt = "مرحبا، كيف حالك؟" - print(f"Testing Arabic prompt: {arabic_prompt}") - try: - response = llm.invoke(arabic_prompt) - print(f"Response: {response}") - print("Ollama connection successful!") - except Exception as e: - print(f"Error: {str(e)}") - -def test_prompt_analysis(): - """Test the prompt analyzer chain.""" - chain = create_prompt_analyzer_chain('ar') - if not chain: - print("Failed to create prompt analyzer chain") - return - - # Test with an Arabic analysis prompt - analysis_prompt = "كم عدد السيارات التي لدينا؟" - print(f"Testing analysis prompt: {analysis_prompt}") - try: - result = chain.run(prompt=analysis_prompt) - print(f"Analysis result: {result}") - except Exception as e: - print(f"Error: {str(e)}") - -if __name__ == "__main__": - print("Testing Ollama and LangChain integration...") - test_ollama_connection() - print("\n---\n") - test_prompt_analysis() -``` - -Run the test script: - -```bash -python test_ollama.py -``` - -## Troubleshooting - -### Common Issues and Solutions - -1. **Ollama Connection Error** - - Ensure Ollama is running: `ollama serve` - - Check if the model is downloaded: `ollama list` - - Verify the base URL in settings - -2. **Model Not Found** - - Download the model: `ollama pull jais:13b` - - Check model name spelling in settings - -3. **Timeout Errors** - - Increase the timeout setting for complex queries - - Consider using a smaller model if your hardware is limited - -4. **Poor Arabic Analysis** - - Ensure you're using an Arabic-capable model like Jais-13B - - Check that your prompts are properly formatted in Arabic - - Adjust temperature and other parameters for better results - -5. **JSON Parsing Errors** - - Improve the prompt template to emphasize strict JSON formatting - - Implement more robust JSON extraction from LLM responses - -## Performance Optimization - -For production use, consider these optimizations: - -1. **Caching LLM Responses** - - Implement Redis or another caching system for LLM responses - - Cache common analysis patterns to reduce API calls - -2. **Batch Processing** - - For bulk analysis, use batch processing to reduce overhead - -3. **Model Quantization** - - If performance is slow, consider using a quantized version of the model - - Example: `ollama pull jais:13b-q4_0` for a 4-bit quantized version - -4. **Asynchronous Processing** - - For long-running analyses, implement asynchronous processing with Celery - -## Advanced Usage: Fine-tuning for Domain-Specific Analysis - -For improved performance on your specific domain: - -1. Create a dataset of example prompts and expected analyses -2. Use Ollama's fine-tuning capabilities to adapt the model -3. Update your application to use the fine-tuned model - -## Conclusion - -This integration enables your Django AI Analyst to leverage Ollama's powerful language models through LangChain, with specific optimizations for Arabic language support. The fallback to rule-based analysis ensures robustness, while the LLM-based approach provides more natural language understanding capabilities. diff --git a/haikalbot/management/.DS_Store b/haikalbot/management/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..67923fdb6630d8ce17f6b44b63e3b40db9c82e92 GIT binary patch literal 6148 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 M2M2$P*#`&z2kl<{DgXcg literal 0 HcmV?d00001 diff --git a/haikalbot/ollama_model_recommendations.md b/haikalbot/ollama_model_recommendations.md deleted file mode 100644 index 070451dc..00000000 --- a/haikalbot/ollama_model_recommendations.md +++ /dev/null @@ -1,76 +0,0 @@ -# Recommended Ollama Models for Arabic Language Support - -## Top Recommendations - -1. **Jais-13B** (Recommended) - - **Size**: 13 billion parameters - - **Strengths**: Specifically trained on Arabic content, excellent understanding of Arabic context and nuances - - **Command**: `ollama pull jais:13b` - - **Best for**: Production-quality Arabic language understanding and generation - -2. **BLOOM-7B** - - **Size**: 7 billion parameters - - **Strengths**: Trained on 46 languages including Arabic, good multilingual capabilities - - **Command**: `ollama pull bloom:7b` - - **Best for**: Multilingual applications where Arabic is one of several languages - -3. **Mistral-7B-Instruct** - - **Size**: 7 billion parameters - - **Strengths**: Strong general performance, good instruction following, reasonable Arabic support - - **Command**: `ollama pull mistral:7b-instruct` - - **Best for**: General purpose applications with moderate Arabic requirements - -4. **Qwen2-7B** - - **Size**: 7 billion parameters - - **Strengths**: Good multilingual capabilities including Arabic - - **Command**: `ollama pull qwen2:7b` - - **Best for**: Applications requiring both Chinese and Arabic support - -## Comparison Table - -| Model | Size | Arabic Support | Instruction Following | Resource Requirements | Command | -|-------|------|---------------|----------------------|----------------------|---------| -| Jais-13B | 13B | Excellent | Very Good | High (16GB+ RAM) | `ollama pull jais:13b` | -| BLOOM-7B | 7B | Good | Good | Medium (8GB+ RAM) | `ollama pull bloom:7b` | -| Mistral-7B-Instruct | 7B | Moderate | Excellent | Medium (8GB+ RAM) | `ollama pull mistral:7b-instruct` | -| Qwen2-7B | 7B | Good | Very Good | Medium (8GB+ RAM) | `ollama pull qwen2:7b` | - -## Justification for Jais-13B Recommendation - -Jais-13B is specifically recommended for your Django AI Analyst application because: - -1. **Arabic-First Design**: Unlike most models that treat Arabic as one of many languages, Jais was specifically designed for Arabic language understanding and generation. - -2. **Cultural Context**: The model has better understanding of Arabic cultural contexts and nuances, which is important for analyzing domain-specific queries about your data models. - -3. **Technical Terminology**: Better handling of technical terms in Arabic, which is crucial for a model analyzing Django models and database structures. - -4. **Instruction Following**: Good ability to follow complex instructions in Arabic, which is essential for your prompt-based analysis system. - -5. **Performance on Analytical Tasks**: Superior performance on analytical and reasoning tasks in Arabic compared to general multilingual models. - -If your system has limited resources (less than 12GB RAM), Mistral-7B-Instruct would be the next best alternative, offering a good balance between performance and resource requirements. - -## Installation Instructions - -To install the recommended Jais-13B model: - -```bash -ollama pull jais:13b -``` - -For systems with limited resources, install Mistral-7B-Instruct instead: - -```bash -ollama pull mistral:7b-instruct -``` - -After installation, update the `OLLAMA_MODEL` setting in your Django view: - -```python -# For Jais-13B -OLLAMA_MODEL = 'jais:13b' - -# OR for Mistral-7B-Instruct if resources are limited -# OLLAMA_MODEL = 'mistral:7b-instruct' -``` diff --git a/haikalbot/run_haikal_qa.py b/haikalbot/run_haikal_qa.py new file mode 100644 index 00000000..cd026642 --- /dev/null +++ b/haikalbot/run_haikal_qa.py @@ -0,0 +1,19 @@ +from langchain.document_loaders import TextLoader +from langchain.indexes import VectorstoreIndexCreator +from langchain.chat_models import ChatOpenAI +from langchain.chains import RetrievalQA + +# Load YAML doc +loader = TextLoader("haikal_kb.yaml") +index = VectorstoreIndexCreator().from_loaders([loader]) + +# Setup QA chain +qa = RetrievalQA.from_chain_type( + llm=ChatOpenAI(model="gpt-3.5-turbo", temperature=0), + retriever=index.vectorstore.as_retriever() +) + +# Ask a question +query = "How do I add a new invoice?" +response = qa.run(query) +print("Answer:", response) \ No newline at end of file diff --git a/haikalbot/services/analysis_service.py b/haikalbot/services/analysis_service.py deleted file mode 100644 index 10081ef5..00000000 --- a/haikalbot/services/analysis_service.py +++ /dev/null @@ -1,227 +0,0 @@ -import inspect -import hashlib -from django.db import models -from django.db.models import Avg, Sum, Max, Min, ForeignKey, OneToOneField, Count -from django.utils.translation import gettext_lazy as _ -from django.utils import timezone - - -def _localized_keys(language): - return { - 'type': 'نوع' if language == 'ar' else 'type', - 'model': 'النموذج' if language == 'ar' else 'model', - 'count': 'العدد' if language == 'ar' else 'count', - 'filters': 'الفلاتر_المطبقة' if language == 'ar' else 'filters_applied', - 'error': 'خطأ' if language == 'ar' else 'error', - 'chart_type': 'نوع_الرسم_البياني' if language == 'ar' else 'chart_type', - 'labels': 'التسميات' if language == 'ar' else 'labels', - 'data': 'البيانات' if language == 'ar' else 'data', - 'visualization_data': 'بيانات_الرسم_البياني' if language == 'ar' else 'visualization_data', - 'field': 'الحقل' if language == 'ar' else 'field', - 'value': 'القيمة' if language == 'ar' else 'value', - 'statistic_type': 'نوع_الإحصاء' if language == 'ar' else 'statistic_type', - 'results': 'النتائج' if language == 'ar' else 'results', - 'title': 'العنوان' if language == 'ar' else 'title', - } - - -def generate_count_insight(models, query_params, dealer_id=None, language='en'): - keys = _localized_keys(language) - results = [] - - for model in models: - try: - queryset = model.objects.all() - - if dealer_id: - if hasattr(model, 'dealer_id'): - queryset = queryset.filter(dealer_id=dealer_id) - elif hasattr(model, 'dealer'): - queryset = queryset.filter(dealer=dealer_id) - - filters = {} - for key, value in query_params.items(): - if key in ['field', 'operation']: - continue - if hasattr(model, key): - try: - field = model._meta.get_field(key) - if isinstance(field, models.IntegerField): - value = int(value) - elif isinstance(field, models.BooleanField): - value = value.lower() in ('true', '1', 'yes') - except Exception: - pass - filters[key] = value - - if filters: - queryset = queryset.filter(**filters) - - results.append({ - keys['model']: model.__name__, - keys['count']: queryset.count(), - keys['filters']: filters, - }) - except Exception as e: - results.append({ - keys['model']: model.__name__, - keys['error']: str(e), - }) - - return { - keys['type']: keys['type'] + '_analysis', - keys['results']: results, - keys['visualization_data']: { - keys['chart_type']: 'bar', - keys['labels']: [r[keys['model']] for r in results if keys['count'] in r], - keys['data']: [r[keys['count']] for r in results if keys['count'] in r], - } - } - - -def generate_statistics_insight(models, query_params, dealer_id=None, language='en'): - keys = _localized_keys(language) - results = [] - field = query_params.get('field') - operation = query_params.get('operation', 'average') - stat_map = {'average': Avg, 'sum': Sum, 'max': Max, 'min': Min} - - for model in models: - try: - if not field or not hasattr(model, field): - continue - - queryset = model.objects.all() - if dealer_id: - if hasattr(model, 'dealer_id'): - queryset = queryset.filter(dealer_id=dealer_id) - elif hasattr(model, 'dealer'): - queryset = queryset.filter(dealer=dealer_id) - - filters = { - k: v for k, v in query_params.items() - if k not in ['field', 'operation'] and hasattr(model, k) - } - - if filters: - queryset = queryset.filter(**filters) - - value = queryset.aggregate(val=stat_map.get(operation, Count)(field))['val'] - - results.append({ - keys['model']: model.__name__, - keys['field']: field, - keys['statistic_type']: operation, - keys['value']: value, - keys['filters']: filters, - }) - except Exception as e: - results.append({ - keys['model']: model.__name__, - keys['error']: str(e), - }) - - return { - keys['type']: keys['type'] + '_analysis', - keys['results']: results, - keys['visualization_data']: { - keys['chart_type']: 'bar', - keys['labels']: [f"{r[keys['model']]}.{r[keys['field']]}" for r in results if keys['value'] in r], - keys['data']: [r[keys['value']] for r in results if keys['value'] in r], - keys['title']: f"{operation} of {field}" if language != 'ar' else f"{field} ({operation})" - } - } - - -def generate_recommendations(model_classes, analysis_type, language='en'): - recs = [] - for model in model_classes: - for field in model._meta.fields: - if isinstance(field, ForeignKey) and not field.db_index: - msg = f"أضف db_index=True إلى {model.__name__}.{field.name}" if language == 'ar' else f"Add db_index=True to {model.__name__}.{field.name}" - recs.append(msg) - if isinstance(field, models.CharField) and not field.db_index and field.name in ['name', 'title', 'description', 'text']: - msg = f"فكر في فهرسة الحقل النصي {model.__name__}.{field.name}" if language == 'ar' else f"Consider indexing the text field {model.__name__}.{field.name}" - recs.append(msg) - return recs[:5] - - -def generate_model_insight(model, dealer_id=None, language='en'): - keys = _localized_keys(language) - fields_info = [{ - 'name': f.name, - 'type': f.__class__.__name__, - 'null': f.null, - 'blank': f.blank, - 'unique': f.unique, - 'pk': f.primary_key - } for f in model._meta.fields] - - try: - qs = model.objects.all() - if dealer_id: - if hasattr(model, 'dealer'): - qs = qs.filter(dealer_id=dealer_id) - elif hasattr(model, 'dealer'): - qs = qs.filter(dealer=dealer_id) - count = qs.count() - except Exception: - count = "error" - - return { - keys['type']: keys['type'] + '_analysis', - keys['model']: model.__name__, - 'fields': fields_info, - 'count': count - } - - -def generate_relationship_insight(models, query_params=None, dealer_id=None, language='en'): - from_ = "من" if language == 'ar' else "from" - to_ = "إلى" if language == 'ar' else "to" - rel_type = "نوع" if language == 'ar' else "type" - relationships = [] - - for model in models: - for field in model._meta.fields: - if isinstance(field, (ForeignKey, OneToOneField)): - relationships.append({ - from_: model.__name__, - to_: field.related_model.__name__, - rel_type: field.__class__.__name__, - }) - for field in model._meta.many_to_many: - relationships.append({ - from_: model.__name__, - to_: field.related_model.__name__, - rel_type: 'ManyToManyField' - }) - - return { - 'type': 'تحليل_العلاقات' if language == 'ar' else 'relationship_analysis', - 'relationships': relationships - } - - -def generate_performance_insight(models, query_params=None, dealer_id=None, language='en'): - issues = [] - - for model in models: - for field in model._meta.fields: - if isinstance(field, ForeignKey) and not field.db_index: - issues.append({ - # 'model': model.__name__, - 'field': field.name, - 'issue': 'Missing index on ForeignKey' - }) - # if isinstance(field, models.CharField) and not field.db_index and field.name in ['name', 'title']: - # issues.append({ - # 'model': model.__name__, - # 'field': field.name, - # 'issue': 'Unindexed CharField used in filtering' - # }) - - return { - 'type': 'تحليل_الأداء' if language == 'ar' else 'performance_analysis', - 'issues': issues - } \ No newline at end of file diff --git a/haikalbot/services/cache_service.py b/haikalbot/services/cache_service.py deleted file mode 100644 index 6c80f725..00000000 --- a/haikalbot/services/cache_service.py +++ /dev/null @@ -1,61 +0,0 @@ -import hashlib -import logging -from django.utils import timezone -from django.db import models -from ..models import AnalysisCache - -logger = logging.getLogger(__name__) - - -class CacheService: - def generate_hash(self, prompt, dealer_id, language): - """ - Generate a unique MD5 hash based on the prompt, dealer ID, and language. - """ - key = f"{prompt}:{dealer_id or 'all'}:{language}" - return hashlib.md5(key.encode()).hexdigest() - - def get_cached_result(self, prompt_hash, user, dealer_id): - """ - Retrieve a cached analysis result based on hash, dealer, and optionally user. - """ - try: - # Check for user-specific cache if authenticated - if user and user.is_authenticated: - user_cache = AnalysisCache.objects.filter( - prompt_hash=prompt_hash, - user=user, - expires_at__gt=timezone.now() - ).first() - if user_cache: - return user_cache.result - - # Otherwise check for dealer-wide cache - dealer_cache = AnalysisCache.objects.filter( - prompt_hash=prompt_hash, - dealer_id=dealer_id, - expires_at__gt=timezone.now() - ).first() - - return dealer_cache.result if dealer_cache else None - except Exception as e: - logger.warning(f"Cache retrieval failed: {str(e)}") - return None - - def cache_result(self, prompt_hash, result, user, dealer_id, duration=3600): - """ - Save or update a cached result with an expiration timestamp. - """ - try: - expires_at = timezone.now() + timezone.timedelta(seconds=duration) - AnalysisCache.objects.update_or_create( - prompt_hash=prompt_hash, - user=user if user and user.is_authenticated else None, - dealer_id=dealer_id, - defaults={ - 'result': result, - 'expires_at': expires_at - } - ) - except Exception as e: - logger.warning(f"Cache saving failed: {str(e)}") \ No newline at end of file diff --git a/haikalbot/services/llm_service.py b/haikalbot/services/llm_service.py deleted file mode 100644 index 32109991..00000000 --- a/haikalbot/services/llm_service.py +++ /dev/null @@ -1,150 +0,0 @@ -import json -import logging -from django.apps import apps -from django.http import JsonResponse -from django.db.models import Count, Avg, Max, Min -from langchain_ollama import OllamaLLM -from langchain.chains import LLMChain -from langchain.prompts import PromptTemplate -from django.conf import settings - -logger = logging.getLogger(__name__) - - -def get_llm_instance(): - try: - base_url = getattr(settings, 'OLLAMA_BASE_URL', 'http://10.10.1.132:11434') - model = getattr(settings, 'OLLAMA_MODEL', 'qwen3:8b') - temperature = getattr(settings, 'OLLAMA_TEMPERATURE', 0.2) - top_p = getattr(settings, 'OLLAMA_TOP_P', 0.8) - top_k = getattr(settings, 'OLLAMA_TOP_K', 40) - num_ctx = getattr(settings, 'OLLAMA_NUM_CTX', 4096) - num_predict = getattr(settings, 'OLLAMA_NUM_PREDICT', 2048) - - return OllamaLLM( - base_url=base_url, - model=model, - temperature=temperature, - top_p=top_p, - top_k=top_k, - num_ctx=num_ctx, - num_predict=num_predict, - stop=["```", ""], - repeat_penalty=1.1, - ) - except Exception as e: - logger.error(f"Error initializing Ollama LLM: {str(e)}") - return None - - -def get_llm_chain(language='en'): - llm = get_llm_instance() - if not llm: - return None - - if language == 'ar': - template = """ - قم بتحليل الاستعلام التالي وتحديد نوع التحليل المطلوب ونماذج البيانات المستهدفة وأي معلمات استعلام. - - الاستعلام: {prompt} - - قم بتقديم إجابتك بتنسيق JSON كما يلي: - {{ - "analysis_type": "count" أو "relationship" أو "performance" أو "statistics" أو "general", - "target_models": ["ModelName1", "ModelName2"], - "query_params": {{"field1": "value1", "field2": "value2"}} - }} - """ - else: - template = """ - Analyze the following prompt and determine the type of analysis required, target data models, and any query parameters. - - Prompt: {prompt} - - Provide your answer in JSON format as follows: - { - "analysis_type": "count" or "relationship" or "performance" or "statistics" or "general", - "target_models": ["ModelName1", "ModelName2"], - "query_params": {"field1": "value1", "field2": "value2"} - } - """ - - prompt_template = PromptTemplate( - input_variables=["prompt"], - template=template - ) - - return prompt_template | llm - - -def analyze_models_with_orm(analysis_type, target_models, query_params): - results = {} - - for model_name in target_models: - try: - model = apps.get_model('your_app_name', model_name) - except LookupError: - results[model_name] = {"error": f"Model '{model_name}' not found"} - continue - - try: - queryset = model.objects.filter(**query_params) - - if analysis_type == 'count': - results[model_name] = {'count': queryset.count()} - - elif analysis_type == 'statistics': - numeric_fields = [f.name for f in model._meta.fields if f.get_internal_type() in ['IntegerField', 'FloatField', 'DecimalField']] - stats = {} - for field in numeric_fields: - stats[field] = { - 'avg': queryset.aggregate(avg=Avg(field))['avg'], - 'max': queryset.aggregate(max=Max(field))['max'], - 'min': queryset.aggregate(min=Min(field))['min'] - } - results[model_name] = stats - - elif analysis_type == 'relationship': - related = {} - for field in model._meta.get_fields(): - if field.is_relation and field.many_to_one: - related[field.name] = queryset.values(field.name).annotate(count=Count(field.name)).count() - results[model_name] = related - - elif analysis_type == 'performance': - results[model_name] = {'note': 'Performance analysis logic not implemented.'} - - else: - results[model_name] = list(queryset.values()) - - except Exception as e: - results[model_name] = {'error': str(e)} - - return results - - -def analyze_prompt_and_return_json(request): - try: - prompt = request.POST.get('prompt') - language = request.POST.get('language', 'en') - - chain = get_llm_chain(language) - if not chain: - return JsonResponse({'success': False, 'error': 'LLM not initialized'}) - - result = chain.invoke({'prompt': prompt}) - parsed = json.loads(result) - - analysis_type = parsed.get('analysis_type') - target_models = parsed.get('target_models', []) - query_params = parsed.get('query_params', {}) - - if not analysis_type or not target_models: - return JsonResponse({'success': False, 'error': 'Incomplete analysis instruction returned by LLM'}) - - orm_results = analyze_models_with_orm(analysis_type, target_models, query_params) - - return JsonResponse({'success': True, 'data': orm_results}) - - except Exception as e: - return JsonResponse({'success': False, 'error': str(e)}) \ No newline at end of file diff --git a/haikalbot/temp.txt b/haikalbot/temp.txt deleted file mode 100644 index ba64182e..00000000 --- a/haikalbot/temp.txt +++ /dev/null @@ -1,80 +0,0 @@ -from langchain_ollama import OllamaLLM - -from langchain.chains import LLMChain -from langchain.prompts import PromptTemplate -from django.conf import settings -import logging - -logger = logging.getLogger(__name__) - - -def get_ollama_llm(): - """ - Initialize and return an Ollama LLM instance configured for Arabic support. - """ - try: - # Get settings from Django settings or use defaults - base_url = getattr(settings, 'OLLAMA_BASE_URL', 'http://localhost:11434') - model = getattr(settings, 'OLLAMA_MODEL', 'qwen3:8b') - # timeout = getattr(settings, 'OLLAMA_TIMEOUT', 120) - - return OllamaLLM( - base_url=base_url, - model=model, - temperature= 0.2, - top_p= 0.8, - top_k= 40, - num_ctx= 4096, - num_predict= 2048, - stop= ["```", ""], - repeat_penalty= 1.1, - ) - except Exception as e: - logger.error(f"Error initializing Ollama LLM: {str(e)}") - return None - - -def create_prompt_analyzer_chain(language='ar'): - """ - Create a LangChain for analyzing prompts in Arabic or English. - """ - llm = get_ollama_llm() - if not llm: - return None - - # Define the prompt template based on language - if language == 'ar': - template = """ - قم بتحليل الاستعلام التالي وتحديد نوع التحليل المطلوب ونماذج البيانات المستهدفة وأي معلمات استعلام. - - الاستعلام: {prompt} - - قم بتقديم إجابتك بتنسيق JSON كما يلي: - {{ - "analysis_type": "count" أو "relationship" أو "performance" أو "statistics" أو "general", - "target_models": ["ModelName1", "ModelName2"], - "query_params": {{"field1": "value1", "field2": "value2"}} - }} - """ - else: - template = """ - Analyze the following prompt and determine the type of analysis required, target data models, and any query parameters. - - Prompt: {prompt} - - Provide your answer in JSON format as follows: - { - "analysis_type": "count" or "relationship" or "performance" or "statistics" or "general", - "target_models": ["ModelName1", "ModelName2"], - "query_params": {"field1": "value1", "field2": "value2"} - } - """ - - # Create the prompt template - prompt_template = PromptTemplate( - input_variables=["prompt"], - template=template - ) - - # Create and return the LLM chain - return prompt_template | llm \ No newline at end of file diff --git a/haikalbot/training_prompt.md b/haikalbot/training_prompt.md deleted file mode 100644 index ee381661..00000000 --- a/haikalbot/training_prompt.md +++ /dev/null @@ -1,161 +0,0 @@ -# Training Prompt for Django Model Analyst AI Agent - -## Agent Purpose -You are a specialized AI agent designed to analyze Django models and provide insightful information to users. Your primary function is to interpret Django model structures, relationships, and metadata to generate meaningful insights that help developers and stakeholders understand their data models better. - -## Core Capabilities -1. Parse and understand Django model definitions -2. Identify relationships between models (ForeignKey, ManyToMany, OneToOne) -3. Analyze model fields, types, constraints, and metadata -4. Generate statistics and insights about model usage and structure -5. Provide recommendations for model optimization -6. Respond to natural language queries about models -7. Format responses as structured JSON for integration with frontend applications - -## Input Processing -You will receive inputs in the following format: -1. Django model code or references to model files -2. A natural language prompt specifying the type of analysis or insights requested -3. Optional context about the project or specific concerns - -## Output Requirements -Your responses must: -1. Be formatted as valid JSON -2. Include a "status" field indicating success or failure -3. Provide an "insights" array containing the requested analysis -4. Include metadata about the analysis performed -5. Be structured in a way that's easy to parse and display in a frontend - -## Analysis Types -You should be able to perform the following types of analysis: - -### Structural Analysis -- Model count and complexity metrics -- Field type distribution -- Relationship mapping and visualization data -- Inheritance patterns -- Abstract models usage - -### Performance Analysis -- Potential query bottlenecks -- Missing index recommendations -- Relationship optimization suggestions -- N+1 query vulnerability detection - -### Security Analysis -- Sensitive field detection -- Permission model recommendations -- Data exposure risk assessment - -### Data Integrity Analysis -- Constraint analysis -- Validation rule assessment -- Data consistency recommendations - -## Example Interactions - -### Example 1: Basic Model Analysis -**Input Prompt:** -"Analyze the User and Profile models and show me their relationship structure." - -**Expected Response:** -```json -{ - "status": "success", - "request_id": "a1b2c3d4", - "timestamp": "2025-05-25T23:21:56Z", - "insights": [ - { - "type": "relationship_analysis", - "models": ["User", "Profile"], - "relationships": [ - { - "from": "Profile", - "to": "User", - "type": "OneToOne", - "field": "user", - "related_name": "profile", - "on_delete": "CASCADE" - } - ], - "visualization_data": { - "nodes": [...], - "edges": [...] - } - } - ], - "recommendations": [ - "Consider adding an index to Profile.user for faster lookups" - ] -} -``` - -### Example 2: Query Performance Analysis -**Input Prompt:** -"Identify potential performance issues in the Order and OrderItem models." - -**Expected Response:** -```json -{ - "status": "success", - "request_id": "e5f6g7h8", - "timestamp": "2025-05-25T23:22:30Z", - "insights": [ - { - "type": "performance_analysis", - "models": ["Order", "OrderItem"], - "issues": [ - { - "severity": "high", - "model": "OrderItem", - "field": "order", - "issue": "Missing database index on ForeignKey", - "impact": "Slow queries when filtering OrderItems by Order", - "solution": "Add db_index=True to order field" - }, - { - "severity": "medium", - "model": "Order", - "issue": "No select_related in common queries", - "impact": "Potential N+1 query problems", - "solution": "Use select_related when querying Orders with OrderItems" - } - ] - } - ], - "code_suggestions": [ - { - "model": "OrderItem", - "current": "order = models.ForeignKey(Order, on_delete=models.CASCADE)", - "suggested": "order = models.ForeignKey(Order, on_delete=models.CASCADE, db_index=True)" - } - ] -} -``` - -## Limitations and Boundaries -1. You should not modify or execute code unless explicitly requested -2. You should indicate when you need additional information to provide accurate insights -3. You should acknowledge when a requested analysis is beyond your capabilities -4. You should not make assumptions about implementation details not present in the provided models -5. You should clearly distinguish between factual observations and recommendations - -## Learning and Improvement -You should continuously improve your analysis capabilities by: -1. Learning from user feedback -2. Staying updated on Django best practices -3. Expanding your understanding of common model patterns -4. Refining your insight generation to be more relevant and actionable - -## Ethical Considerations -1. Respect data privacy by not suggesting exposing sensitive information -2. Provide balanced recommendations that consider security, performance, and usability -3. Be transparent about the limitations of your analysis -4. Avoid making judgments about the quality of code beyond objective metrics - -## Technical Integration -You will be integrated into a Django application as a service that: -1. Receives requests through a REST API -2. Has access to model definitions through Django's introspection capabilities -3. Returns JSON responses that can be directly used by frontend components -4. Maintains context across multiple related queries when session information is provided diff --git a/haikalbot/training_prompt_arabic.md b/haikalbot/training_prompt_arabic.md deleted file mode 100644 index 2d32829b..00000000 --- a/haikalbot/training_prompt_arabic.md +++ /dev/null @@ -1,161 +0,0 @@ -# تدريب وكيل محلل نماذج Django بالعربية - -## هدف الوكيل -أنت وكيل ذكاء اصطناعي متخصص مصمم لتحليل نماذج Django وتقديم معلومات مفيدة للمستخدمين. وظيفتك الأساسية هي تفسير هياكل نماذج Django والعلاقات والبيانات الوصفية لتوليد رؤى ذات معنى تساعد المطورين وأصحاب المصلحة على فهم نماذج البيانات الخاصة بهم بشكل أفضل. - -## القدرات الأساسية -1. تحليل وفهم تعريفات نماذج Django -2. تحديد العلاقات بين النماذج (ForeignKey, ManyToMany, OneToOne) -3. تحليل حقول النموذج وأنواعها والقيود والبيانات الوصفية -4. توليد إحصائيات ورؤى حول استخدام النموذج وهيكله -5. تقديم توصيات لتحسين النموذج -6. الاستجابة للاستعلامات باللغة الطبيعية حول النماذج -7. تنسيق الردود كـ JSON منظم للتكامل مع تطبيقات الواجهة الأمامية - -## معالجة المدخلات -ستتلقى المدخلات بالتنسيق التالي: -1. كود نموذج Django أو مراجع لملفات النموذج -2. استعلام باللغة الطبيعية يحدد نوع التحليل أو الرؤى المطلوبة -3. سياق اختياري حول المشروع أو مخاوف محددة - -## متطلبات المخرجات -يجب أن تكون ردودك: -1. منسقة كـ JSON صالح -2. تتضمن حقل "status" يشير إلى النجاح أو الفشل -3. توفر مصفوفة "insights" تحتوي على التحليل المطلوب -4. تتضمن بيانات وصفية حول التحليل الذي تم إجراؤه -5. منظمة بطريقة يسهل تحليلها وعرضها في واجهة أمامية - -## أنواع التحليل -يجب أن تكون قادرًا على إجراء الأنواع التالية من التحليل: - -### التحليل الهيكلي -- عدد النماذج ومقاييس التعقيد -- توزيع أنواع الحقول -- رسم خرائط العلاقات وبيانات التصور -- أنماط الوراثة -- استخدام النماذج المجردة - -### تحليل الأداء -- اختناقات الاستعلام المحتملة -- توصيات الفهرس المفقود -- اقتراحات تحسين العلاقة -- كشف ضعف استعلام N+1 - -### تحليل الأمان -- كشف الحقول الحساسة -- توصيات نموذج الإذن -- تقييم مخاطر التعرض للبيانات - -### تحليل سلامة البيانات -- تحليل القيود -- تقييم قواعد التحقق -- توصيات اتساق البيانات - -## أمثلة على التفاعلات - -### مثال 1: تحليل النموذج الأساسي -**استعلام المدخلات:** -"قم بتحليل نماذج المستخدم والملف الشخصي وأظهر لي هيكل العلاقة بينهما." - -**الرد المتوقع:** -```json -{ - "status": "نجاح", - "request_id": "a1b2c3d4", - "timestamp": "2025-05-25T23:21:56Z", - "insights": [ - { - "type": "تحليل_العلاقات", - "models": ["User", "Profile"], - "relationships": [ - { - "from": "Profile", - "to": "User", - "type": "OneToOne", - "field": "user", - "related_name": "profile", - "on_delete": "CASCADE" - } - ], - "visualization_data": { - "nodes": [...], - "edges": [...] - } - } - ], - "recommendations": [ - "فكر في إضافة فهرس إلى Profile.user للبحث الأسرع" - ] -} -``` - -### مثال 2: تحليل أداء الاستعلام -**استعلام المدخلات:** -"حدد مشاكل الأداء المحتملة في نماذج الطلب وعناصر الطلب." - -**الرد المتوقع:** -```json -{ - "status": "نجاح", - "request_id": "e5f6g7h8", - "timestamp": "2025-05-25T23:22:30Z", - "insights": [ - { - "type": "تحليل_الأداء", - "models": ["Order", "OrderItem"], - "issues": [ - { - "severity": "عالية", - "model": "OrderItem", - "field": "order", - "issue": "فهرس قاعدة بيانات مفقود على ForeignKey", - "impact": "استعلامات بطيئة عند تصفية OrderItems حسب Order", - "solution": "أضف db_index=True إلى حقل order" - }, - { - "severity": "متوسطة", - "model": "Order", - "issue": "لا يوجد select_related في الاستعلامات الشائعة", - "impact": "مشاكل استعلام N+1 محتملة", - "solution": "استخدم select_related عند الاستعلام عن Orders مع OrderItems" - } - ] - } - ], - "code_suggestions": [ - { - "model": "OrderItem", - "current": "order = models.ForeignKey(Order, on_delete=models.CASCADE)", - "suggested": "order = models.ForeignKey(Order, on_delete=models.CASCADE, db_index=True)" - } - ] -} -``` - -## القيود والحدود -1. لا يجب عليك تعديل أو تنفيذ التعليمات البرمجية ما لم يُطلب منك ذلك صراحةً -2. يجب أن تشير عندما تحتاج إلى معلومات إضافية لتقديم رؤى دقيقة -3. يجب أن تعترف عندما يكون التحليل المطلوب خارج قدراتك -4. لا يجب أن تفترض تفاصيل التنفيذ غير الموجودة في النماذج المقدمة -5. يجب أن تميز بوضوح بين الملاحظات الواقعية والتوصيات - -## التعلم والتحسين -يجب أن تحسن باستمرار قدرات التحليل الخاصة بك من خلال: -1. التعلم من تعليقات المستخدم -2. البقاء على اطلاع بأفضل ممارسات Django -3. توسيع فهمك لأنماط النموذج الشائعة -4. تحسين توليد الرؤى لتكون أكثر صلة وقابلية للتنفيذ - -## الاعتبارات الأخلاقية -1. احترام خصوصية البيانات من خلال عدم اقتراح كشف المعلومات الحساسة -2. تقديم توصيات متوازنة تراعي الأمان والأداء وسهولة الاستخدام -3. الشفافية بشأن حدود تحليلك -4. تجنب إصدار أحكام حول جودة الكود بما يتجاوز المقاييس الموضوعية - -## التكامل التقني -سيتم دمجك في تطبيق Django كخدمة: -1. تتلقى الطلبات من خلال واجهة برمجة تطبيقات REST -2. لديها إمكانية الوصول إلى تعريفات النموذج من خلال قدرات التفتيش الذاتي لـ Django -3. تعيد استجابات JSON التي يمكن استخدامها مباشرة بواسطة مكونات الواجهة الأمامية -4. تحافظ على السياق عبر استعلامات متعددة ذات صلة عند توفير معلومات الجلسة diff --git a/haikalbot/urls.py b/haikalbot/urls.py index ddd4713a..fc75cfe1 100644 --- a/haikalbot/urls.py +++ b/haikalbot/urls.py @@ -1,8 +1,8 @@ from django.urls import path from . import views -app_name = "haikalbot" +# app_name = "haikalbot" urlpatterns = [ - path("analyze/", views.ModelAnalystView.as_view(), name="haikalbot"), + path("bot/", views.HaikalBot.as_view(), name="haikalbot"), ] diff --git a/haikalbot/utils/export.py b/haikalbot/utils/export.py new file mode 100644 index 00000000..f2701671 --- /dev/null +++ b/haikalbot/utils/export.py @@ -0,0 +1,66 @@ +from django.http import HttpResponse +import pandas as pd +from io import BytesIO, StringIO + + +def export_to_excel(self, data, filename): + """ + Export data to Excel format. + + Args: + data: Data to export + filename: Base filename without extension + + Returns: + HttpResponse: Response with Excel file + """ + + + # Convert data to DataFrame + df = pd.DataFrame(data) + + # Create Excel file in memory + excel_file = BytesIO() + with pd.ExcelWriter(excel_file, engine='xlsxwriter') as writer: + df.to_excel(writer, sheet_name='Model Analysis', index=False) + + # Auto-adjust columns width + worksheet = writer.sheets['Model Analysis'] + for i, col in enumerate(df.columns): + max_width = max(df[col].astype(str).map(len).max(), len(col)) + 2 + worksheet.set_column(i, i, max_width) + + # Set up response + excel_file.seek(0) + response = HttpResponse( + excel_file.read(), + content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ) + response['Content-Disposition'] = f'attachment; filename="{filename}.xlsx"' + return response + + +def export_to_csv(self, data, filename): + """ + Export data to CSV format. + + Args: + data: Data to export + filename: Base filename without extension + + Returns: + HttpResponse: Response with CSV file + """ + + + # Convert data to DataFrame + df = pd.DataFrame(data) + + # Create CSV file in memory + csv_file = StringIO() + df.to_csv(csv_file, index=False) + + # Set up response + response = HttpResponse(csv_file.getvalue(), content_type='text/csv') + response['Content-Disposition'] = f'attachment; filename="{filename}.csv"' + return response diff --git a/haikalbot/views.py b/haikalbot/views.py index b685bbf8..2bebda15 100644 --- a/haikalbot/views.py +++ b/haikalbot/views.py @@ -1,253 +1,63 @@ from django.contrib.auth.mixins import LoginRequiredMixin -from django.shortcuts import render -from django.views.decorators.csrf import csrf_exempt -from django.utils.decorators import method_decorator -from django.views import View from django.http import JsonResponse -from django.apps import apps -from django.db import models -from django.conf import settings -from django.utils import timezone -from datetime import timedelta -import json -import hashlib +from django.shortcuts import render +from django.utils.translation import gettext as _ +from django.views import View import logging -import uuid -import re -from inventory import models as inventory_models -from inventory.utils import get_user_type -from .models import AnalysisCache -from .services.llm_service import get_llm_chain -from .services.analysis_service import ( - generate_model_insight, - generate_count_insight, - generate_relationship_insight, - generate_performance_insight, - generate_statistics_insight, - generate_recommendations -) -from .services.cache_service import CacheService -from .utils.response_formatter import format_response +from .ai_agent import analyze_prompt +from .utils.export import export_to_excel, export_to_csv logger = logging.getLogger(__name__) -@method_decorator(csrf_exempt, name='dispatch') -class ModelAnalystView(View): - """ - View for handling model analysis requests and rendering the chatbot interface. - - This view provides both GET and POST methods: - - GET: Renders the chatbot interface - - POST: Processes analysis requests and returns JSON responses - - The view includes caching, permission checking, and multilingual support. - """ - # Configuration settings (can be moved to Django settings) - CACHE_DURATION = getattr(settings, 'ANALYSIS_CACHE_DURATION', 3600) - DEFAULT_LANGUAGE = getattr(settings, 'DEFAULT_LANGUAGE', 'en') - +class HaikalBot(LoginRequiredMixin, View): def get(self, request, *args, **kwargs): """ - Render the chatbot interface. - - :param request: The HTTP request - :return: Rendered chatbot.html template + Render the chat interface. """ context = { - 'dark_mode': request.session.get('dark_mode', False) + 'dark_mode': request.session.get('dark_mode', False), + 'page_title': _('AI Assistant') } - return render(request, "haikalbot/chatbot.html", context) + return render(request, "haikalbot/chat.html", context) def post(self, request, *args, **kwargs): """ - Process analysis requests and return JSON responses. - - :param request: The HTTP request containing the prompt - :return: JsonResponse with analysis results + Process the prompt and return results. """ + prompt = request.POST.get("prompt") + export = request.POST.get("export") + language = request.POST.get("language", request.LANGUAGE_CODE) + + if not prompt: + error_msg = _("Prompt is required.") if language != "ar" else "الاستعلام مطلوب." + return JsonResponse({"status": "error", "error": error_msg}, status=400) + try: - # Parse request data - data = json.loads(request.body) - prompt = data.get('prompt') - language = data.get('language', self.DEFAULT_LANGUAGE) - dealer = get_user_type(request) + result = analyze_prompt(prompt) - # Validate request - if not prompt: - error_msg = "الاستعلام مطلوب" if language == 'ar' else "Prompt is required" - return self._error_response(error_msg, 400) + # Handle export requests if data is available + if export and result.get("status") == "success" and result.get("data"): + try: + if export == "excel": + return export_to_excel(result["data"]) + elif export == "csv": + return export_to_csv(result["data"]) + except Exception as e: + logger.error(f"Export error: {e}") + result["export_error"] = str(e) - if not self._check_permissions(dealer.id): - error_msg = "تم رفض الإذن" if language == 'ar' else "Permission denied" - return self._error_response(error_msg, 403) + return JsonResponse(result, safe=False) - # Check cache - cache_service = CacheService() - prompt_hash = cache_service.generate_hash(prompt, dealer.id, language) - cached_result = cache_service.get_cached_result(prompt_hash, request.user, dealer.id) - - if cached_result: - return JsonResponse(cached_result) - - # Process prompt and generate insights - insights = self._process_prompt(prompt, dealer, language) - - # Cache results - cache_service.cache_result( - prompt_hash, - insights, - request.user, - dealer.id, - self.CACHE_DURATION - ) - - return JsonResponse(insights) - - except json.JSONDecodeError: - error_msg = "بيانات JSON غير صالحة في نص الطلب" if language == 'ar' else "Invalid JSON in request body" - return self._error_response(error_msg, 400) except Exception as e: - logger.exception("Error processing model analysis request") - error_msg = f"حدث خطأ: {str(e)}" if language == 'ar' else f"An error occurred: {str(e)}" - return self._error_response(error_msg, 500) + logger.exception(f"Error processing prompt: {e}") + error_msg = _("An error occurred while processing your request.") + if language == "ar": + error_msg = "حدث خطأ أثناء معالجة طلبك." - def _error_response(self, message, status): - """ - Create a standardized error response. - - :param message: Error message - :param status: HTTP status code - :return: JsonResponse with error details - """ - return JsonResponse({"status": "error", "message": message}, status=status) - - def _check_permissions(self, dealer_id): - """ - Check if the dealer has permissions to access the analysis. - - :param dealer_id: ID of the dealer - :return: True if dealer has permissions, False otherwise - """ - try: - return inventory_models.Dealer.objects.filter(id=dealer_id).exists() - except Exception: - logger.exception("Error checking permissions") - return False - - def _process_prompt(self, prompt, dealer, language): - """ - Process the prompt and generate insights. - - :param prompt: User's prompt text - :param dealer: Dealer object - :param language: Language code (e.g., 'en', 'ar') - :return: Dictionary with analysis results - """ - # Initialize response structure - response = format_response( - prompt=prompt, - language=language, - request_id=str(uuid.uuid4()), - timestamp=timezone.now().isoformat() - ) - - # Get LLM chain for prompt analysis - chain = get_llm_chain(language=language) - - # Parse prompt using LLM - if chain: - try: - result = chain.invoke({"prompt": prompt}) - json_match = re.search(r'({.*})', result.replace('\n', ' '), re.DOTALL) - result = json.loads(json_match.group(1)) if json_match else {} - except Exception as e: - logger.error(f"LLM error fallback: {e}") - result = {} - else: - result = {} - - # Extract analysis parameters - analysis_type = result.get('analysis_type', 'general') - target_models = result.get('target_models', []) - query_params = result.get('query_params', {}) - - # Get models to analyze - all_models = list(apps.get_models()) - models_to_analyze = self._filter_models(all_models, target_models) - if dealer: - models_to_analyze = self._filter_by_dealer(models_to_analyze, dealer.id) - - # Select analysis method based on type - analysis_method = { - 'count': generate_count_insight, - 'relationship': generate_relationship_insight, - 'performance': generate_performance_insight, - 'statistics': generate_statistics_insight - }.get(analysis_type, self._generate_model_insight_all) - - # Generate insights - insights = analysis_method(models_to_analyze, query_params, dealer.id if dealer else None, language) - - # Add insights to response - insights_key = "التحليلات" if language == 'ar' else "insights" - if isinstance(insights, list): - response[insights_key].extend(insights) - else: - response[insights_key].append(insights) - - # Generate recommendations - recommendations = generate_recommendations(models_to_analyze, analysis_type, language) - if recommendations: - recs_key = "التوصيات" if language == 'ar' else "recommendations" - response[recs_key] = recommendations - - # Add plain text summary for response - summary_lines = [] - for insight in response[insights_key]: - if isinstance(insight, dict): - summary_lines.append(insight.get('type', 'Insight')) - else: - summary_lines.append(str(insight)) - - response['response'] = "\n".join(summary_lines) - - return response - - def _filter_models(self, all_models, target_models): - """ - Filter models based on target model names. - - :param all_models: List of all available models - :param target_models: List of target model names - :return: Filtered list of models - """ - if not target_models: - return all_models - return [m for m in all_models if m.__name__ in target_models or - m.__name__.lower() in [t.lower() for t in target_models]] - - def _filter_by_dealer(self, models, dealer_id): - """ - Filter models that are relevant to the dealer. - - :param models: List of models - :param dealer_id: ID of the dealer - :return: Filtered list of models - """ - dealer_models = [m for m in models if any(f.name in ('dealer', 'dealer_id') - for f in m._meta.fields)] - return dealer_models if dealer_models else models - - def _generate_model_insight_all(self, models, query_params, dealer_id, language): - """ - Generate insights for all models. - - :param models: List of models - :param query_params: Query parameters - :param dealer_id: ID of the dealer - :param language: Language code - :return: List of insights - """ - return [generate_model_insight(m, dealer_id, language) for m in models] + return JsonResponse({ + "status": "error", + "error": error_msg, + "details": str(e) if request.user.is_staff else None + }, status=500) diff --git a/inventory/services.py b/inventory/services.py index 9124f968..262b7327 100644 --- a/inventory/services.py +++ b/inventory/services.py @@ -6,7 +6,7 @@ import requests import json -from pyvin import VIN +from vin import VIN from django.conf import settings from .models import CarMake @@ -111,9 +111,9 @@ def decode_vin(vin): data = {} if v: data = { - "maker": v.Make, - "model": v.Model, - "modelYear": v.ModelYear, + "maker": v.make, + "model": v.model, + "modelYear": v.model_year, } return data if all([x for x in data.values()]) else None diff --git a/inventory/utils.py b/inventory/utils.py index e966838e..9bccf5bc 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -1028,8 +1028,7 @@ class CarFinanceCalculator: def calculate_totals(self): total_price = sum( - Decimal(self._get_nested_value(item, self.CAR_FINANCE_KEY, 'selling_price')) * - int(self._get_quantity(item)) + Decimal(self._get_nested_value(item, self.CAR_FINANCE_KEY, 'selling_price')) * int(self._get_quantity(item)) for item in self.item_transactions ) total_additionals = sum(Decimal(x.get('price_')) for x in self._get_additional_services()) @@ -1068,7 +1067,6 @@ class CarFinanceCalculator: - def get_item_transactions(txs): """ Extracts and compiles relevant transaction details from a list of transactions, diff --git a/inventory/views.py b/inventory/views.py index 9ddade51..35dabf24 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -658,40 +658,40 @@ class AjaxHandlerView(LoginRequiredMixin, View): vin_no = vin_no.strip() vin_data = {} decoding_method = "" + if result := decodevin(vin_no): + manufacturer_name, model_name, year_model = result.values() + car_make = get_make(manufacturer_name) + car_model = get_model(model_name, car_make) + + logger.info( + f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}" + ) + + if not car_make: + return JsonResponse( + { + "success": False, + "error": _("Manufacturer not found in the database"), + }, + status=404, + ) + vin_data["make_id"] = car_make.id_car_make + vin_data["name"] = car_make.name + vin_data["arabic_name"] = car_make.arabic_name + + if not car_model: + vin_data["model_id"] = "" + else: + vin_data["model_id"] = car_model.id_car_model + vin_data["year"] = year_model + return JsonResponse({"success": True, "data": vin_data}) + # manufacturer_name = model_name = year_model = None - if not (result := decodevin(vin_no)): - return JsonResponse( - {"success": False, "error": _("VIN not found in all sources")}, - status=404, - ) - - manufacturer_name, model_name, year_model = result.values() - car_make = get_make(manufacturer_name) - car_model = get_model(model_name, car_make) - - logger.info( - f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}" + return JsonResponse( + {"success": False, "error": _("VIN not found in all sources")}, + status=404, ) - if not car_make: - return JsonResponse( - { - "success": False, - "error": _("Manufacturer not found in the database"), - }, - status=404, - ) - vin_data["make_id"] = car_make.id_car_make - vin_data["name"] = car_make.name - vin_data["arabic_name"] = car_make.arabic_name - - if not car_model: - vin_data["model_id"] = "" - else: - vin_data["model_id"] = car_model.id_car_model - vin_data["year"] = year_model - return JsonResponse({"success": True, "data": vin_data}) - def get_models(self, request): make_id = request.GET.get("make_id") car_models = ( @@ -7896,6 +7896,7 @@ def submit_plan(request): def payment_callback(request): + message = request.GET.get("message") dealer = get_user_type(request) payment_id = request.GET.get("id") history = models.PaymentHistory.objects.filter(transaction_id=payment_id).first() @@ -7933,7 +7934,7 @@ def payment_callback(request): elif payment_status == "failed": history.status = "failed" history.save() - message = request.GET.get("message") + return render(request, "payment_failed.html", {"message": message}) diff --git a/locale/ar/LC_MESSAGES/django.mo b/locale/ar/LC_MESSAGES/django.mo index 902a838e184d556a86b733575e845144fd7a364f..27769f18c29983235cb8ae835caf268084d9c82d 100644 GIT binary patch delta 58336 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%0As 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 V23rUwCtKzu`9C2qrv3l` delta 56737 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 diff --git a/locale/ar/LC_MESSAGES/django.po b/locale/ar/LC_MESSAGES/django.po index 210f2d5f..644c4794 100644 --- a/locale/ar/LC_MESSAGES/django.po +++ b/locale/ar/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-05-26 15:53+0300\n" +"POT-Creation-Date: 2025-05-29 17:51+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -20,18 +20,18 @@ msgstr "" "&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" #: api/models.py:6 inventory/models.py:468 inventory/tables.py:51 -#: templates/inventory/car_detail.html:76 templates/inventory/car_form.html:48 +#: templates/inventory/car_detail.html:77 templates/inventory/car_form.html:49 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:30 #: templates/inventory/car_inventory.html:65 -#: templates/inventory/car_list.html:71 templates/inventory/car_list.html:77 -#: templates/inventory/car_list_view.html:162 +#: templates/inventory/car_list.html:72 templates/inventory/car_list.html:78 +#: templates/inventory/car_list_view.html:163 #: templates/inventory/cars_list_api.html:32 #: templates/inventory/transfer_details.html:87 #: templates/sales/estimates/estimate_detail.html:194 #: templates/sales/estimates/sale_order_form.html:122 #: templates/sales/estimates/sale_order_preview.html:178 #: templates/sales/invoices/invoice_detail.html:241 -#: templates/sales/sales_list.html:114 +#: templates/sales/sales_list.html:115 msgid "VIN" msgstr "رقم الهيكل" @@ -61,7 +61,24 @@ msgstr "العربية" msgid "Haikal" msgstr "هيكل" -#: inventory/forms.py:105 inventory/forms.py:1631 inventory/models.py:1108 +#: haikalbot/temp_files_not_included/ai_agent_complete/templates/chat.html:25 +#: haikalbot/temp_files_not_included/ai_agent_full_export/templates/chat.html:25 +msgid "Ask me anything..." +msgstr "اسألني عن أي شيء..." + +msgid "Chart" +msgstr "رسم بياني" + +msgid "AI Assistant" +msgstr "المساعد الذكي" + +msgid "Prompt is required." +msgstr "الإدخال مطلوب." + +msgid "An error occurred while processing your request." +msgstr "حدث خطأ أثناء معالجة طلبك." + +#: inventory/forms.py:105 inventory/forms.py:1646 inventory/models.py:1108 #: inventory/models.py:1137 inventory/models.py:1198 inventory/models.py:1324 #: inventory/models.py:1453 inventory/models.py:1646 inventory/models.py:1809 #: templates/account/login.html:29 templates/account/login.html:31 @@ -80,8 +97,8 @@ msgstr "هيكل" #: templates/crm/leads/lead_list.html:39 #: templates/crm/opportunities/opportunity_detail.html:202 #: templates/customers/view_customer.html:78 -#: templates/dealers/dealer_detail.html:84 -#: templates/groups/group_detail.html:61 templates/pricing_page.html:186 +#: templates/dealers/dealer_detail.html:87 +#: templates/groups/group_detail.html:61 templates/pricing_page.html:187 #: templates/sales/estimates/estimate_detail.html:158 #: templates/sales/estimates/sale_order_form.html:56 #: templates/sales/estimates/sale_order_preview.html:168 @@ -101,15 +118,15 @@ msgstr "الخدمات المقدمة" #: inventory/forms.py:118 inventory/forms.py:121 inventory/forms.py:153 #: inventory/forms.py:168 inventory/forms.py:267 inventory/forms.py:515 #: inventory/forms.py:602 inventory/forms.py:822 inventory/forms.py:1019 -#: inventory/forms.py:1637 inventory/models.py:928 inventory/models.py:1015 +#: inventory/forms.py:1652 inventory/models.py:928 inventory/models.py:1015 #: inventory/models.py:1203 inventory/models.py:1325 inventory/models.py:1434 #: inventory/models.py:1454 inventory/models.py:1878 #: templates/administration/staff_index.html:123 #: templates/crm/leads/lead_list.html:103 #: templates/crm/opportunities/opportunity_detail.html:192 #: templates/customers/customer_list.html:41 -#: templates/customers/view_customer.html:80 templates/pricing_page.html:113 -#: templates/pricing_page.html:116 templates/users/user_detail.html:20 +#: templates/customers/view_customer.html:80 templates/pricing_page.html:114 +#: templates/pricing_page.html:117 templates/users/user_detail.html:20 #: templates/vendors/view_vendor.html:20 #: venv/lib/python3.11/site-packages/appointment/templates/administration/staff_index.html:369 #: venv/lib/python3.11/site-packages/django_ledger/models/mixins.py:114 @@ -117,7 +134,7 @@ msgid "Phone Number" msgstr "رقم الهاتف" #: inventory/forms.py:466 inventory/models.py:834 -#: templates/inventory/car_detail.html:143 +#: templates/inventory/car_detail.html:144 msgid "Custom Date" msgstr "تاريخ البطاقة الجمركية" @@ -130,9 +147,9 @@ msgstr "الشخص المسؤول" msgid "Both exterior and interior colors must be selected." msgstr "يجب اختيار اللونين الخارجي والداخلي." -#: inventory/forms.py:677 inventory/forms.py:1634 inventory/models.py:1435 +#: inventory/forms.py:677 inventory/forms.py:1649 inventory/models.py:1435 #: inventory/models.py:1879 templates/account/email_change.html:5 -#: templates/account/email_change.html:9 templates/pricing_page.html:106 +#: templates/account/email_change.html:9 templates/pricing_page.html:107 msgid "Email Address" msgstr "عنوان البريد الإلكتروني" @@ -200,7 +217,7 @@ msgstr "كلمات المرور غير متطابقة." #: templates/ledger/bank_accounts/bank_account_list.html:19 #: templates/organizations/organization_list.html:45 #: templates/plans/order_detail_table.html:8 templates/plans/order_list.html:19 -#: templates/pricing_page.html:185 +#: templates/pricing_page.html:186 #: templates/representatives/representative_list.html:17 #: templates/users/user_detail.html:16 templates/vendors/vendors_list.html:26 #: templates/vendors/view_vendor.html:14 @@ -267,7 +284,7 @@ msgstr "يجب أن يكون رقم التسجيل الضريبي مكونًا #: templates/crm/leads/lead_detail.html:199 #: templates/customers/customer_list.html:51 #: templates/customers/view_customer.html:75 -#: templates/dealers/dealer_detail.html:74 +#: templates/dealers/dealer_detail.html:77 #: templates/organizations/organization_detail.html:11 #: templates/organizations/organization_list.html:71 #: templates/representatives/representative_detail.html:10 @@ -325,7 +342,7 @@ msgstr "الكمية" #: templates/sales/invoices/invoice_create.html:5 #: templates/sales/invoices/invoice_detail.html:69 #: templates/sales/payments/payment_list.html:21 -#: templates/sales/sales_list.html:118 +#: templates/sales/sales_list.html:119 #: venv/lib/python3.11/site-packages/django_ledger/models/entity.py:3172 #: venv/lib/python3.11/site-packages/django_ledger/models/invoice.py:361 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/invoice/includes/card_invoice.html:10 @@ -359,7 +376,7 @@ msgid "credit" msgstr "دائن" #: inventory/forms.py:951 inventory/models.py:1968 -#: templates/inventory/car_detail.html:191 +#: templates/inventory/car_detail.html:192 #: templates/inventory/transfer_car.html:18 msgid "transfer" msgstr "نقل" @@ -397,58 +414,58 @@ msgstr "تم دفع الفاتورة بالفعل" msgid "To" msgstr "إلى" -#: inventory/forms.py:1022 inventory/models.py:203 inventory/models.py:485 +#: inventory/forms.py:1037 inventory/models.py:203 inventory/models.py:485 #: inventory/models.py:1471 inventory/tables.py:52 -#: templates/inventory/car_list_view.html:103 -#: templates/inventory/car_list_view.html:163 +#: templates/inventory/car_list_view.html:104 +#: templates/inventory/car_list_view.html:164 #: templates/inventory/cars_list_api.html:33 #: templates/sales/estimates/estimate_detail.html:191 #: templates/sales/estimates/sale_order_form.html:124 #: templates/sales/estimates/sale_order_preview.html:179 #: templates/sales/invoices/invoice_detail.html:238 -#: templates/sales/sales_list.html:112 +#: templates/sales/sales_list.html:113 msgid "Make" msgstr "الصانع" -#: inventory/forms.py:1039 inventory/models.py:226 inventory/models.py:493 +#: inventory/forms.py:1054 inventory/models.py:226 inventory/models.py:493 #: inventory/models.py:1478 inventory/tables.py:53 -#: templates/inventory/car_list_view.html:117 -#: templates/inventory/car_list_view.html:164 +#: templates/inventory/car_list_view.html:118 +#: templates/inventory/car_list_view.html:165 #: templates/inventory/cars_list_api.html:34 #: templates/sales/estimates/estimate_detail.html:192 #: templates/sales/estimates/sale_order_form.html:126 #: templates/sales/estimates/sale_order_preview.html:180 #: templates/sales/invoices/invoice_detail.html:239 -#: templates/sales/sales_list.html:113 +#: templates/sales/sales_list.html:114 msgid "Model" msgstr "الموديل" -#: inventory/forms.py:1153 +#: inventory/forms.py:1168 msgid "Expected Closing Date" msgstr "تاريخ الإغلاق المتوقع" -#: inventory/forms.py:1158 +#: inventory/forms.py:1173 msgid "Probability (%)" msgstr "الاحتمالية (%)" -#: inventory/forms.py:1347 inventory/models.py:516 inventory/models.py:1513 +#: inventory/forms.py:1362 inventory/models.py:516 inventory/models.py:1513 #: inventory/models.py:1801 inventory/tables.py:62 #: templates/admin_management/user_management.html:22 #: templates/admin_management/user_management.html:86 #: templates/admin_management/user_management.html:150 #: templates/admin_management/user_management.html:214 #: templates/crm/leads/lead_detail.html:130 -#: templates/inventory/car_detail.html:100 -#: templates/inventory/car_detail.html:399 +#: templates/inventory/car_detail.html:101 +#: templates/inventory/car_detail.html:418 #: templates/inventory/car_inventory.html:78 -#: templates/inventory/car_list.html:172 -#: templates/inventory/car_list_view.html:169 +#: templates/inventory/car_list.html:173 +#: templates/inventory/car_list_view.html:170 #: templates/inventory/cars_list_api.html:19 #: templates/inventory/cars_list_api.html:35 templates/plans/current.html:24 #: templates/sales/estimates/estimate_list.html:16 #: templates/sales/invoices/invoice_list.html:17 #: templates/sales/journals/journal_list.html:17 -#: templates/sales/sales_list.html:119 +#: templates/sales/sales_list.html:120 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/tags/bill_table.html:10 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/estimate/includes/card_estimate.html:12 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/estimate/includes/estimate_table.html:12 @@ -458,74 +475,74 @@ msgstr "الاحتمالية (%)" msgid "Status" msgstr "الحالة" -#: inventory/forms.py:1363 inventory/models.py:1717 +#: inventory/forms.py:1378 inventory/models.py:1717 msgid "Stage" msgstr "المرحلة" -#: inventory/forms.py:1492 +#: inventory/forms.py:1507 msgid "Select Car Makes" msgstr "اختر ماركات السيارات" -#: inventory/forms.py:1552 +#: inventory/forms.py:1567 msgid "Please enter a valid credit card number" msgstr "يرجى إدخال رقم بطاقة ائتمان صالح" -#: inventory/forms.py:1582 +#: inventory/forms.py:1597 msgid "Please enter a valid month (01-12)" msgstr "يرجى إدخال شهر صالح (01-12)" -#: inventory/forms.py:1589 +#: inventory/forms.py:1604 msgid "This card appears to be expired" msgstr "يبدو أن هذه البطاقة منتهية الصلاحية" -#: inventory/forms.py:1592 +#: inventory/forms.py:1607 msgid "Please enter a valid expiry date in MM/YY format" msgstr "يرجى إدخال تاريخ انتهاء صلاحية صحيح بصيغة MM/YY" -#: inventory/forms.py:1601 +#: inventory/forms.py:1616 msgid "CVV must contain only digits" msgstr "يجب أن يحتوي رمز التحقق (CVV) على أرقام فقط" -#: inventory/forms.py:1603 +#: inventory/forms.py:1618 msgid "CVV must be 3 or 4 digits" msgstr "يجب أن يكون رمز التحقق (CVV) مكونًا من 3 أو 4 أرقام" -#: inventory/forms.py:1612 inventory/forms.py:1615 inventory/models.py:1187 +#: inventory/forms.py:1627 inventory/forms.py:1630 inventory/models.py:1187 #: inventory/models.py:1451 templates/admin_management/user_management.html:19 #: templates/administration/manage_staff_personal_info.html:18 -#: templates/pricing_page.html:92 templates/pricing_page.html:95 +#: templates/pricing_page.html:93 templates/pricing_page.html:96 msgid "First Name" msgstr "الاسم الأول" -#: inventory/forms.py:1622 inventory/forms.py:1625 inventory/models.py:1191 +#: inventory/forms.py:1637 inventory/forms.py:1640 inventory/models.py:1191 #: inventory/models.py:1452 templates/admin_management/user_management.html:20 #: templates/administration/manage_staff_personal_info.html:24 -#: templates/pricing_page.html:99 templates/pricing_page.html:102 +#: templates/pricing_page.html:100 templates/pricing_page.html:103 msgid "Last Name" msgstr "اسم العائلة" -#: inventory/forms.py:1648 templates/pricing_page.html:142 -#: templates/pricing_page.html:145 templates/pricing_page.html:192 +#: inventory/forms.py:1663 templates/pricing_page.html:143 +#: templates/pricing_page.html:146 templates/pricing_page.html:193 msgid "Card Number" msgstr "رقم البطاقة" -#: inventory/forms.py:1659 +#: inventory/forms.py:1674 msgid "Expiration Date" msgstr "تاريخ الانتهاء" -#: inventory/forms.py:1670 +#: inventory/forms.py:1685 msgid "Security Code (CVV)" msgstr "رمز الأمان (CVV)" -#: inventory/forms.py:1682 +#: inventory/forms.py:1697 msgid "Name on Card" msgstr "الاسم على البطاقة" -#: inventory/forms.py:1692 +#: inventory/forms.py:1707 msgid "I agree to the Terms and Conditions" msgstr "أوافق على الشروط وسياسة الخصوصية" -#: inventory/forms.py:1694 +#: inventory/forms.py:1709 msgid "You must accept the terms and conditions" msgstr "يجب أن تقبل الشروط وسياسة الخصوصية." @@ -612,14 +629,14 @@ msgid "logo" msgstr "الشعار" #: inventory/models.py:255 inventory/models.py:502 inventory/tables.py:55 -#: templates/inventory/car_form.html:82 +#: templates/inventory/car_form.html:83 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:100 msgid "Series" msgstr "السلسلة" #: inventory/models.py:283 inventory/models.py:510 inventory/tables.py:56 -#: templates/inventory/car_list_view.html:166 -#: templates/sales/sales_list.html:115 +#: templates/inventory/car_list_view.html:167 +#: templates/sales/sales_list.html:116 msgid "Trim" msgstr "الفئة" @@ -700,9 +717,9 @@ msgstr "ملغى" #: templates/dashboards/manager.html:235 templates/dashboards/sales.html:20 #: templates/dashboards/sales.html:332 #: templates/inventory/car_inventory.html:131 -#: templates/inventory/car_list_view.html:45 -#: templates/inventory/car_list_view.html:130 -#: templates/inventory/car_list_view.html:210 +#: templates/inventory/car_list_view.html:46 +#: templates/inventory/car_list_view.html:131 +#: templates/inventory/car_list_view.html:216 #: templates/inventory/cars_list_api.html:20 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/invoice/tags/invoice_item_formset.html:21 msgid "Available" @@ -712,9 +729,9 @@ msgstr "متاح" #: templates/dashboards/manager.html:236 templates/dashboards/sales.html:26 #: templates/dashboards/sales.html:333 #: templates/inventory/car_inventory.html:133 -#: templates/inventory/car_list_view.html:56 -#: templates/inventory/car_list_view.html:132 -#: templates/inventory/car_list_view.html:214 +#: templates/inventory/car_list_view.html:57 +#: templates/inventory/car_list_view.html:133 +#: templates/inventory/car_list_view.html:220 #: templates/inventory/cars_list_api.html:22 msgid "Sold" msgstr "تم البيع" @@ -738,9 +755,9 @@ msgstr "تالف" #: templates/dashboards/manager.html:237 templates/dashboards/sales.html:32 #: templates/dashboards/sales.html:334 #: templates/inventory/car_inventory.html:140 -#: templates/inventory/car_list_view.html:49 -#: templates/inventory/car_list_view.html:131 -#: templates/inventory/car_list_view.html:212 +#: templates/inventory/car_list_view.html:50 +#: templates/inventory/car_list_view.html:132 +#: templates/inventory/car_list_view.html:218 #: templates/inventory/cars_list_api.html:21 msgid "Reserved" msgstr "محجوزة" @@ -748,9 +765,9 @@ msgstr "محجوزة" #: inventory/models.py:408 inventory/models.py:1147 #: templates/dashboards/manager.html:121 templates/dashboards/manager.html:238 #: templates/dashboards/sales.html:38 templates/dashboards/sales.html:335 -#: templates/inventory/car_list_view.html:53 -#: templates/inventory/car_list_view.html:133 -#: templates/inventory/car_list_view.html:216 +#: templates/inventory/car_list_view.html:54 +#: templates/inventory/car_list_view.html:134 +#: templates/inventory/car_list_view.html:222 #: templates/inventory/cars_list_api.html:23 #: templates/inventory/transfer_preview.html:264 msgid "Transfer" @@ -800,8 +817,8 @@ msgstr "الوصف" #: templates/administration/manage_service.html:55 #: templates/administration/service_list.html:25 #: templates/administration/user_profile.html:245 -#: templates/inventory/transfer_details.html:89 templates/pricing_page.html:179 -#: templates/sales/sales_list.html:116 +#: templates/inventory/transfer_details.html:89 templates/pricing_page.html:180 +#: templates/sales/sales_list.html:117 #: venv/lib/python3.11/site-packages/appointment/templates/administration/service_list.html:30 #: venv/lib/python3.11/site-packages/appointment/templates/administration/user_profile.html:237 msgid "Price" @@ -832,8 +849,8 @@ msgid "Item Model" msgstr "نموذج العنصر" #: inventory/models.py:477 inventory/models.py:1901 -#: templates/inventory/car_detail.html:117 -#: templates/inventory/car_form.html:147 +#: templates/inventory/car_detail.html:118 +#: templates/inventory/car_form.html:148 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:136 #: templates/ledger/bills/bill_list.html:52 #: venv/lib/python3.11/site-packages/django_ledger/models/bill.py:364 @@ -844,11 +861,11 @@ msgid "Vendor" msgstr "المورد" #: inventory/models.py:495 inventory/models.py:1481 inventory/tables.py:54 -#: templates/inventory/car_form.html:72 +#: templates/inventory/car_form.html:73 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:55 #: templates/inventory/car_inventory.html:67 -#: templates/inventory/car_list_view.html:123 -#: templates/inventory/car_list_view.html:165 +#: templates/inventory/car_list_view.html:124 +#: templates/inventory/car_list_view.html:166 #: templates/ledger/reports/components/period_navigator.html:21 #: templates/sales/estimates/estimate_detail.html:193 #: templates/sales/estimates/sale_order_form.html:128 @@ -858,31 +875,31 @@ msgid "Year" msgstr "السنة" #: inventory/models.py:522 inventory/tables.py:50 -#: templates/inventory/car_detail.html:104 -#: templates/inventory/car_form.html:158 +#: templates/inventory/car_detail.html:105 +#: templates/inventory/car_form.html:159 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:147 -#: templates/inventory/car_list.html:184 +#: templates/inventory/car_list.html:185 msgid "Stock Type" msgstr "نوع المخزون" #: inventory/models.py:524 inventory/models.py:645 -#: templates/inventory/car_detail.html:122 -#: templates/inventory/car_form.html:191 +#: templates/inventory/car_detail.html:123 +#: templates/inventory/car_form.html:192 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:181 -#: templates/inventory/car_list.html:210 +#: templates/inventory/car_list.html:211 msgid "Remarks" msgstr "ملاحظات" #: inventory/models.py:525 inventory/tables.py:57 -#: templates/inventory/car_detail.html:108 -#: templates/inventory/car_form.html:169 +#: templates/inventory/car_detail.html:109 +#: templates/inventory/car_form.html:170 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:157 -#: templates/inventory/car_list.html:196 templates/inventory/car_list.html:202 +#: templates/inventory/car_list.html:197 templates/inventory/car_list.html:203 msgid "Mileage" msgstr "عدد الكيلومترات" -#: inventory/models.py:526 templates/inventory/car_detail.html:112 -#: templates/inventory/car_form.html:180 +#: inventory/models.py:526 templates/inventory/car_detail.html:113 +#: templates/inventory/car_form.html:181 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:168 msgid "Receiving Date" msgstr "تاريخ الاستلام" @@ -916,7 +933,7 @@ msgstr "سجل نقل السيارة" msgid "Car Transfer Logs" msgstr "سجلات نقل السيارات" -#: inventory/models.py:678 templates/inventory/car_detail.html:334 +#: inventory/models.py:678 templates/inventory/car_detail.html:353 msgid "Reserved By" msgstr "محجوز بواسطة" @@ -928,7 +945,7 @@ msgstr "تاريخ الحجز" msgid "Reserved Until" msgstr "محجوز حتى" -#: inventory/models.py:697 templates/inventory/car_detail.html:499 +#: inventory/models.py:697 templates/inventory/car_detail.html:518 msgid "Car Reservation" msgstr "حجز السيارة" @@ -936,15 +953,15 @@ msgstr "حجز السيارة" msgid "Car Reservations" msgstr "حجوزات السيارات" -#: inventory/models.py:708 templates/inventory/car_detail.html:229 +#: inventory/models.py:708 templates/inventory/car_detail.html:230 msgid "Cost Price" msgstr "سعر التكلفة" -#: inventory/models.py:711 templates/inventory/car_detail.html:234 +#: inventory/models.py:711 templates/inventory/car_detail.html:235 msgid "Selling Price" msgstr "سعر البيع" -#: inventory/models.py:716 templates/inventory/car_detail.html:238 +#: inventory/models.py:716 templates/inventory/car_detail.html:239 #: templates/sales/estimates/estimate_detail.html:221 #: templates/sales/invoices/invoice_detail.html:261 msgid "Discount Amount" @@ -968,7 +985,7 @@ msgstr "الألوان الخارجية" msgid "Interior Colors" msgstr "الألوان الداخلية" -#: inventory/models.py:818 templates/inventory/car_list_view.html:167 +#: inventory/models.py:818 templates/inventory/car_list_view.html:168 msgid "Color" msgstr "اللون" @@ -976,12 +993,12 @@ msgstr "اللون" msgid "Colors" msgstr "الألوان" -#: inventory/models.py:833 templates/inventory/car_detail.html:139 +#: inventory/models.py:833 templates/inventory/car_detail.html:140 msgid "Custom Number" msgstr "رقم البطاقة الجمركية" -#: inventory/models.py:837 templates/inventory/car_detail.html:148 -#: templates/inventory/car_detail.html:457 +#: inventory/models.py:837 templates/inventory/car_detail.html:149 +#: templates/inventory/car_detail.html:476 msgid "Custom Card" msgstr "البطاقة الجمركية" @@ -1037,13 +1054,13 @@ msgstr "النص 2" msgid "Text 3" msgstr "النص 3" -#: inventory/models.py:896 templates/inventory/car_detail.html:165 +#: inventory/models.py:896 templates/inventory/car_detail.html:166 msgid "Registration Date" msgstr "تاريخ التسجيل" -#: inventory/models.py:899 templates/inventory/car_detail.html:159 -#: templates/inventory/car_detail.html:170 -#: templates/inventory/car_detail.html:478 +#: inventory/models.py:899 templates/inventory/car_detail.html:160 +#: templates/inventory/car_detail.html:171 +#: templates/inventory/car_detail.html:497 msgid "Registration" msgstr "التسجيل" @@ -1099,6 +1116,7 @@ msgid "Accountant" msgstr "محاسب" #: inventory/models.py:1004 templates/header.html:110 +#: templates/sales/sales_list.html:4 msgid "Sales" msgstr "المبيعات" @@ -1112,7 +1130,7 @@ msgstr "نوع الموظف" #: templates/admin_management/user_management.html:163 #: templates/admin_management/user_management.html:227 #: templates/customers/customer_list.html:56 -#: templates/dealers/dealer_detail.html:111 +#: templates/dealers/dealer_detail.html:114 #: templates/ledger/coa_accounts/account_detail.html:55 #: templates/ledger/coa_accounts/account_list.html:28 #: templates/plans/current.html:26 @@ -1127,8 +1145,7 @@ msgstr "نشط" #: inventory/models.py:1082 inventory/models.py:1083 #: templates/admin_management/user_management.html:206 #: templates/crm/opportunities/opportunity_detail.html:233 -#: templates/dashboards/manager.html:16 templates/users/user_form.html:4 -#: templates/users/user_list.html:5 +#: templates/dashboards/manager.html:16 msgid "Staff" msgstr "الموظفون" @@ -1254,8 +1271,8 @@ msgstr "الأمير" msgid "Princess" msgstr "الأميرة" -#: inventory/models.py:1130 templates/pricing_page.html:120 -#: templates/pricing_page.html:123 templates/pricing_page.html:187 +#: inventory/models.py:1130 templates/pricing_page.html:121 +#: templates/pricing_page.html:124 templates/pricing_page.html:188 msgid "Company" msgstr "الشركة" @@ -1295,7 +1312,7 @@ msgstr "متابعة" msgid "Converted" msgstr "تم التحويل" -#: inventory/models.py:1148 templates/inventory/car_form.html:37 +#: inventory/models.py:1148 templates/inventory/car_form.html:38 #: templates/sales/estimates/estimate_form.html:20 msgid "Add Car" msgstr "إضافة سيارة" @@ -1420,7 +1437,6 @@ msgid "Customer" msgstr "العميل" #: inventory/models.py:1229 templates/admin_management/user_management.html:14 -#: templates/customers/customer_form.html:4 #: templates/customers/customer_list.html:4 #: templates/customers/customer_list.html:5 #: templates/customers/customer_list.html:9 @@ -1500,7 +1516,6 @@ msgstr "فرصة" #: inventory/models.py:1539 templates/crm/leads/lead_list.html:3 #: templates/crm/leads/lead_list.html:7 templates/crm/leads/lead_send.html:5 -#: templates/crm/leads/lead_tracking.html:3 #: templates/dashboards/manager.html:21 test.txt:21 msgid "Leads" msgstr "الفرص" @@ -1605,6 +1620,7 @@ msgstr "فرصة" #: inventory/models.py:1743 #: ⁨templates/crm/opportunities/opportunity_list copy.html⁩:8 +#: templates/crm/opportunities/opportunity_list.html:4 #: templates/crm/opportunities/opportunity_list.html:8 msgid "Opportunities" msgstr "الفرص" @@ -1680,7 +1696,7 @@ msgstr "نوع النشاط" #: inventory/models.py:1832 templates/components/activity_modal.html:7 #: templates/crm/leads/lead_detail.html:221 -#: templates/dealers/activity_log.html:11 +#: templates/dealers/activity_log.html:3 templates/dealers/activity_log.html:12 #: templates/ledger/journal_entry/includes/card_journal_entry.html:32 #: templates/ledger/journal_entry/journal_entry_list.html:50 #: venv/lib/python3.11/site-packages/django_ledger/models/closing_entry.py:396 @@ -1714,8 +1730,8 @@ msgid "Vendor Model" msgstr "نموذج المورد" #: inventory/models.py:1902 templates/admin_management/user_management.html:142 -#: templates/vendors/vendor_form.html:4 templates/vendors/vendors_list.html:5 -#: templates/vendors/vendors_list.html:7 templates/vendors/vendors_list.html:12 +#: templates/vendors/vendors_list.html:5 templates/vendors/vendors_list.html:7 +#: templates/vendors/vendors_list.html:12 msgid "Vendors" msgstr "الموردين" @@ -1797,8 +1813,7 @@ msgstr "بطاقة ائتمان" msgid "Bank Transfer" msgstr "تحويل بنكي" -#: inventory/models.py:2088 templates/groups/group_form.html:4 -#: templates/groups/group_list.html:5 templates/users/user_group_form.html:4 +#: inventory/models.py:2088 templates/users/user_group_form.html:4 msgid "Group" msgstr "مجموعة" @@ -2067,7 +2082,7 @@ msgstr "مصروفات الفائدة" msgid "success" msgstr "ناجحة" -#: inventory/utils.py:75 templates/inventory/car_form.html:386 +#: inventory/utils.py:75 templates/inventory/car_form.html:387 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:381 msgid "error" msgstr "خطأ" @@ -2089,7 +2104,7 @@ msgstr "أدخل رقم جوال سعودي صحيح 05XXXXXXXX" msgid "Passwords do not match" msgstr "كلمات المرور غير متطابقة." -#: inventory/views.py:275 inventory/views.py:2641 +#: inventory/views.py:275 inventory/views.py:2672 msgid "User created successfully" msgstr "تم إنشاء المستخدم بنجاح." @@ -2101,14 +2116,14 @@ msgstr "تم حفظ السيارة بنجاح" msgid "VIN number exists" msgstr "رقم الهيكل موجود مسبقاً" -#: inventory/views.py:664 -msgid "VIN not found in all sources" -msgstr "لم يتم العثور على رقم التعريف (VIN) في جميع المصادر" - -#: inventory/views.py:680 +#: inventory/views.py:674 msgid "Manufacturer not found in the database" msgstr "لم يتم العثور على الشركة المصنعة في قاعدة البيانات" +#: inventory/views.py:691 +msgid "VIN not found in all sources" +msgstr "لم يتم العثور على رقم التعريف (VIN) في جميع المصادر" + #: inventory/views.py:721 msgid "Server error occurred" msgstr "حدث خطأ في الخادم" @@ -2127,435 +2142,435 @@ msgstr "لم يتم اكتشاف رمز QR أو الباركود" msgid "inventory" msgstr "المخزون" -#: inventory/views.py:1214 -msgid "Car finance details saved successfully" -msgstr "تم حفظ تفاصيل المالية للسيارة بنجاح." - -#: inventory/views.py:1261 +#: inventory/views.py:952 inventory/views.py:1292 msgid "Car finance details updated successfully" msgstr "تم تحديث تفاصيل المالية للسيارة بنجاح." -#: inventory/views.py:1314 +#: inventory/views.py:1245 +msgid "Car finance details saved successfully" +msgstr "تم حفظ تفاصيل المالية للسيارة بنجاح." + +#: inventory/views.py:1345 msgid "Car updated successfully" msgstr "تم تحديث السيارة بنجاح" -#: inventory/views.py:1354 +#: inventory/views.py:1385 msgid "Car deleted successfully" msgstr "تم حذف السيارة بنجاح." -#: inventory/views.py:1391 +#: inventory/views.py:1422 msgid "Location saved successfully" msgstr "تم حفظ يوم الإجازة بنجاح." -#: inventory/views.py:1430 +#: inventory/views.py:1461 msgid "Location updated successfully" msgstr "تم تحديث البريد الإلكتروني بنجاح!" -#: inventory/views.py:1537 +#: inventory/views.py:1568 msgid "Car transfer canceled successfully" msgstr "تم إلغاء نقل السيارة بنجاح." -#: inventory/views.py:1554 +#: inventory/views.py:1585 msgid "Car transfer approved successfully" msgstr "تمت الموافقة على نقل السيارة بنجاح." -#: inventory/views.py:1579 +#: inventory/views.py:1610 msgid "Car transfer rejected successfully" msgstr "تم رفض نقل السيارة بنجاح." -#: inventory/views.py:1591 +#: inventory/views.py:1622 msgid "Car Transfer Completed successfully." msgstr "تم إكمال نقل السيارة بنجاح." -#: inventory/views.py:1661 +#: inventory/views.py:1692 msgid "Custom Card added successfully" msgstr "تم إضافة البطاقة الجمركية بنجاح." -#: inventory/views.py:1703 +#: inventory/views.py:1734 msgid "Registration added successfully" msgstr "تم إلغاء الحجز بنجاح." -#: inventory/views.py:1726 +#: inventory/views.py:1757 msgid "This car is already reserved" msgstr "هذه السيارة محجوزة بالفعل." -#: inventory/views.py:1764 +#: inventory/views.py:1795 msgid "Reservation renewed successfully" msgstr "تم تجديد الحجز بنجاح" -#: inventory/views.py:1772 +#: inventory/views.py:1803 msgid "Reservation canceled successfully" msgstr "تم إلغاء الحجز بنجاح." -#: inventory/views.py:1777 +#: inventory/views.py:1808 msgid "Invalid action" msgstr "إجراء غير صالح." -#: inventory/views.py:1781 +#: inventory/views.py:1812 msgid "Invalid request method" msgstr "طريقة الطلب غير صالحة" -#: inventory/views.py:1857 +#: inventory/views.py:1888 msgid "Dealer updated successfully" msgstr "تم تحديث المعرض بنجاح." -#: inventory/views.py:1891 templates/header.html:157 +#: inventory/views.py:1922 templates/header.html:157 msgid "customers" msgstr "العملاء" -#: inventory/views.py:2052 +#: inventory/views.py:2083 msgid "Customer Account with this email is Deactivated,Please Contact Admin" msgstr "" "تم تعطيل حساب العميل المرتبط بهذا البريد الإلكتروني، يرجى التواصل مع المسؤول" -#: inventory/views.py:2057 +#: inventory/views.py:2088 msgid "Customer with this email already exists" msgstr "يوجد عميل مسجل مسبقًا بهذا البريد الإلكتروني" -#: inventory/views.py:2127 +#: inventory/views.py:2158 msgid "Customer deactivated successfully" msgstr "تم تعطيل حساب العميل بنجاح" -#: inventory/views.py:2222 +#: inventory/views.py:2253 msgid "Vendor created successfully" msgstr "تم إنشاء المورد بنجاح" -#: inventory/views.py:2230 +#: inventory/views.py:2261 msgid "Vendor Account with this email is Deactivated,Please Contact Admin" msgstr "" "تم تعطيل حساب المورد المرتبط بهذا البريد الإلكتروني، يرجى التواصل مع المسؤول" -#: inventory/views.py:2234 +#: inventory/views.py:2265 msgid "Vendor with this email already exists" msgstr "يوجد مورد مسجل مسبقًا بهذا البريد الإلكتروني" -#: inventory/views.py:2273 +#: inventory/views.py:2304 msgid "Vendor updated successfully" msgstr "تم تحديث المورد بنجاح" -#: inventory/views.py:2316 +#: inventory/views.py:2347 msgid "Vendor deleted successfully" msgstr "تم حذف المورد بنجاح." -#: inventory/views.py:2404 +#: inventory/views.py:2435 msgid "Group created successfully" msgstr "تم إنشاء المجموعة بنجاح." -#: inventory/views.py:2447 +#: inventory/views.py:2478 msgid "Group updated successfully" msgstr "تم تحديث المجموعة بنجاح." -#: inventory/views.py:2474 +#: inventory/views.py:2505 msgid "Group deleted successfully" msgstr "تم حذف المجموعة بنجاح." -#: inventory/views.py:2508 +#: inventory/views.py:2539 msgid "Permission added successfully" msgstr "تمت إضافة الإذن بنجاح." -#: inventory/views.py:2544 +#: inventory/views.py:2575 msgid "Group added successfully" msgstr "تمت إضافة المجموعة بنجاح." -#: inventory/views.py:2656 +#: inventory/views.py:2687 msgid "" "You have reached the maximum number of staff users allowed for your plan" msgstr "لقد وصلت إلى الحد الأقصى لعدد أعضاء الفريق المسموح به في خطتك." -#: inventory/views.py:2713 +#: inventory/views.py:2744 msgid "User updated successfully" msgstr "تم تحديث المستخدم بنجاح" -#: inventory/views.py:2764 +#: inventory/views.py:2795 msgid "User deleted successfully" msgstr "تم حذف المستخدم بنجاح." -#: inventory/views.py:2856 +#: inventory/views.py:2887 msgid "" "Organization Account with this email is Deactivated,Please Contact Admin" msgstr "" "تم تعطيل حساب المؤسسة المرتبط بهذا البريد الإلكتروني، يرجى التواصل مع المسؤول" -#: inventory/views.py:2861 +#: inventory/views.py:2892 msgid "Organization with this email already exists" msgstr "يوجد مؤسسة مسجلة مسبقًا بهذا البريد الإلكتروني" -#: inventory/views.py:2922 +#: inventory/views.py:2953 msgid "Organization Deactivated successfully" msgstr "تم إلغاء تفعيل المؤسسة بنجاح" -#: inventory/views.py:3007 +#: inventory/views.py:3038 msgid "Representative created successfully" msgstr "تم إنشاء الخدمة بنجاح." -#: inventory/views.py:3050 +#: inventory/views.py:3081 msgid "Representative updated successfully" msgstr "تم تحديث الخدمة بنجاح." -#: inventory/views.py:3075 +#: inventory/views.py:3106 msgid "Representative deleted successfully" msgstr "تم حذف الخدمة بنجاح!" -#: inventory/views.py:3148 +#: inventory/views.py:3179 msgid "Bank account created successfully" msgstr "تم إنشاء المنظمة بنجاح." -#: inventory/views.py:3221 +#: inventory/views.py:3252 msgid "Bank account updated successfully" msgstr "تم تحديث المجموعة بنجاح." -#: inventory/views.py:3257 +#: inventory/views.py:3288 msgid "Bank account deleted successfully" msgstr "تم حذف الملاحظة بنجاح." -#: inventory/views.py:3345 +#: inventory/views.py:3376 msgid "Account created successfully" msgstr "تم إنشاء المجموعة بنجاح." -#: inventory/views.py:3459 +#: inventory/views.py:3490 msgid "Account updated successfully" msgstr "تم تحديث المجموعة بنجاح." -#: inventory/views.py:3487 +#: inventory/views.py:3518 msgid "Account deleted successfully" msgstr "تم حذف الملاحظة بنجاح." -#: inventory/views.py:3601 inventory/views.py:6168 +#: inventory/views.py:3632 inventory/views.py:6210 msgid "Items and Quantities are required" msgstr "المنتجات والكميات مطلوبة" -#: inventory/views.py:3610 inventory/views.py:3618 inventory/views.py:6176 -#: inventory/views.py:6184 +#: inventory/views.py:3641 inventory/views.py:3649 inventory/views.py:6218 +#: inventory/views.py:6226 msgid "Quantity must be greater than zero" msgstr "يجب أن تكون مدة الفاصل الزمني أكبر من 0." -#: inventory/views.py:3631 inventory/views.py:3644 +#: inventory/views.py:3662 inventory/views.py:3675 msgid "Quantity must be less than or equal to the number of cars in stock" msgstr "يجب أن تكون الكمية أقل من أو تساوي عدد السيارات المتوفرة في المخزون" -#: inventory/views.py:3760 +#: inventory/views.py:3791 msgid "Quotation created successfully" msgstr "تم إنشاء عرض السعر بنجاح" -#: inventory/views.py:4037 +#: inventory/views.py:4068 msgid "Quotation is not ready for review" msgstr "العرض غير جاهز للمراجعة." -#: inventory/views.py:4043 +#: inventory/views.py:4074 msgid "Quotation is not ready for approval" msgstr "العرض غير جاهز للموافقة." -#: inventory/views.py:4046 +#: inventory/views.py:4077 msgid "Quotation approved successfully" msgstr "تمت الموافقة على العرض بنجاح." -#: inventory/views.py:4049 +#: inventory/views.py:4080 msgid "Quotation is not ready for rejection" msgstr "العرض غير جاهز للرفض." -#: inventory/views.py:4052 inventory/views.py:4070 +#: inventory/views.py:4083 inventory/views.py:4101 msgid "Quotation canceled successfully" msgstr "تم إلغاء الحجز بنجاح." -#: inventory/views.py:4055 +#: inventory/views.py:4086 msgid "Quotation is not ready for completion" msgstr "العرض غير جاهز للإكمال." -#: inventory/views.py:4059 +#: inventory/views.py:4090 msgid "Quotation is not ready for cancellation" msgstr "العرض غير جاهز للإلغاء." -#: inventory/views.py:4072 +#: inventory/views.py:4103 msgid "Quotation marked as " msgstr "تم وضع علامة على عرض السعر كـ" -#: inventory/views.py:4492 +#: inventory/views.py:4523 msgid "fully paid" msgstr "مدفوع بالكامل" -#: inventory/views.py:4495 +#: inventory/views.py:4526 msgid "Amount exceeds due amount" msgstr "المبلغ يتجاوز المبلغ المستحق" -#: inventory/views.py:4503 inventory/views.py:4621 +#: inventory/views.py:4534 inventory/views.py:4652 msgid "Payment created successfully" msgstr "تم إنشاء الدفعة بنجاح" -#: inventory/views.py:4625 +#: inventory/views.py:4656 msgid "Invoice is not fully paid, Payment cannot be marked as paid" msgstr "لم يتم دفع الفاتورة بالكامل، لا يمكن وضع علامة مدفوعة على الدفعة" -#: inventory/views.py:4830 +#: inventory/views.py:4861 msgid "Lead created successfully" msgstr "تم إنشاء العميل المتوقع بنجاح" -#: inventory/views.py:4985 +#: inventory/views.py:5027 msgid "Lead deleted successfully" msgstr "تم حذف العميل المتوقع بنجاح" -#: inventory/views.py:5014 inventory/views.py:5045 inventory/views.py:8076 +#: inventory/views.py:5056 inventory/views.py:5087 inventory/views.py:8119 msgid "Note added successfully" msgstr "تمت إضافة الملاحظة بنجاح" -#: inventory/views.py:5040 +#: inventory/views.py:5082 msgid "Notes field is required" msgstr "حقل الملاحظات مطلوب" -#: inventory/views.py:5068 +#: inventory/views.py:5110 msgid "Note deleted successfully." msgstr "تم حذف الملاحظة بنجاح." -#: inventory/views.py:5094 +#: inventory/views.py:5136 msgid "Lead is already converted to customer" msgstr "تم تحويل العميل المتوقع بالفعل إلى عميل" -#: inventory/views.py:5105 +#: inventory/views.py:5147 msgid "Lead converted to customer successfully" msgstr "تم تحويل العميل المتوقع إلى عميل بنجاح" -#: inventory/views.py:5131 +#: inventory/views.py:5173 msgid "You do not have permission to schedule lead" msgstr "ليست لديك صلاحية جدولة هذا العميل المتوقع" -#: inventory/views.py:5175 +#: inventory/views.py:5217 msgid "Lead scheduled and appointment created successfully" msgstr "تمت جدولة العميل المتوقع وإنشاء الموعد بنجاح" -#: inventory/views.py:5206 +#: inventory/views.py:5248 msgid "Lead transferred successfully" msgstr "تم نقل العميل المتوقع بنجاح" -#: inventory/views.py:5253 +#: inventory/views.py:5295 msgid "Email Draft successfully" msgstr "تم إنشاء مسودة البريد الإلكتروني بنجاح" -#: inventory/views.py:5288 inventory/views.py:6388 +#: inventory/views.py:5330 inventory/views.py:6430 msgid "Email sent successfully" msgstr "تم إرسال البريد الإلكتروني بنجاح!" -#: inventory/views.py:5537 +#: inventory/views.py:5579 msgid "Opportunity deleted successfully" msgstr "تم حذف الفرصة بنجاح." -#: inventory/views.py:5575 +#: inventory/views.py:5617 msgid "Opportunity status updated successfully" msgstr "تم تحديث حالة الفرصة بنجاح" -#: inventory/views.py:5691 +#: inventory/views.py:5733 msgid "Service created successfully" msgstr "تم إنشاء الخدمة بنجاح" -#: inventory/views.py:5736 +#: inventory/views.py:5778 msgid "Service updated successfully" msgstr "تم تحديث الخدمة بنجاح" -#: inventory/views.py:5999 inventory/views.py:6052 +#: inventory/views.py:6041 inventory/views.py:6094 msgid "Bill updated successfully" msgstr "تم تحديث الفاتورة بنجاح." -#: inventory/views.py:6093 +#: inventory/views.py:6135 msgid "Bill is already approved" msgstr "تمت الموافقة على الفاتورة مسبقًا." -#: inventory/views.py:6097 +#: inventory/views.py:6139 msgid "Bill marked as approved successfully" msgstr "تم تحديد الفاتورة كموافقة بنجاح." -#: inventory/views.py:6124 +#: inventory/views.py:6166 msgid "Bill is already paid" msgstr "تم دفع الفاتورة مسبقًا." -#: inventory/views.py:6133 +#: inventory/views.py:6175 msgid "Bill marked as paid successfully" msgstr "تم تحديد الفاتورة كمدفوعة بنجاح." -#: inventory/views.py:6135 +#: inventory/views.py:6177 msgid "Amount paid is not equal to amount due" msgstr "المبلغ المدفوع لا يساوي المبلغ المستحق." -#: inventory/views.py:6246 +#: inventory/views.py:6288 msgid "Bill created successfully" msgstr "تم تحديث الفاتورة بنجاح." -#: inventory/views.py:6349 +#: inventory/views.py:6391 msgid "Quotation has no items" msgstr "عرض السعر لا يحتوي على أي عناصر" -#: inventory/views.py:6905 +#: inventory/views.py:6947 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/unit/unit_detail.html:23 #: venv/lib/python3.11/site-packages/django_ledger/views/entity.py:210 msgid "Dashboard" msgstr "لوحة القيادة" -#: inventory/views.py:7060 inventory/views.py:7093 inventory/views.py:7152 +#: inventory/views.py:7102 inventory/views.py:7135 inventory/views.py:7194 msgid "Unauthorized" msgstr "غير مصرح" -#: inventory/views.py:7278 +#: inventory/views.py:7320 msgid "Settings updated" msgstr "تم تحديث الإعدادات" -#: inventory/views.py:7618 +#: inventory/views.py:7660 msgid "Journal Entry created" msgstr "تم إنشاء قيد اليومية" -#: inventory/views.py:7659 +#: inventory/views.py:7701 msgid "Journal Entry cannot be deleted" msgstr "لا يمكن حذف قيد اليومية" -#: inventory/views.py:7733 +#: inventory/views.py:7775 msgid "Ledger is already locked" msgstr "دفتر الأستاذ مقفل بالفعل" -#: inventory/views.py:7760 +#: inventory/views.py:7802 msgid "Ledger is already Unlocked" msgstr "دفتر الأستاذ غير مقفل بالفعل" -#: inventory/views.py:7789 +#: inventory/views.py:7831 msgid "Ledger is already posted" msgstr "دفتر الأستاذ تم ترحيله بالفعل" -#: inventory/views.py:7819 +#: inventory/views.py:7861 msgid "Ledger is already Unposted" msgstr "دفتر الأستاذ لم يتم ترحيله بعد" -#: inventory/views.py:8011 +#: inventory/views.py:8054 msgid "Activity added successfully" msgstr "تمت إضافة النشاط بنجاح" -#: inventory/views.py:8013 +#: inventory/views.py:8056 msgid "Activity form is not valid" msgstr "نموذج النشاط غير صالح" -#: inventory/views.py:8035 +#: inventory/views.py:8078 msgid "Task added successfully" msgstr "تمت إضافة المهمة بنجاح" -#: inventory/views.py:8038 inventory/views.py:8051 +#: inventory/views.py:8081 inventory/views.py:8094 msgid "Task form is not valid" msgstr "نموذج المهمة غير صالح" -#: inventory/views.py:8049 +#: inventory/views.py:8092 msgid "Task updated successfully" msgstr "تم تحديث المهمة بنجاح" -#: inventory/views.py:8079 inventory/views.py:8093 +#: inventory/views.py:8122 inventory/views.py:8136 msgid "Note form is not valid" msgstr "نموذج الملاحظة غير صالح" -#: inventory/views.py:8090 +#: inventory/views.py:8133 msgid "Note updated successfully" msgstr "تم تحديث الملاحظة بنجاح" -#: inventory/views.py:8126 +#: inventory/views.py:8169 msgid "Account activated successfully" msgstr "تم تفعيل الحساب بنجاح" -#: inventory/views.py:8143 +#: inventory/views.py:8186 msgid "Account Deleted successfully" msgstr "تم حذف الحساب بنجاح" -#: inventory/views.py:8147 +#: inventory/views.py:8190 msgid "You cannot delete this account,it is related to another account" msgstr "لا يمكنك حذف هذا الحساب، لأنه مرتبط بحساب آخر" @@ -2595,7 +2610,7 @@ msgstr "" #: templates/ledger/bills/bill_detail.html:11 #: templates/ledger/bills/bill_detail.html:36 #: templates/ledger/journal_entry/journal_entry_list.html:13 -#: templates/plans/billing_info_delete.html:13 templates/pricing_page.html:201 +#: templates/plans/billing_info_delete.html:13 templates/pricing_page.html:202 #: templates/sales/estimates/estimate_detail.html:52 #: templates/sales/invoices/invoice_detail.html:19 #: templates/sales/invoices/invoice_detail.html:43 @@ -2606,16 +2621,16 @@ msgstr "تأكيد" #: templates/account/confirm_email_verification_code.html:31 #: templates/account/confirm_email_verification_code.html:35 #: templates/account/confirm_login_code..html:38 -#: templates/crm/leads/lead_form.html:32 +#: templates/crm/leads/lead_form.html:40 #: templates/crm/leads/schedule_lead.html:18 #: templates/crm/opportunities/opportunity_detail.html:59 -#: templates/customers/customer_form.html:33 -#: templates/dealers/dealer_form.html:21 templates/groups/group_form.html:35 +#: templates/customers/customer_form.html:39 +#: templates/dealers/dealer_form.html:21 templates/groups/group_form.html:42 #: templates/groups/group_permission_form.html:35 #: templates/inventory/add_colors.html:74 #: templates/inventory/add_custom_card.html:13 #: templates/inventory/car_confirm_delete.html:11 -#: templates/inventory/car_detail.html:359 +#: templates/inventory/car_detail.html:378 #: templates/inventory/car_finance_form.html:46 #: templates/inventory/car_registration_form.html:14 #: templates/inventory/color_palette.html:108 @@ -2623,24 +2638,24 @@ msgstr "تأكيد" #: templates/inventory/transfer_preview.html:222 #: templates/inventory/transfer_preview.html:246 #: templates/items/expenses/expense_create.html:22 -#: templates/items/service/service_create.html:24 -#: templates/ledger/bank_accounts/bank_account_form.html:34 +#: templates/items/service/service_create.html:33 +#: templates/ledger/bank_accounts/bank_account_form.html:42 #: templates/ledger/bills/bill_form.html:43 #: templates/ledger/bills/bill_update_form.html:18 -#: templates/ledger/coa_accounts/account_form.html:35 +#: templates/ledger/coa_accounts/account_form.html:43 #: templates/ledger/journal_entry/includes/card_invoice.html:134 #: templates/ledger/journal_entry/journal_entry_form.html:17 #: templates/ledger/ledger/ledger_form.html:23 #: templates/modal/event_details_modal.html:24 -#: templates/organizations/organization_form.html:16 +#: templates/organizations/organization_form.html:23 #: templates/representatives/representative_form.html:12 #: templates/sales/estimates/estimate_detail.html:120 #: templates/sales/estimates/estimate_form.html:77 #: templates/sales/estimates/estimate_send.html:26 #: templates/sales/journals/journal_form.html:19 #: templates/two_factor/_wizard_actions.html:5 -#: templates/users/user_form.html:41 templates/users/user_group_form.html:24 -#: templates/vendors/vendor_form.html:42 +#: templates/users/user_form.html:48 templates/users/user_group_form.html:24 +#: templates/vendors/vendor_form.html:50 #: venv/lib/python3.11/site-packages/appointment/templates/modal/event_details_modal.html:22 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/bill_create.html:37 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/includes/card_bill.html:205 @@ -2686,7 +2701,7 @@ msgstr "تسجيل الدخول" #: templates/account/verification_sent.html:14 #: templates/account/verified_email_required.html:13 #: templates/account/verified_email_required.html:14 -#: templates/haikalbot/chatbot.html:62 templates/haikalbot/chatbot.html:63 +#: templates/haikalbot/chatbot.html:66 templates/haikalbot/chatbot.html:67 #: templates/otp/verify_otp.html:11 templates/otp/verify_otp.html:12 msgid "home" msgstr "الرئيسية" @@ -2726,7 +2741,7 @@ msgstr "إعادة إرسال التحقق" #: templates/account/email.html:51 templates/administration/staff_list.html:48 #: templates/sales/estimates/estimate_form.html:53 #: templates/sales/estimates/estimate_form.html:117 -#: templates/sales/sales_list.html:208 +#: templates/sales/sales_list.html:209 #: venv/lib/python3.11/site-packages/appointment/templates/administration/staff_list.html:52 msgid "Remove" msgstr "إزالة" @@ -3113,7 +3128,7 @@ msgstr "لم تستلم الرمز؟" #: templates/account/password_reset_from_key.html:40 #: templates/account/password_reset_from_key_done.html:5 #: templates/account/password_reset_from_key_done.html:20 -#: templates/dealers/dealer_detail.html:16 +#: templates/dealers/dealer_detail.html:19 msgid "Change Password" msgstr "تغيير كلمة المرور" @@ -3246,13 +3261,13 @@ msgstr "في أي مكان" msgid "Submit" msgstr "إرسال" -#: templates/account/signup-wizard.html:74 templates/pricing_page.html:199 +#: templates/account/signup-wizard.html:74 templates/pricing_page.html:200 #: venv/lib/python3.11/site-packages/alabaster/relations.html:9 msgid "Previous" msgstr "السابق" #: templates/account/signup-wizard.html:76 -#: templates/appointment/appointments.html:86 templates/pricing_page.html:200 +#: templates/appointment/appointments.html:86 templates/pricing_page.html:201 #: templates/two_factor/_wizard_actions.html:14 #: venv/lib/python3.11/site-packages/alabaster/relations.html:13 #: venv/lib/python3.11/site-packages/appointment/templates/appointment/appointments.html:85 @@ -3269,13 +3284,13 @@ msgid "Password does not match" msgstr "كلمة المرور غير متطابقة" #: templates/account/signup-wizard.html:252 -#: templates/inventory/car_form.html:617 +#: templates/inventory/car_form.html:618 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:608 msgid "Please Wait" msgstr "الرجاء الإنتظار" #: templates/account/signup-wizard.html:253 -#: templates/inventory/car_form.html:618 +#: templates/inventory/car_form.html:619 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:609 msgid "Loading" msgstr "تحميل" @@ -3378,6 +3393,10 @@ msgstr "" "ملاحظة: لا يزال بإمكانك تغيير " "عنوان بريدك الإلكتروني." +#: templates/admin_management/management.html:3 +msgid "Admin Management" +msgstr "إدارة المشرفين" + #: templates/admin_management/user_management.html:5 #: templates/admin_management/user_management.html:11 msgid "User Management" @@ -3391,7 +3410,7 @@ msgstr "تاريخ الإنشاء" #: templates/admin_management/user_management.html:88 #: templates/admin_management/user_management.html:152 #: templates/admin_management/user_management.html:216 -#: templates/inventory/car_detail.html:336 +#: templates/inventory/car_detail.html:355 #: templates/ledger/coa_accounts/account_detail.html:71 #: templates/representatives/representative_list.html:20 #: templates/sales/estimates/estimate_list.html:19 @@ -3475,7 +3494,7 @@ msgstr "تاريخ الإنشاء" #: templates/email_sender/reminder_email.html:80 #: templates/email_sender/reschedule_email.html:64 #: templates/email_sender/reschedule_email.html:69 -#: templates/inventory/car_detail.html:402 +#: templates/inventory/car_detail.html:421 #: templates/inventory/transfer_details.html:73 #: templates/inventory/transfer_preview.html:271 #: templates/ledger/coa_accounts/account_detail.html:67 @@ -3514,7 +3533,6 @@ msgstr "وقت الانتهاء" #: templates/administration/display_appointment.html:37 #: templates/appointment/default_thank_you.html:20 #: templates/email_sender/reminder_email.html:79 -#: templates/items/service/service_create.html:5 #: venv/lib/python3.11/site-packages/appointment/templates/administration/display_appointment.html:37 #: venv/lib/python3.11/site-packages/appointment/templates/appointment/default_thank_you.html:20 #: venv/lib/python3.11/site-packages/appointment/templates/email_sender/reminder_email.html:130 @@ -3531,10 +3549,10 @@ msgstr "العميل" #: templates/administration/display_appointment.html:55 #: templates/appointment/appointment_client_information.html:57 #: templates/crm/leads/lead_detail.html:169 -#: templates/dealers/dealer_detail.html:90 +#: templates/dealers/dealer_detail.html:93 #: templates/organizations/organization_detail.html:10 #: templates/organizations/organization_list.html:64 -#: templates/pricing_page.html:188 +#: templates/pricing_page.html:189 #: templates/representatives/representative_detail.html:9 #: templates/representatives/representative_list.html:19 #: templates/vendors/vendors_list.html:47 @@ -3614,7 +3632,7 @@ msgstr "إدارة أيام الإجازة" #: templates/administration/manage_day_off.html:32 #: templates/administration/manage_working_hours.html:25 #: templates/administration/staff_index.html:106 -#: templates/sales/sales_list.html:120 +#: templates/sales/sales_list.html:121 #: venv/lib/python3.11/site-packages/appointment/templates/administration/manage_day_off.html:32 #: venv/lib/python3.11/site-packages/appointment/templates/administration/manage_working_hours.html:34 #: venv/lib/python3.11/site-packages/appointment/templates/administration/staff_index.html:352 @@ -3755,12 +3773,12 @@ msgstr "" #: templates/components/activity_modal.html:25 #: templates/crm/leads/lead_detail.html:581 #: templates/crm/leads/lead_detail.html:601 -#: templates/crm/leads/lead_form.html:30 +#: templates/crm/leads/lead_form.html:38 #: templates/crm/leads/schedule_lead.html:15 #: templates/crm/opportunities/opportunity_detail.html:60 -#: templates/customers/customer_form.html:31 +#: templates/customers/customer_form.html:37 #: templates/dealers/assign_car_makes.html:88 -#: templates/dealers/dealer_form.html:19 templates/groups/group_form.html:38 +#: templates/dealers/dealer_form.html:19 templates/groups/group_form.html:45 #: templates/groups/group_permission_form.html:38 #: templates/inventory/add_colors.html:72 #: templates/inventory/add_custom_card.html:18 @@ -3772,15 +3790,15 @@ msgstr "" #: templates/items/expenses/expense_create.html:16 #: templates/items/expenses/expense_create.html:21 #: templates/items/expenses/expense_update.html:16 -#: templates/items/service/service_create.html:23 -#: templates/ledger/bank_accounts/bank_account_form.html:32 +#: templates/items/service/service_create.html:32 +#: templates/ledger/bank_accounts/bank_account_form.html:40 #: templates/ledger/bills/bill_form.html:42 #: templates/ledger/bills/bill_update_form.html:15 -#: templates/ledger/coa_accounts/account_form.html:32 +#: templates/ledger/coa_accounts/account_form.html:40 #: templates/ledger/journal_entry/journal_entry_form.html:16 #: templates/ledger/journal_entry/journal_entry_txs.html:62 #: templates/ledger/ledger/ledger_form.html:22 -#: templates/organizations/organization_form.html:15 +#: templates/organizations/organization_form.html:22 #: templates/plans/billing_info_create_or_update.html:17 #: templates/representatives/representative_form.html:11 #: templates/sales/estimates/estimate_form.html:74 @@ -3791,8 +3809,8 @@ msgstr "" #: templates/sales/invoices/paid_invoice_update.html:16 #: templates/sales/journals/journal_form.html:18 #: templates/sales/payments/payment_create.html:16 -#: templates/users/user_form.html:44 templates/users/user_group_form.html:27 -#: templates/vendors/vendor_form.html:40 +#: templates/users/user_form.html:51 templates/users/user_group_form.html:27 +#: templates/vendors/vendor_form.html:48 #: venv/lib/python3.11/site-packages/appointment/templates/administration/manage_staff_member.html:70 #: venv/lib/python3.11/site-packages/appointment/views_admin.py:464 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/tags/bill_item_formset.html:81 @@ -3835,7 +3853,7 @@ msgstr "قائمة الخدمات" #: templates/administration/user_profile.html:162 #: templates/crm/leads/lead_list.html:115 #: templates/crm/leads/lead_list.html:132 templates/groups/group_detail.html:83 -#: templates/inventory/car_detail.html:398 +#: templates/inventory/car_detail.html:417 #: templates/items/expenses/expenses_list.html:23 #: templates/items/service/service_list.html:24 #: templates/ledger/bank_accounts/bank_account_list.html:22 @@ -3954,18 +3972,17 @@ msgstr "أعضاء الفريق" #: templates/administration/staff_list.html:25 templates/crm/note_form.html:15 #: templates/customers/note_form.html:6 #: templates/customers/view_customer.html:88 -#: templates/inventory/car_detail.html:153 -#: templates/inventory/car_detail.html:175 -#: templates/inventory/car_detail.html:196 -#: templates/inventory/car_detail.html:273 -#: templates/inventory/car_detail.html:316 +#: templates/inventory/car_detail.html:154 +#: templates/inventory/car_detail.html:176 +#: templates/inventory/car_detail.html:197 +#: templates/inventory/car_detail.html:274 #: venv/lib/python3.11/site-packages/appointment/services.py:170 msgid "Add" msgstr "إضافة" #: templates/administration/staff_list.html:36 #: ⁨templates/crm/opportunities/opportunity_list copy.html⁩:71 -#: templates/inventory/car_form.html:228 +#: templates/inventory/car_form.html:229 #: templates/inventory/inventory_stats.html:63 #: venv/lib/python3.11/site-packages/appointment/templates/administration/staff_list.html:40 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/closing_entry/tags/closing_entry_table.html:43 @@ -4229,7 +4246,7 @@ msgstr "تفاصيل الدفع" #: templates/appointment/appointment_client_information.html:96 #: templates/customers/view_customer.html:121 -#: templates/inventory/car_detail.html:258 +#: templates/inventory/car_detail.html:259 #: templates/inventory/inventory_stats.html:70 #: templates/inventory/transfer_details.html:91 #: templates/inventory/transfer_preview.html:288 @@ -4241,7 +4258,7 @@ msgstr "تفاصيل الدفع" #: templates/ledger/reports/tags/cash_flow_statement.html:11 #: templates/plans/invoices/layout.html:112 #: templates/plans/invoices/layout.html:133 -#: templates/plans/order_detail_table.html:12 templates/pricing_page.html:182 +#: templates/plans/order_detail_table.html:12 templates/pricing_page.html:183 #: templates/sales/estimates/estimate_detail.html:197 #: templates/sales/estimates/sale_order_preview.html:184 #: templates/sales/invoices/invoice_detail.html:244 @@ -4441,9 +4458,9 @@ msgid "No Action" msgstr "لا يوجد إجراء" #: templates/crm/leads/lead_detail.html:87 -#: templates/crm/leads/lead_list.html:56 templates/inventory/car_form.html:239 -#: templates/inventory/car_form.html:258 templates/inventory/car_form.html:276 -#: templates/inventory/car_form.html:293 +#: templates/crm/leads/lead_list.html:56 templates/inventory/car_form.html:240 +#: templates/inventory/car_form.html:259 templates/inventory/car_form.html:277 +#: templates/inventory/car_form.html:294 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:229 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:235 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:252 @@ -4530,13 +4547,13 @@ msgstr "تم الإنشاء في" #: templates/crm/leads/lead_detail.html:342 #: templates/crm/leads/lead_detail.html:343 -#: templates/crm/leads/lead_form.html:20 +#: templates/crm/leads/lead_form.html:28 #: templates/crm/leads/schedule_lead.html:5 templates/crm/note_form.html:13 -#: templates/crm/opportunities/opportunity_form.html:142 +#: templates/crm/opportunities/opportunity_form.html:149 #: templates/crm/opportunities/partials/opportunity_grid.html:103 #: templates/customers/view_customer.html:30 #: templates/items/expenses/expenses_list.html:44 -#: templates/items/service/service_list.html:55 +#: templates/items/service/service_list.html:51 #: templates/ledger/bank_accounts/bank_account_list.html:41 #: templates/ledger/journal_entry/includes/card_invoice.html:34 #: venv/lib/python3.11/site-packages/appointment/services.py:170 @@ -4588,9 +4605,16 @@ msgstr "إرسال البريد الإلكتروني" msgid "Add Task" msgstr "إضافة مهمة" -#: templates/crm/leads/lead_form.html:20 +#: templates/crm/leads/lead_form.html:6 +msgid "Update Lead" +msgstr "تحديث العميل المحتمل" + +msgid "Add New Lead" +msgstr "إضافة عميل محتمل جديد" + +#: templates/crm/leads/lead_form.html:28 #: templates/crm/leads/schedule_lead.html:5 -#: templates/crm/opportunities/opportunity_form.html:144 +#: templates/crm/opportunities/opportunity_form.html:151 #: venv/lib/python3.11/site-packages/appointment/views_admin.py:429 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bank_account/bank_account_create.html:22 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/bill_create.html:33 @@ -4641,7 +4665,7 @@ msgstr "هل أنت متأكد أنك تريد حذف هذا العميل الم #: templates/crm/leads/lead_list.html:150 #: ⁨templates/crm/opportunities/opportunity_list copy.html⁩:158 #: templates/groups/group_detail.html:32 -#: templates/inventory/car_detail.html:519 +#: templates/inventory/car_detail.html:538 #: templates/inventory/transfer_details.html:62 #: templates/ledger/bank_accounts/bank_account_detail.html:31 #: templates/ledger/bills/bill_detail.html:24 @@ -4671,10 +4695,10 @@ msgstr "مؤهل" #: templates/crm/leads/lead_list.html:267 #: ⁨templates/crm/opportunities/opportunity_list copy.html⁩:27 -#: templates/dealers/dealer_detail.html:19 +#: templates/dealers/dealer_detail.html:22 #: templates/groups/group_detail.html:103 -#: templates/inventory/car_detail.html:207 -#: templates/inventory/car_detail.html:265 +#: templates/inventory/car_detail.html:208 +#: templates/inventory/car_detail.html:266 #: templates/ledger/bank_accounts/bank_account_detail.html:57 #: templates/ledger/coa_accounts/account_detail.html:140 #: templates/modal/event_details_modal.html:27 @@ -4699,6 +4723,10 @@ msgstr "جدولة الحدث" msgid "Convert to Opportunity" msgstr "تحويل إلى فرصة" +#: templates/crm/leads/lead_tracking.html:3 +msgid "Leads Tracking" +msgstr "متابعة العملاء المحتملين" + #: templates/crm/leads/lead_tracking.html:65 msgid "Lead Tracking" msgstr "تتبع العملاء المحتملين" @@ -4720,6 +4748,10 @@ msgstr "نظام" msgid "Mark as Read" msgstr "وضع علامة مقروء" +#: templates/crm/opportunities/opportunity_detail.html:3 +msgid "Opportunity Detail" +msgstr "تفاصيل الفرصة" + #: templates/crm/opportunities/opportunity_detail.html:7 msgid "Opportunity details" msgstr "تفاصيل الفرصة" @@ -4746,35 +4778,41 @@ msgstr "اسم جهة الاتصال" msgid "Create Date" msgstr "تاريخ الإنشاء" -#: templates/crm/opportunities/opportunity_form.html:10 +msgid "Update Opportunity" +msgstr "تحديث الفرصة" + +msgid "Add New Opportunity" +msgstr "إضافة فرصة جديدة" + +#: templates/crm/opportunities/opportunity_form.html:17 msgid "Edit Opportunity" msgstr "تعديل الفرصة" -#: templates/crm/opportunities/opportunity_form.html:12 +#: templates/crm/opportunities/opportunity_form.html:19 msgid "Create New Opportunity" msgstr "إنشاء فرصة جديدة" -#: templates/crm/opportunities/opportunity_form.html:18 +#: templates/crm/opportunities/opportunity_form.html:25 msgid "Back to list" msgstr "العودة إلى القائمة" -#: templates/crm/opportunities/opportunity_form.html:138 +#: templates/crm/opportunities/opportunity_form.html:145 msgid "Reset" msgstr "إعادة تعيين" -#: templates/crm/opportunities/opportunity_form.html:155 +#: templates/crm/opportunities/opportunity_form.html:162 msgid "Opportunity Guidelines" msgstr "إرشادات الفرص" -#: templates/crm/opportunities/opportunity_form.html:160 +#: templates/crm/opportunities/opportunity_form.html:167 msgid "Probability indicates conversion chance" msgstr "تشير النسبة المئوية إلى فرصة التحويل" -#: templates/crm/opportunities/opportunity_form.html:166 +#: templates/crm/opportunities/opportunity_form.html:173 msgid "Update stage as deal progresses" msgstr "تحديث المرحلة مع تقدم الصفقة" -#: templates/crm/opportunities/opportunity_form.html:172 +#: templates/crm/opportunities/opportunity_form.html:179 msgid "Set realistic closing dates" msgstr "تحديد تواريخ إغلاق واقعية" @@ -4784,13 +4822,13 @@ msgid "Add Opportunity" msgstr "إضافة فرصة" #: ⁨templates/crm/opportunities/opportunity_list copy.html⁩:55 -#: templates/inventory/car_list_view.html:231 +#: templates/inventory/car_list_view.html:244 #: templates/ledger/bills/bill_list.html:84 #: templates/ledger/coa_accounts/account_list.html:98 #: templates/ledger/journal_entry/includes/card_invoice.html:33 #: templates/ledger/journal_entry/journal_entry_list.html:99 #: templates/sales/invoices/invoice_list.html:61 -#: templates/sales/sales_list.html:207 +#: templates/sales/sales_list.html:208 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/includes/card_bill.html:44 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/entity/entitiy_list.html:20 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/invoice/includes/card_invoice.html:38 @@ -4819,7 +4857,7 @@ msgstr "هل أنت متأكد أنك تريد حذف هذه الفرصة؟" #: ⁨templates/crm/opportunities/opportunity_list copy.html⁩:155 #: templates/groups/group_detail.html:27 -#: templates/inventory/car_detail.html:514 +#: templates/inventory/car_detail.html:533 #: templates/inventory/transfer_details.html:29 #: templates/inventory/transfer_details.html:58 #: templates/ledger/bank_accounts/bank_account_detail.html:26 @@ -4864,11 +4902,21 @@ msgstr "أقرب تاريخ إغلاق" msgid "View Details" msgstr "عرض التفاصيل" -#: templates/customers/customer_form.html:15 +#: templates/customers/customer_form.html:7 +#: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/customer/customer_update.html:11 +#: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/customer/customer_update.html:23 +msgid "Update Customer" +msgstr "تحديث العميل" + +#: templates/customers/customer_form.html:9 +msgid "Add New Customer" +msgstr "إضافة عميل جديد" + +#: templates/customers/customer_form.html:21 msgid "Edit Customer" msgstr "تحديث العميل" -#: templates/customers/customer_form.html:17 +#: templates/customers/customer_form.html:23 #: templates/customers/customer_list.html:15 #: templates/sales/estimates/estimate_form.html:27 msgid "Add Customer" @@ -4906,7 +4954,7 @@ msgid "Quotations" msgstr "العروض" #: templates/customers/view_customer.html:72 -#: templates/dealers/dealer_detail.html:69 +#: templates/dealers/dealer_detail.html:72 msgid "Default Address" msgstr "العنوان الافتراضي" @@ -4930,7 +4978,7 @@ msgstr "حالة الدفع" #: templates/sales/estimates/estimate_detail.html:79 #: templates/sales/estimates/estimate_send.html:5 #: templates/sales/estimates/sale_order_form.html:171 -#: templates/sales/sales_list.html:117 +#: templates/sales/sales_list.html:118 msgid "Quotation" msgstr "عرض سعر" @@ -4945,7 +4993,7 @@ msgid "Paid" msgstr "مدفوع" #: templates/customers/view_customer.html:223 -#: templates/inventory/car_detail.html:588 +#: templates/inventory/car_detail.html:607 msgid "Error loading form. Please try again later" msgstr "حدث خطأ أثناء تحميل النموذج. يرجى المحاولة مرة أخرى لاحقًا." @@ -4980,11 +5028,11 @@ msgstr "تم استلام الدفع عبر جميع القنوات." #: templates/dashboards/sales.html:77 msgid "New Customers" -msgstr "عملاء جدد." +msgstr "عملاء جدد" #: templates/dashboards/sales.html:154 msgid "Canceled Invoices" -msgstr "الفواتير الملغاة." +msgstr "الفواتير الملغاة" #: templates/dashboards/sales.html:160 msgid "From last month" @@ -4996,82 +5044,88 @@ msgstr "من الشهر الماضي." msgid "Gross Profit" msgstr "الربح الإجمالي" +#: templates/dealers/assign_car_makes.html:3 +msgid "Car Makes" +msgstr "ماركات السيارات" + #: templates/dealers/assign_car_makes.html:59 msgid "Select Car Makes You Sell" msgstr "اختر ماركات السيارات التي تبيعها" -#: templates/dealers/dealer_detail.html:5 +#: templates/dealers/dealer_detail.html:3 +#: templates/dealers/dealer_detail.html:8 msgid "Profile" msgstr "الملف الشخصي" -#: templates/dealers/dealer_detail.html:10 +#: templates/dealers/dealer_detail.html:13 #: templates/sales/orders/order_list.html:4 #: templates/sales/orders/order_list.html:8 msgid "Orders" msgstr "طلبات" -#: templates/dealers/dealer_detail.html:13 +#: templates/dealers/dealer_detail.html:16 +#: templates/plans/billing_info_create_or_update.html:3 msgid "Billing Information" msgstr "معلومات الفوترة" -#: templates/dealers/dealer_detail.html:43 +#: templates/dealers/dealer_detail.html:46 msgid "Joined" msgstr "انضم" -#: templates/dealers/dealer_detail.html:43 +#: templates/dealers/dealer_detail.html:46 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/chart_of_accounts/includes/coa_card.html:41 msgid "ago" msgstr "منذ" -#: templates/dealers/dealer_detail.html:50 +#: templates/dealers/dealer_detail.html:53 msgid "last login" msgstr "آخر تسجيل دخول" -#: templates/dealers/dealer_detail.html:54 +#: templates/dealers/dealer_detail.html:57 msgid "Total users" msgstr "إجمالي المستخدمين" -#: templates/dealers/dealer_detail.html:58 +#: templates/dealers/dealer_detail.html:61 msgid "Total cars" msgstr "إجمالي السيارات" -#: templates/dealers/dealer_detail.html:113 -#: templates/inventory/car_detail.html:364 templates/plans/current.html:28 +#: templates/dealers/dealer_detail.html:116 +#: templates/inventory/car_detail.html:383 templates/plans/current.html:28 msgid "Expired" msgstr "منتهي الصلاحية" -#: templates/dealers/dealer_detail.html:114 -#: templates/inventory/car_detail.html:353 +#: templates/dealers/dealer_detail.html:117 +#: templates/inventory/car_detail.html:372 msgid "Renew" msgstr "تجديد" -#: templates/dealers/dealer_detail.html:117 templates/plans/current.html:46 +#: templates/dealers/dealer_detail.html:120 templates/plans/current.html:46 msgid "Upgrade" msgstr "ترقية" -#: templates/dealers/dealer_detail.html:120 +#: templates/dealers/dealer_detail.html:123 #: templates/subscriptions/subscription_plan.html:85 msgid "Subscribe" msgstr "الاشتراك" -#: templates/dealers/dealer_detail.html:124 templates/plans/current.html:35 +#: templates/dealers/dealer_detail.html:127 templates/plans/current.html:35 msgid "Active until" msgstr "نشط حتى" -#: templates/dealers/dealer_detail.html:124 +#: templates/dealers/dealer_detail.html:127 msgid "Days left" msgstr "الأيام المتبقية" -#: templates/dealers/dealer_detail.html:127 +#: templates/dealers/dealer_detail.html:130 #: templates/subscriptions/subscription_plan.html:42 templates/welcome.html:107 msgid "Per month" msgstr "شهريًا" -#: templates/dealers/dealer_detail.html:157 +#: templates/dealers/dealer_detail.html:160 msgid "Makes you are selling" msgstr "الماركات التي تبيعها" -#: templates/dealers/dealer_detail.html:172 +#: templates/dealers/dealer_detail.html:175 msgid "Select Makes" msgstr "اختر العلامات التجارية" @@ -5164,7 +5218,7 @@ msgid "This is a reminder for your upcoming appointment." msgstr "هذه تذكرة بموعدك القادم." #: templates/email_sender/reminder_email.html:83 -#: templates/inventory/car_detail.html:180 +#: templates/inventory/car_detail.html:181 #: venv/lib/python3.11/site-packages/appointment/templates/email_sender/reminder_email.html:142 msgid "Location" msgstr "الموقع" @@ -5474,14 +5528,21 @@ msgstr "الاسم" msgid "Back to List" msgstr "العودة إلى القائمة" -#: templates/groups/group_form.html:16 +msgid "Update Group" +msgstr "تحديث المجموعة" + +msgid "Add New Group" +msgstr "إضافة مجموعة جديدة" + msgid "Edit Group" msgstr "تعديل المجموعة" -#: templates/groups/group_form.html:18 templates/groups/group_list.html:14 msgid "Add Group" msgstr "إضافة مجموعة" +msgid "Groups" +msgstr "المجموعات" + #: templates/groups/group_list.html:25 msgid "total Users" msgstr "إجمالي المستخدمين" @@ -5494,7 +5555,7 @@ msgstr "إجمالي الأذونات" msgid "actions" msgstr "الإجراءات" -#: templates/groups/group_list.html:40 templates/inventory/car_detail.html:133 +#: templates/groups/group_list.html:40 templates/inventory/car_detail.html:134 #: templates/inventory/car_inventory.html:150 #: templates/ledger/coa_accounts/account_detail.html:102 #: templates/representatives/representative_list.html:30 @@ -5516,30 +5577,45 @@ msgstr "تحرير الإذن" msgid "Add Permission" msgstr "إضافة إذن" +#: templates/haikalbot/chat.html:13 +msgid "HaikalBot" +msgstr "هيكل بوت" + +msgid "Export CSV" +msgstr "تصدير CSV" + +msgid "Type your question..." +msgstr "اكتب سؤالك..." + +msgid "Chart displayed below." +msgstr "تم عرض المخطط أدناه." + +msgid "Here is the data table." +msgstr "إليك جدول البيانات." + +msgid "Here is your result." +msgstr "إليك النتيجة." + #: templates/haikalbot/chatbot.html:5 msgid "Haikalbot" msgstr "هيكل بوت" -#: templates/haikalbot/chatbot.html:78 +#: templates/haikalbot/chatbot.html:82 msgid "How many cars are in inventory" msgstr "كم عدد السيارات في المخزون" -#: templates/haikalbot/chatbot.html:79 +#: templates/haikalbot/chatbot.html:83 msgid "Show me sales analysis" msgstr "اعرض لي تحليل المبيعات" -#: templates/haikalbot/chatbot.html:80 +#: templates/haikalbot/chatbot.html:84 msgid "What are the best-selling cars" msgstr "ما هي السيارات الأكثر مبيعًا" -#: templates/haikalbot/chatbot.html:85 +#: templates/haikalbot/chatbot.html:89 msgid "Type your message here" msgstr "اكتب رسالتك هنا" -#: templates/haikalbot/chatbot.html:346 -msgid "Copy" -msgstr "نسخ" - #: templates/header.html:27 msgid "add car" msgstr "إضافة سيارة" @@ -5608,9 +5684,7 @@ msgstr "دفاتر الأستاذ" msgid "Services" msgstr "الخدمات" -#: templates/header.html:239 templates/items/expenses/expense_create.html:5 -#: templates/items/expenses/expense_update.html:5 -#: templates/items/expenses/expenses_list.html:4 +#: templates/header.html:239 templates/items/expenses/expenses_list.html:4 #: templates/items/expenses/expenses_list.html:11 #: templates/ledger/reports/dashboard.html:48 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/includes/widget_ic.html:8 @@ -5636,6 +5710,7 @@ msgid "Cash Flow" msgstr "التدفق النقدي" #: templates/header.html:308 templates/ledger/ledger/ledger_detail.html:117 +#: templates/ledger/reports/income_statement.html:5 #: templates/ledger/reports/income_statement.html:31 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/bill_detail.html:146 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/bill_update.html:71 @@ -5648,6 +5723,7 @@ msgid "Income Statement" msgstr "بيان الدخل" #: templates/header.html:319 templates/ledger/ledger/ledger_detail.html:115 +#: templates/ledger/reports/balance_sheet.html:5 #: templates/ledger/reports/balance_sheet.html:36 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/bill_detail.html:144 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/bill_update.html:66 @@ -5689,12 +5765,12 @@ msgstr "إضافة لون" msgid "Select exterior and interior colors for" msgstr "اختر الألوان الخارجية والداخلية لـ" -#: templates/inventory/car_detail.html:3 templates/inventory/car_detail.html:71 +#: templates/inventory/car_detail.html:3 templates/inventory/car_detail.html:72 #: templates/inventory/car_history.html:3 msgid "Car Details" msgstr "تفاصيل السيارة" -#: templates/inventory/car_detail.html:24 +#: templates/inventory/car_detail.html:25 msgid "" "This car information is not complete , please add colors and finances before " "making it ready for sale ." @@ -5702,149 +5778,145 @@ msgstr "" "معلومات هذه السيارة غير مكتملة، يرجى إضافة الألوان المعلومات المالية قبل " "تجهيزها للبيع." -#: templates/inventory/car_detail.html:25 +#: templates/inventory/car_detail.html:26 msgid "Add Color" msgstr "إضافة لون" -#: templates/inventory/car_detail.html:37 +#: templates/inventory/car_detail.html:38 msgid "Action Required , Please Approved The Tranfer Request Of This Car ." msgstr "الإجراء مطلوب، يرجى الموافقة على طلب نقل هذه السيارة." -#: templates/inventory/car_detail.html:48 +#: templates/inventory/car_detail.html:49 msgid "" "Car Is In Transfer Process To Another Dealer, Please Wait For The " "Acceptance ." msgstr "السيارة قيد عملية النقل إلى تاجر آخر، يرجى انتظار القبول." -#: templates/inventory/car_detail.html:59 +#: templates/inventory/car_detail.html:60 msgid "This car is reserved until " msgstr "هذه السيارة محجوزة حتى " -#: templates/inventory/car_detail.html:80 templates/inventory/car_list.html:128 +#: templates/inventory/car_detail.html:81 templates/inventory/car_list.html:129 msgid "year" msgstr "السنة" -#: templates/inventory/car_detail.html:84 templates/inventory/car_form.html:91 +#: templates/inventory/car_detail.html:85 templates/inventory/car_form.html:92 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:70 -#: templates/inventory/car_list.html:88 +#: templates/inventory/car_list.html:89 msgid "make" msgstr "الصانع" -#: templates/inventory/car_detail.html:88 templates/inventory/car_form.html:114 +#: templates/inventory/car_detail.html:89 templates/inventory/car_form.html:115 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:83 -#: templates/inventory/car_list.html:106 +#: templates/inventory/car_list.html:107 msgid "model" msgstr "الموديل" -#: templates/inventory/car_detail.html:92 templates/inventory/car_list.html:139 +#: templates/inventory/car_detail.html:93 templates/inventory/car_list.html:140 msgid "series" msgstr "السلسلة" -#: templates/inventory/car_detail.html:96 templates/inventory/car_form.html:101 +#: templates/inventory/car_detail.html:97 templates/inventory/car_form.html:102 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:117 -#: templates/inventory/car_list.html:150 +#: templates/inventory/car_list.html:151 msgid "trim" msgstr "الفئة" -#: templates/inventory/car_detail.html:126 -#: templates/inventory/car_detail.html:536 -#: templates/inventory/car_form.html:125 templates/inventory/car_form.html:228 +#: templates/inventory/car_detail.html:127 +#: templates/inventory/car_detail.html:555 +#: templates/inventory/car_form.html:126 templates/inventory/car_form.html:229 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:196 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:225 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:248 -#: templates/inventory/car_list.html:48 templates/inventory/car_list.html:234 +#: templates/inventory/car_list.html:49 templates/inventory/car_list.html:235 msgid "specifications" msgstr "المواصفات" -#: templates/inventory/car_detail.html:185 +#: templates/inventory/car_detail.html:186 #: templates/inventory/car_inventory.html:124 msgid "Our Showroom" msgstr "معرضنا" -#: templates/inventory/car_detail.html:194 +#: templates/inventory/car_detail.html:195 msgid "No location available." msgstr "لا يوجد موقع متاح." -#: templates/inventory/car_detail.html:210 +#: templates/inventory/car_detail.html:211 msgid "Sell to another dealer" msgstr "بيع السيارة لمعرض آخر" -#: templates/inventory/car_detail.html:214 -#: templates/inventory/car_detail.html:267 +#: templates/inventory/car_detail.html:215 +#: templates/inventory/car_detail.html:268 msgid "Cannot Edit, Car in Transfer." msgstr "لا يمكن التعديل، السيارة قيد النقل." -#: templates/inventory/car_detail.html:222 +#: templates/inventory/car_detail.html:223 msgid "Financial Details" msgstr "التفاصيل المالية" -#: templates/inventory/car_detail.html:242 +#: templates/inventory/car_detail.html:243 msgid "Additional Fee" msgstr "رسوم إضافية" -#: templates/inventory/car_detail.html:254 +#: templates/inventory/car_detail.html:255 #: templates/plans/invoices/layout.html:111 msgid "VAT Amount" msgstr "مبلغ ضريبة القيمة المضافة" -#: templates/inventory/car_detail.html:270 +#: templates/inventory/car_detail.html:271 msgid "No finance details available." msgstr "لا توجد تفاصيل مالية متاحة." -#: templates/inventory/car_detail.html:283 +#: templates/inventory/car_detail.html:284 msgid "Colors Details" msgstr "تفاصيل الألوان" -#: templates/inventory/car_detail.html:289 +#: templates/inventory/car_detail.html:290 msgid "Exterior" msgstr "الخارجي" -#: templates/inventory/car_detail.html:299 +#: templates/inventory/car_detail.html:300 msgid "Interior" msgstr "الداخلي" -#: templates/inventory/car_detail.html:310 -msgid "No colors available for this car." -msgstr "لا تتوفر ألوان لهذه السيارة." - -#: templates/inventory/car_detail.html:327 +#: templates/inventory/car_detail.html:346 msgid "Reservations Details" msgstr "تفاصيل الحجز" -#: templates/inventory/car_detail.html:335 +#: templates/inventory/car_detail.html:354 msgid "Expires At" msgstr "ينتهي في" -#: templates/inventory/car_detail.html:377 +#: templates/inventory/car_detail.html:396 #: templates/inventory/reserve_car.html:22 msgid "Reserve" msgstr "حجز" -#: templates/inventory/car_detail.html:392 +#: templates/inventory/car_detail.html:411 #: templates/inventory/transfer_details.html:70 msgid "Transfer Details" msgstr "تفاصيل النقل" -#: templates/inventory/car_detail.html:400 +#: templates/inventory/car_detail.html:419 msgid "From Showroom" msgstr "من صالة العرض" -#: templates/inventory/car_detail.html:401 +#: templates/inventory/car_detail.html:420 msgid "To Showroom" msgstr "إلى صالة العرض" -#: templates/inventory/car_detail.html:505 +#: templates/inventory/car_detail.html:524 msgid "Are you sure you want to reserve this car?" msgstr "هل أنت متأكد أنك تريد حجز هذه السيارة؟" -#: templates/inventory/car_detail.html:651 -#: templates/inventory/car_list.html:548 +#: templates/inventory/car_detail.html:670 +#: templates/inventory/car_list.html:549 #: templates/partials/specifications_modal.html:11 msgid "No specifications available." msgstr "لا توجد مواصفات متاحة." -#: templates/inventory/car_detail.html:655 -#: templates/inventory/car_list.html:552 +#: templates/inventory/car_detail.html:674 +#: templates/inventory/car_list.html:553 msgid "Error loading specifications." msgstr "حدث خطأ أثناء تحميل المواصفات." @@ -5884,20 +5956,24 @@ msgstr "التفاصيل المالية السيارة" msgid "Finance Details for" msgstr "التفاصيل المالية لـ" -#: templates/inventory/car_form.html:23 +#: templates/inventory/car_form.html:3 +msgid "Add New Car" +msgstr "إضافة سيارة" + +#: templates/inventory/car_form.html:24 msgid "Please Add A Vendor, Before Adding A Car ." msgstr "يرجى إضافة مورد قبل إضافة السيارة." -#: templates/inventory/car_form.html:23 templates/vendors/vendor_form.html:20 +#: templates/inventory/car_form.html:24 templates/vendors/vendor_form.html:28 #: templates/vendors/vendors_list.html:15 msgid "Add Vendor" msgstr "إضافة مورد" -#: templates/inventory/car_form.html:80 templates/inventory/car_form.html:99 -#: templates/inventory/car_form.html:112 templates/inventory/car_form.html:268 -#: templates/inventory/car_form.html:457 templates/inventory/car_form.html:474 -#: templates/inventory/car_form.html:475 templates/inventory/car_form.html:497 -#: templates/inventory/car_form.html:516 +#: templates/inventory/car_form.html:81 templates/inventory/car_form.html:100 +#: templates/inventory/car_form.html:113 templates/inventory/car_form.html:269 +#: templates/inventory/car_form.html:458 templates/inventory/car_form.html:475 +#: templates/inventory/car_form.html:476 templates/inventory/car_form.html:498 +#: templates/inventory/car_form.html:517 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:88 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:104 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:121 @@ -5911,67 +5987,67 @@ msgstr "إضافة مورد" msgid "Select" msgstr "اختيار" -#: templates/inventory/car_form.html:131 +#: templates/inventory/car_form.html:132 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:202 -#: templates/inventory/car_list.html:25 templates/inventory/car_list.html:228 +#: templates/inventory/car_list.html:26 templates/inventory/car_list.html:229 msgid "options" msgstr "الخيارات" -#: templates/inventory/car_form.html:203 +#: templates/inventory/car_form.html:204 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:206 msgid "Save and Add Another" msgstr "حفظ وإضافة آخر" -#: templates/inventory/car_form.html:209 +#: templates/inventory/car_form.html:210 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:210 msgid "Save and Go to Inventory" msgstr "حفظ والانتقال إلى المخزون" -#: templates/inventory/car_form.html:254 +#: templates/inventory/car_form.html:255 #: venv/lib/python3.11/site-packages/click/core.py:1140 msgid "Options" msgstr "الخيارات" -#: templates/inventory/car_form.html:264 +#: templates/inventory/car_form.html:265 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:258 -#: templates/inventory/car_list.html:161 +#: templates/inventory/car_list.html:162 msgid "equipment" msgstr "التجهيزات" -#: templates/inventory/car_form.html:289 +#: templates/inventory/car_form.html:290 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:283 msgid "scanner" msgstr "الماسح الضوئي" -#: templates/inventory/car_form.html:298 +#: templates/inventory/car_form.html:299 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:292 #: templates/partials/scanner_modal.html:10 msgid "VIN will appear here." msgstr "رقم الهيكل سيظهر هنا." -#: templates/inventory/car_form.html:299 +#: templates/inventory/car_form.html:300 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:293 #: templates/partials/scanner_modal.html:11 msgid "Use OCR Fallback" msgstr "التعرف الآلي على الحروف" -#: templates/inventory/car_form.html:368 templates/inventory/car_form.html:369 +#: templates/inventory/car_form.html:369 templates/inventory/car_form.html:370 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:363 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:364 msgid "Please enter a valid VIN." msgstr "الرجاء إدخال رقم هيكل صالح مكون من 17 حرفًا." -#: templates/inventory/car_form.html:391 +#: templates/inventory/car_form.html:392 #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:386 msgid "An error occurred while decoding the VIN." msgstr "حدث خطأ أثناء فك تشفير الهيكل" #: ⁨templates/inventory/car_form_qabl alfalsafa.html⁩:45 -#: templates/inventory/car_list_view.html:144 +#: templates/inventory/car_list_view.html:145 #: templates/inventory/scan_vin.html:13 templates/partials/search_box.html:4 #: templates/representatives/representative_list.html:9 #: templates/representatives/representative_list.html:10 -#: templates/sales/sales_list.html:50 +#: templates/sales/sales_list.html:51 msgid "Search" msgstr "بحث" @@ -5996,54 +6072,54 @@ msgstr "لا توجد سيارات متاحة." msgid "Add a Car" msgstr "إضافة سيارة" -#: templates/inventory/car_list.html:79 +#: templates/inventory/car_list.html:80 #: templates/ledger/bills/bill_list.html:26 msgid "search" msgstr "بحث" -#: templates/inventory/car_list.html:90 templates/inventory/car_list.html:108 -#: templates/inventory/car_list.html:119 templates/inventory/car_list.html:130 -#: templates/inventory/car_list.html:141 templates/inventory/car_list.html:152 -#: templates/inventory/car_list.html:163 templates/inventory/car_list.html:174 -#: templates/inventory/car_list.html:186 templates/inventory/car_list.html:279 -#: templates/inventory/car_list.html:280 templates/inventory/car_list.html:281 -#: templates/inventory/car_list.html:282 templates/inventory/car_list.html:283 -#: templates/inventory/car_list.html:284 templates/inventory/car_list.html:384 -#: templates/inventory/car_list.html:385 templates/inventory/car_list.html:386 -#: templates/inventory/car_list.html:387 templates/inventory/car_list.html:388 -#: templates/inventory/car_list.html:413 templates/inventory/car_list.html:414 -#: templates/inventory/car_list.html:415 templates/inventory/car_list.html:416 -#: templates/inventory/car_list.html:442 templates/inventory/car_list.html:443 -#: templates/inventory/car_list.html:444 templates/inventory/car_list.html:469 -#: templates/inventory/car_list.html:470 templates/inventory/car_list.html:494 +#: templates/inventory/car_list.html:91 templates/inventory/car_list.html:109 +#: templates/inventory/car_list.html:120 templates/inventory/car_list.html:131 +#: templates/inventory/car_list.html:142 templates/inventory/car_list.html:153 +#: templates/inventory/car_list.html:164 templates/inventory/car_list.html:175 +#: templates/inventory/car_list.html:187 templates/inventory/car_list.html:280 +#: templates/inventory/car_list.html:281 templates/inventory/car_list.html:282 +#: templates/inventory/car_list.html:283 templates/inventory/car_list.html:284 +#: templates/inventory/car_list.html:285 templates/inventory/car_list.html:385 +#: templates/inventory/car_list.html:386 templates/inventory/car_list.html:387 +#: templates/inventory/car_list.html:388 templates/inventory/car_list.html:389 +#: templates/inventory/car_list.html:414 templates/inventory/car_list.html:415 +#: templates/inventory/car_list.html:416 templates/inventory/car_list.html:417 +#: templates/inventory/car_list.html:443 templates/inventory/car_list.html:444 +#: templates/inventory/car_list.html:445 templates/inventory/car_list.html:470 +#: templates/inventory/car_list.html:471 templates/inventory/car_list.html:495 msgid "select" msgstr "اختيار" -#: templates/inventory/car_list.html:117 +#: templates/inventory/car_list.html:118 msgid "generation" msgstr "الجيل" -#: templates/inventory/car_list.html:215 +#: templates/inventory/car_list.html:216 msgid "Enter remarks" msgstr "أدخل الملاحظات" -#: templates/inventory/car_list.html:235 +#: templates/inventory/car_list.html:236 msgid "save" msgstr "حفظ" -#: templates/inventory/car_list.html:351 +#: templates/inventory/car_list.html:352 msgid "Make not found for the decoded VIN." msgstr "لم يتم العثور على الشركة الصانعة الخاصة برقم الهيكل المدخل." -#: templates/inventory/car_list.html:359 +#: templates/inventory/car_list.html:360 msgid "Please enter a valid 17-character VIN number." msgstr "الرجاء إدخال رقم هيكل صالح مكون من 17 حرفًا." -#: templates/inventory/car_list.html:593 +#: templates/inventory/car_list.html:594 msgid "No options available." msgstr "لا توجد سيارات متاحة." -#: templates/inventory/car_list.html:597 +#: templates/inventory/car_list.html:598 msgid "Error loading options." msgstr "خطأ في تحميل الخيارات." @@ -6051,8 +6127,12 @@ msgstr "خطأ في تحميل الخيارات." msgid "Excel" msgstr "إكسل" -#: templates/inventory/car_list_view.html:41 -#: templates/inventory/car_list_view.html:129 +#: templates/inventory/car_list_view.html:3 +msgid "Stocks" +msgstr "المخزون" + +#: templates/inventory/car_list_view.html:42 +#: templates/inventory/car_list_view.html:130 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/bill_list.html:98 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/bill_list.html:110 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/closing_entry/closing_entry_list.html:76 @@ -6070,16 +6150,25 @@ msgstr "إكسل" msgid "All" msgstr "الكل" -#: templates/inventory/car_list_view.html:61 +#: templates/inventory/car_list_view.html:62 msgid "Filter" msgstr "تصفية" -#: templates/inventory/car_list_view.html:168 +#: templates/inventory/car_list_view.html:169 msgid "Date Received" msgstr "تاريخ الاستلام" -#: templates/inventory/car_list_view.html:232 -#: templates/sales/sales_list.html:207 +msgid "Inventory Ready" +msgstr "جاهز للمخزون" + +msgid "NO" +msgstr "لا" + +msgid "YES" +msgstr "نعم" + +#: templates/inventory/car_list_view.html:245 +#: templates/sales/sales_list.html:208 msgid "Export" msgstr "تصدير" @@ -6195,7 +6284,7 @@ msgstr "من" #: templates/inventory/transfer_details.html:90 #: templates/plans/invoices/layout.html:110 -#: templates/plans/order_detail_table.html:10 templates/pricing_page.html:180 +#: templates/plans/order_detail_table.html:10 templates/pricing_page.html:181 #: templates/sales/estimates/sale_order_preview.html:205 #: templates/sales/invoices/invoice_detail.html:267 msgid "VAT" @@ -6271,11 +6360,16 @@ msgstr "إذا كان لديك أي أسئلة، يرجى التواصل معن msgid "Thank you for your business" msgstr "شكراً لتعاملك معنا" +#: templates/items/expenses/expense_create.html:5 +msgid "Add New Expense" +msgstr "إضافة مصروف جديد" + #: templates/items/expenses/expense_create.html:11 #: templates/items/expenses/expenses_list.html:12 msgid "Add Expense" msgstr "إضافة مصروف" +#: templates/items/expenses/expense_update.html:5 #: templates/items/expenses/expense_update.html:11 msgid "Update Expense" msgstr "تحديث المصروف" @@ -6295,20 +6389,25 @@ msgid "Unit of Measure" msgstr "وحدة القياس" #: templates/items/expenses/expenses_list.html:58 -#: templates/items/service/service_list.html:63 +#: templates/items/service/service_list.html:59 #: templates/ledger/bank_accounts/bank_account_list.html:48 #: templates/ledger/coa_accounts/account_list.html:107 #: templates/vendors/vendors_list.html:138 msgid "No Accounts Found" msgstr "لم يتم العثور على أي حسابات" -#: templates/items/service/service_create.html:13 +#: templates/items/service/service_create.html:9 +#: templates/items/service/service_create.html:22 #: venv/lib/python3.11/site-packages/appointment/views_admin.py:472 #: venv/lib/python3.11/site-packages/django_ledger/views/item.py:340 msgid "Update Service" msgstr "تحديث الخدمة" -#: templates/items/service/service_create.html:15 +#: templates/items/service/service_create.html:11 +msgid "Add New Service" +msgstr "إضافة خدمة جديدة" + +#: templates/items/service/service_create.html:24 #: templates/items/service/service_list.html:11 #: venv/lib/python3.11/site-packages/appointment/views_admin.py:465 msgid "Add Service" @@ -6343,15 +6442,20 @@ msgstr "اسم الحساب البنكي" msgid "Cash Account" msgstr "حساب نقدي" -#: templates/ledger/bank_accounts/bank_account_form.html:4 -msgid "bank account" -msgstr "الحساب المصرفي" +#: templates/ledger/bank_accounts/bank_account_form.html:7 +#: venv/lib/python3.11/site-packages/django_ledger/views/bank_account.py:76 +msgid "Update Bank Account" +msgstr "تحديث الحساب المصرفي" -#: templates/ledger/bank_accounts/bank_account_form.html:15 +#: templates/ledger/bank_accounts/bank_account_form.html:9 +msgid "Add New Bank Account" +msgstr "إضافة حساب بنكي جديد" + +#: templates/ledger/bank_accounts/bank_account_form.html:23 msgid "Edit Bank Account" msgstr "تحديث الحساب المصرفي" -#: templates/ledger/bank_accounts/bank_account_form.html:18 +#: templates/ledger/bank_accounts/bank_account_form.html:26 #: templates/ledger/bank_accounts/bank_account_list.html:11 msgid "Add Bank Account" msgstr "إضافة حساب بنكي" @@ -6441,7 +6545,7 @@ msgstr "تاريخ الفاتورة" #: templates/ledger/bills/bill_detail.html:180 #: templates/sales/estimates/sale_order_form.html:42 #: templates/sales/invoices/invoice_detail.html:201 -#: templates/sales/sales_list.html:109 +#: templates/sales/sales_list.html:110 msgid "Customer Name" msgstr "اسم العميل" @@ -6598,15 +6702,20 @@ msgstr "دائن" msgid "JE Number" msgstr "رقم القيد المحاسبي" -#: templates/ledger/coa_accounts/account_form.html:4 -msgid "account" -msgstr "الحساب" +#: templates/ledger/coa_accounts/account_form.html:8 +#: venv/lib/python3.11/site-packages/django_ledger/views/account.py:153 +msgid "Update Account" +msgstr "تحديث الحساب" -#: templates/ledger/coa_accounts/account_form.html:14 +#: templates/ledger/coa_accounts/account_form.html:10 +msgid "Add New Account" +msgstr "إضافة حساب جديد" + +#: templates/ledger/coa_accounts/account_form.html:22 msgid "Edit Account" msgstr "تعديل الحساب" -#: templates/ledger/coa_accounts/account_form.html:17 +#: templates/ledger/coa_accounts/account_form.html:25 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/chart_of_accounts/includes/coa_card.html:58 msgid "Add Account" msgstr "إضافة حساب" @@ -6917,6 +7026,7 @@ msgid "Unit Cost" msgstr "تكلفة الوحدة" #: templates/ledger/ledger/ledger_detail.html:119 +#: templates/ledger/reports/cash_flow_statement.html:5 #: templates/ledger/reports/cash_flow_statement.html:31 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/bill_detail.html:148 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/financial_statements/cash_flow.html:31 @@ -7422,8 +7532,13 @@ msgstr "وضع علامة مقروء على الكل" msgid "Organization Details" msgstr "تفاصيل الشركة" -#: templates/organizations/organization_form.html:4 -#: templates/organizations/organization_form.html:8 +msgid "Update Organization" +msgstr "تحديث المؤسسة" + +msgid "Add New Organization" +msgstr "إضافة مؤسسة جديدة" + +#: templates/organizations/organization_form.html:15 msgid "Add Organization" msgstr "إضافة شركة" @@ -7626,7 +7741,7 @@ msgid "Your Account" msgstr "حسابك" #: templates/plans/current.html:42 templates/plans/extend.html:24 -#: templates/pricing_page.html:178 +#: templates/pricing_page.html:179 msgid "Plan" msgstr "الخطة" @@ -7705,7 +7820,7 @@ msgid "Pricings" msgstr "السعر" #: templates/plans/extend.html:44 templates/plans/plan_table.html:95 -#: templates/pricing_page.html:67 +#: templates/pricing_page.html:68 msgid "days" msgstr "أيام" @@ -7782,7 +7897,7 @@ msgstr "وحدات" msgid "n/a" msgstr "غير متوفر" -#: templates/plans/invoices/layout.html:151 templates/pricing_page.html:131 +#: templates/plans/invoices/layout.html:151 templates/pricing_page.html:132 msgid "Payment Information" msgstr "معلومات الدفع" @@ -7810,6 +7925,10 @@ msgstr "تم تطبيق الضريبة العكسية" msgid "If you have any questions about this invoice, please contact us" msgstr "إذا كانت لديك أي أسئلة بخصوص هذه الفاتورة، يرجى الاتصال بنا" +#: templates/plans/order_detail.html:3 +msgid "Order Details" +msgstr "تفاصيل الطلب" + #: templates/plans/order_detail.html:18 #, python-format msgid "" @@ -7823,7 +7942,7 @@ msgstr "" msgid "Printable documents" msgstr "مستندات قابلة للطباعة" -#: templates/plans/order_detail.html:40 templates/pricing_page.html:190 +#: templates/plans/order_detail.html:40 templates/pricing_page.html:191 msgid "Payment" msgstr "الدفع" @@ -7894,6 +8013,10 @@ msgstr "ضريبة القيمة المضافة في الاتحاد الأورو msgid " VAT is not applied to order. " msgstr "لم يتم تطبيق الضريبة على الطلب." +#: templates/plans/order_list.html:3 +msgid "Plans Orders" +msgstr "طلبات الخطط" + #: templates/plans/order_list.html:7 msgid "List of orders" msgstr "قائمة الطلبات" @@ -7986,51 +8109,55 @@ msgstr "اطلع على خططنا ذات القيمة الرائعة" msgid "Choose plan" msgstr "اختر الخطة" -#: templates/pricing_page.html:52 +#: templates/pricing_page.html:4 +msgid "Upgrade Plan" +msgstr "ترقية الخطة" + +#: templates/pricing_page.html:53 msgid "Choose Your Plan" msgstr "اختر خطتك" -#: templates/pricing_page.html:57 +#: templates/pricing_page.html:58 msgid "Select a Plan" msgstr "اختر خطة" -#: templates/pricing_page.html:68 templates/welcome.html:109 +#: templates/pricing_page.html:69 templates/welcome.html:109 msgid "Included" msgstr "متضمن" -#: templates/pricing_page.html:89 +#: templates/pricing_page.html:90 msgid "Enter Your Information" msgstr "أدخل معلوماتك" -#: templates/pricing_page.html:134 templates/pricing_page.html:137 +#: templates/pricing_page.html:135 templates/pricing_page.html:138 msgid "Cardholder Name" msgstr "اسم حامل البطاقة" -#: templates/pricing_page.html:152 templates/pricing_page.html:155 +#: templates/pricing_page.html:153 templates/pricing_page.html:156 msgid "Expiry Date" msgstr "تاريخ الانتهاء" -#: templates/pricing_page.html:162 templates/pricing_page.html:165 +#: templates/pricing_page.html:163 templates/pricing_page.html:166 msgid "CVV" msgstr "رمز الأمان (CVV)" -#: templates/pricing_page.html:175 +#: templates/pricing_page.html:176 msgid "Confirm Your Information" msgstr "تأكيد معلوماتك" -#: templates/pricing_page.html:177 +#: templates/pricing_page.html:178 msgid "Order Summary" msgstr "ملخص الطلب" -#: templates/pricing_page.html:184 +#: templates/pricing_page.html:185 msgid "User Information" msgstr "معلومات المستخدم" -#: templates/pricing_page.html:191 +#: templates/pricing_page.html:192 msgid "Cardholder" msgstr "حامل البطاقة" -#: templates/pricing_page.html:193 +#: templates/pricing_page.html:194 msgid "Expiry" msgstr "الانتهاء" @@ -8053,6 +8180,7 @@ msgid "Are you sure you want to Cancel this Estimate?" msgstr "هل أنت متأكد أنك تريد إلغاء هذا التقدير؟" #: templates/sales/estimates/estimate_detail.html:97 +#: templates/sales/invoices/invoice_detail.html:5 msgid "View Invoice" msgstr "عرض الفاتورة" @@ -8180,10 +8308,6 @@ msgstr "تحديث الفاتورة" msgid "Add Invoice" msgstr "إضافة فاتورة" -#: templates/sales/invoices/invoice_detail.html:5 -msgid "View Estimate" -msgstr "عرض التقدير" - #: templates/sales/invoices/invoice_detail.html:86 msgid "Accept" msgstr "قبول" @@ -8213,7 +8337,7 @@ msgid "Invoice Status" msgstr "حالة الفاتورة" #: templates/sales/invoices/invoice_list.html:30 -#: templates/sales/sales_list.html:186 +#: templates/sales/sales_list.html:187 #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/bills/tags/bill_table.html:15 msgid "Past Due" msgstr "مستحق" @@ -8277,11 +8401,11 @@ msgstr "عرض المعالملات" msgid "No Payments Found" msgstr "لم يتم العثور على عملية دفع" -#: templates/sales/sales_list.html:110 +#: templates/sales/sales_list.html:111 msgid "Customer Address" msgstr "عنوان العميل" -#: templates/sales/sales_list.html:111 +#: templates/sales/sales_list.html:112 msgid "Customer Phone" msgstr "هاتف العميل" @@ -8617,11 +8741,19 @@ msgstr "لا يوجد مجموعة" msgid "Are you sure you want to delete this user?" msgstr "هل أنت متأكد أنك تريد حذف هذا المستخدم؟" -#: templates/users/user_form.html:17 +#: templates/users/user_form.html:7 +msgid "Update Staff" +msgstr "تحديث الموظف" + +#: templates/users/user_form.html:9 templates/users/user_list.html:15 +msgid "Add New Staff" +msgstr "إضافة موظف جديد" + +#: templates/users/user_form.html:24 msgid "Edit Staff" msgstr "تعديل الموظف" -#: templates/users/user_form.html:19 +#: templates/users/user_form.html:26 msgid "Add Staff" msgstr "إضافة موظف" @@ -8629,9 +8761,9 @@ msgstr "إضافة موظف" msgid "Manage Groups" msgstr "إدارة المجموعات" -#: templates/users/user_list.html:15 -msgid "Add New Staff" -msgstr "إضافة موظف جديد" +#: templates/users/user_list.html:5 +msgid "Staffs" +msgstr "الموظفون" #: templates/users/user_list.html:16 msgid "Manage Groups & Permissions" @@ -8649,7 +8781,17 @@ msgstr "رقم الهاتف" msgid "role" msgstr "الدور" -#: templates/vendors/vendor_form.html:17 +#: templates/vendors/vendor_form.html:7 +#: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/vendor/vendor_update.html:10 +#: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/vendor/vendor_update.html:22 +msgid "Update Vendor" +msgstr "تحديث المورد" + +#: templates/vendors/vendor_form.html:9 +msgid "Add New Vendor" +msgstr "مورد جديد" + +#: templates/vendors/vendor_form.html:25 msgid "Edit Vendor" msgstr "تعديل مورد" @@ -13206,11 +13348,6 @@ msgstr "إنشاء عميل" msgid "New Customer" msgstr "عميل جديد" -#: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/customer/customer_update.html:11 -#: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/customer/customer_update.html:23 -msgid "Update Customer" -msgstr "تحديث العميل" - #: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/customer/includes/card_customer.html:9 msgid "Customer Information" msgstr "معلومات العميل" @@ -13658,11 +13795,6 @@ msgstr "إنشاء مورد" msgid "New Vendor" msgstr "مورد جديد" -#: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/vendor/vendor_update.html:10 -#: venv/lib/python3.11/site-packages/django_ledger/templates/django_ledger/vendor/vendor_update.html:22 -msgid "Update Vendor" -msgstr "تحديث المورد" - #: venv/lib/python3.11/site-packages/django_ledger/views/account.py:72 msgid "Entity Accounts" msgstr "حسابات الكيان" @@ -13671,14 +13803,6 @@ msgstr "حسابات الكيان" msgid "WARNING: The chart of accounts list is inactive." msgstr "تحذير: قائمة دليل الحسابات غير نشطة." -#: venv/lib/python3.11/site-packages/django_ledger/views/account.py:153 -msgid "Update Account" -msgstr "تحديث الحساب" - -#: venv/lib/python3.11/site-packages/django_ledger/views/bank_account.py:76 -msgid "Update Bank Account" -msgstr "تحديث الحساب المصرفي" - #: venv/lib/python3.11/site-packages/django_ledger/views/chart_of_accounts.py:52 msgid "Create Chart of Account" msgstr "إنشاء دليل الحسابات" diff --git a/static/.DS_Store b/static/.DS_Store index 240a11ab8b0c76b1d83ad568db4c52e27a81f7fd..8d610e136023724024ad787e84fd24179644ea55 100644 GIT binary patch delta 448 zcmYk%Pe{{o9LDkQ`&-VG35iUn5>5yzoT9V+HqvNTW=cq!f0{<7Q$$c_8`_~i{=Gy< zb6@Ka8F(qh3hM_ZCJ9Mc9lA(VAcBM)Ebu7i#hP53?62ZoxueHJNZxzPvEA-U#MbvVh8>f16EXh<+SygTJM%2Ul_xu16vadUXOtQ%1 zJcS0koLa6KZzB`%kRfI59jmyeLvm3=poIJ(UeT-WG9K7)Ic(I(HZKe5gMhtG(nT}hNkHYg`mJfXpn+c$U+V_pa{h% zLpipf3f0(wov48u9yDSPn&91s{Wyq2Xva|;!*QIzN%Y|y&f^05aT$ZSf~&ZWAu--f z+`%aB;XcMNj>ib%6(+-g3=z=szrpcArEx_&eU0tzpgJ(DoKgXSzSzPAq6Cx5llkiT z{&;JS*{HXyH61qUJJwlDMt!c;#=DkTS172Fn#n~Ta+9y#SLdyjW$(|Q{&?WoCR^f? zK@?&|sTT zpwo>8>=qWZ;D8XI9UbVxDV)Yx^vp#+Ai}?f07h^NxA73q@Ej9(jd%En8GOQL%(BHy s%`_~L8QB_^#j=^56|y4c2ygVa65Asijucj1CpYiqo3qCQO6i;O2Xr^I&j0`b diff --git a/static/icons/HaikalAi.ai b/static/icons/HaikalAi.ai new file mode 100644 index 00000000..42c72a1a --- /dev/null +++ b/static/icons/HaikalAi.ai @@ -0,0 +1,1356 @@ +%PDF-1.6 % +1 0 obj <>/OCGs[22 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + HaikalAi + + + Adobe Illustrator 29.5 (Macintosh) + 2025-05-28T01:01:54+03:00 + 2025-05-28T01:01:54+03:00 + 2025-05-28T01:01:54+03:00 + + + + 256 + 196 + JPEG + /9j/4AAQSkZJRgABAgEAAAAAAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAAAAAAAAEA AQAAAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAxAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FUPqGo2GnWct7qFzFaWcI5TXE7rHGg8WZiAMaW3jPm3/nKnyhpksltoFlNrcyGn1gt9Wtq/ 5LMryNT/AFAD2OWjEerUco6IT8v/APnKGLXfMlpout6QlguoSrBa3kEpdVlkIWNZEZRszGnIHbw7 4ZYqCI5bL3nKW52KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2K uxV2KsU/MP8AMny55F0c32qyc7mUEWOnxketO4psoPRRX4mOw9zQGUYksZSAfHP5hfmb5n886mbr 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 xV2Kv//Z + + + + proof:pdf + uuid:65E6390686CF11DBA6E2D887CEACB407 + xmp.did:4e1c18b4-94e0-457b-890a-5230a991a191 + uuid:4e05d1e4-9d06-054b-9f56-db9a7696cc69 + + uuid:71d3b25e-453d-1249-aa45-a002d99380b3 + xmp.did:f1cee0fc-fdf1-44c6-b699-76b3dca1e609 + uuid:65E6390686CF11DBA6E2D887CEACB407 + proof:pdf + + + + + saved + xmp.iid:4e1c18b4-94e0-457b-890a-5230a991a191 + 2025-05-27T18:21:53+03:00 + Adobe Illustrator 29.5 (Macintosh) + / + + + + Web + Document + AIRobin + 1 + False + False + + 1920.000000 + 1080.000000 + Pixels + + + + Cyan + Magenta + Yellow + Black + + + + + + Default Swatch Group + 0 + + + + White + RGB + PROCESS + 255 + 255 + 255 + + + Black + RGB + PROCESS + 0 + 0 + 0 + + + RGB Red + RGB + PROCESS + 255 + 0 + 0 + + + RGB Yellow + RGB + PROCESS + 255 + 255 + 0 + + + RGB Green + RGB + PROCESS + 0 + 255 + 0 + + + RGB Cyan + RGB + PROCESS + 0 + 255 + 255 + + + RGB Blue + RGB + PROCESS + 0 + 0 + 255 + + + RGB Magenta + RGB + PROCESS + 255 + 0 + 255 + + + R=193 G=39 B=45 + RGB + PROCESS + 193 + 39 + 45 + + + R=237 G=28 B=36 + RGB + PROCESS + 237 + 28 + 36 + + + R=241 G=90 B=36 + RGB + PROCESS + 241 + 90 + 36 + + + R=247 G=147 B=30 + RGB + PROCESS + 247 + 147 + 30 + + + R=251 G=176 B=59 + RGB + PROCESS + 251 + 176 + 59 + + + R=252 G=238 B=33 + RGB + PROCESS + 252 + 238 + 33 + + + R=217 G=224 B=33 + RGB + PROCESS + 217 + 224 + 33 + + + R=140 G=198 B=63 + RGB + PROCESS + 140 + 198 + 63 + + + R=57 G=181 B=74 + RGB + PROCESS + 57 + 181 + 74 + + + R=0 G=146 B=69 + RGB + PROCESS + 0 + 146 + 69 + + + R=0 G=104 B=55 + RGB + PROCESS + 0 + 104 + 55 + + + R=34 G=181 B=115 + RGB + PROCESS + 34 + 181 + 115 + + + R=0 G=169 B=157 + RGB + PROCESS + 0 + 169 + 157 + + + R=41 G=171 B=226 + RGB + PROCESS + 41 + 171 + 226 + + + R=0 G=113 B=188 + RGB + PROCESS + 0 + 113 + 188 + + + R=46 G=49 B=146 + RGB + PROCESS + 46 + 49 + 146 + + + R=27 G=20 B=100 + RGB + PROCESS + 27 + 20 + 100 + + + R=102 G=45 B=145 + RGB + PROCESS + 102 + 45 + 145 + + + R=147 G=39 B=143 + RGB + PROCESS + 147 + 39 + 143 + + + R=158 G=0 B=93 + RGB + PROCESS + 158 + 0 + 93 + + + R=212 G=20 B=90 + RGB + PROCESS + 212 + 20 + 90 + + + R=237 G=30 B=121 + RGB + PROCESS + 237 + 30 + 121 + + + R=199 G=178 B=153 + RGB + PROCESS + 199 + 178 + 153 + + + R=153 G=134 B=117 + RGB + PROCESS + 153 + 134 + 117 + + + R=115 G=99 B=87 + RGB + PROCESS + 115 + 99 + 87 + + + R=83 G=71 B=65 + RGB + PROCESS + 83 + 71 + 65 + + + R=198 G=156 B=109 + RGB + PROCESS + 198 + 156 + 109 + + + R=166 G=124 B=82 + RGB + PROCESS + 166 + 124 + 82 + + + R=140 G=98 B=57 + RGB + PROCESS + 140 + 98 + 57 + + + R=117 G=76 B=36 + RGB + PROCESS + 117 + 76 + 36 + + + R=96 G=56 B=19 + RGB + PROCESS + 96 + 56 + 19 + + + R=66 G=33 B=11 + RGB + PROCESS + 66 + 33 + 11 + + + + + + Grays + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=26 G=26 B=26 + RGB + PROCESS + 26 + 26 + 26 + + + R=51 G=51 B=51 + RGB + PROCESS + 51 + 51 + 51 + + + R=77 G=77 B=77 + RGB + PROCESS + 77 + 77 + 77 + + + R=102 G=102 B=102 + RGB + PROCESS + 102 + 102 + 102 + + + R=128 G=128 B=128 + RGB + PROCESS + 128 + 128 + 128 + + + R=153 G=153 B=153 + RGB + PROCESS + 153 + 153 + 153 + + + R=179 G=179 B=179 + RGB + PROCESS + 179 + 179 + 179 + + + R=204 G=204 B=204 + RGB + PROCESS + 204 + 204 + 204 + + + R=230 G=230 B=230 + RGB + PROCESS + 230 + 230 + 230 + + + R=242 G=242 B=242 + RGB + PROCESS + 242 + 242 + 242 + + + + + + Web Color Group + 1 + + + + R=63 G=169 B=245 + RGB + PROCESS + 63 + 169 + 245 + + + R=122 G=201 B=67 + RGB + PROCESS + 122 + 201 + 67 + + + R=255 G=147 B=30 + RGB + PROCESS + 255 + 147 + 30 + + + R=255 G=29 B=37 + RGB + PROCESS + 255 + 29 + 37 + + + R=255 G=123 B=172 + RGB + PROCESS + 255 + 123 + 172 + + + R=189 G=204 B=212 + RGB + PROCESS + 189 + 204 + 212 + + + + + + + Adobe PDF library 17.00 + 21.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 3 0 obj <> endobj 5 0 obj <>/ExtGState<>/Properties<>>>/Thumb 27 0 R/TrimBox[0.0 0.0 1920.0 1080.0]/Type/Page/PieceInfo<>>> endobj 24 0 obj <>stream +H,M1 } +_eɖB#~6 _sz㲫y,O{,߫c[M\O_Jo}&O]Z>\k> s&/fis7yxyr=›kckiyJu`fFކ<6H*^)m׮m9o]Ю喴@!/T:# c! *z4I +ۉVe εU[H 8ިɵ}j!n-AN\ GZG58\FkUh1\$j6D9W7/|B}؀ǹL2Ӏ/0.qL +g* 9EP)p !I [jl/ldbaeܽ&n>>pxy<ЈskHvwLJɦIhHNLXP>7äŦ]$ /xfqsb:$@0XsX{62x}Grڕmڞ&׼j\SH@(]դb?m7)q~@Nb5M7mصbYnd1 )W )>Mr(61TSeB[CV\e(UnNKz Tj:ϗ}< +]`,?=FKC XqG t259RVֻlŠg4?{j]7Cʺ 8bSVfu\>qIL endstream endobj 27 0 obj <>stream +8;Z]__$Rn"&4BAX9egG`Xk6CSWtA7F#U05%TXS>IFUS6M!;r"kNo[QDSB+R+f2No3 +:sGcV\XG=PB1(tM1BuATZ_`$Aj^^`bV+b'n6gT!ahq?Fmkd>JFH)>dQo!S,FTs3;$48%_:!!%E$"TSQ&rr<$!s8N0$)V+RB~> endstream endobj 8 0 obj <> endobj 9 0 obj <> endobj 10 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 24.0 %%AI8_CreatorVersion: 29.5.1 %%For: (Marwan Alwali) () %%Title: (HaikalAi.ai) %%CreationDate: 28/05/2025 01:01 %%Canvassize: 16383 %%BoundingBox: 810 -656 1113 -428 %%HiResBoundingBox: 810.932355970144 -655.955100126255 1112.65976072465 -428.509843147501 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 14.0 %AI12_BuildNumber: 141 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: 0 -1080 1920 0 %AI3_TemplateBox: 960.5 -540.5 960.5 -540.5 %AI3_TileBox: 557 -819.5 1340 -260.5 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 6 %AI24_LargeCanvasScale: 1 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI17_Begin_Content_if_version_gt:24 4 %AI10_OpenToVie: 735.133997426618 -402.085948661937 2.79807187933183 0 8182.7219482007 8228.38741743816 1788 1022 18 0 0 6 64 0 0 0 1 1 0 1 1 0 1 %AI17_Alternate_Content %AI9_OpenToView: 735.133997426618 -402.085948661937 2.79807187933183 1788 1022 18 0 0 6 64 0 0 0 1 1 0 1 1 0 1 %AI17_End_Versioned_Content %AI5_OpenViewLayers: 7 %AI17_Begin_Content_if_version_gt:24 4 %AI17_Alternate_Content %AI17_End_Versioned_Content %%PageOrigin:560 -840 %AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 11 0 obj <>stream +%AI24_ZStandard_Data(/Xm.J +PRl+p +lу`^Z\qߎfP% @ FO#_J?H[.p\b|Imݜ]R%U808>p`l d:Y0q 389+(+hGՐLn׭4vJgU +JIDVN2 +` "FN՘d U)*zhBa$"nյF7g>d63!ta2,L"JӍk:VYǸ2q!a9{ʮF?G(cd@ CaO( + a3O8,"Q# +RŠP) A ,xñC146Ίa@{4$k+[q#0660d0Y]X(cZqKqHbjA +:EX+t04@ M}FKJsI̜ Z ̨cn(9³e+K n=APiki &%&@(8sX<Lļ\`XBCE B1IT`xLD4p|p}6pK\la +KvfVV[W˪ L Uim"TTS Et0t|"YQ0CѮe%J 0ӅbA9 s N ` D2L&] 1(袆BD DO4\zVa YbI bscu12`\8aP 8(4\0&8,@A,VPIࠠ:  +TA $l B$p @(@ $@` Lhhp !p!B4AB@&L`p¡ @,T Al@&8(8(T + 8:4`pP&T0l`qXhpB @ "H`2@„ +ȀA !B4(&8(8,@@$@  +0 ! 8&8(xqAC $Xp!BC&0*pB@A   &@A: $X +x .4H +*X(4dAh4 (4\aYjCo'% $X8$` 0(  \@a! (Xh0k>=G&p k%IPK(cV8y:^;#4V\aA +T<4F\Ap@ƒ .Px@A X ,42ya 4h ,@@!L``&4\P?,h!0pp`@ (PBÄ "0c&  *8@ B`'oVp 8 +.@ &𠎡Vpa!T@(T d`9,@ sX$\  d㰠PA%B2L5 d`C ."@8DPa„ .PhB +H8,B h\А*(e99j{ܑ3ܹì+ $ਸ਼<,A$\@a +r J A Fǂq44$4\PA 4L aLpP aL8 LpX`FtX@ ,4TA&1btP ,T 60!a + 4XH8p`#UA*\p@F4Ieir6y'Z] 6GDO;UJ_DrՙM~,·=4UdYs}u/g+ ;KvG9̻43INN_4D*WgVZh+<~Ϫͧ qz tuT=3Re !U==>ڝd?DrrEر^Guk{OޱΒasYQgxG&ѭ_ݣWfǪ%[Q_m&ǯb.ͷq]r7 I!ZTѐG2kv؜_fS=,W^s̶y$V#¦EMZ}, yʞx%G>y#b9ڲ5i=[%)ُ\9??h_2+ kiso)5tB'MutTg?l_!M̴MC*Lg'td˛SuiggS:R42:]V,D/*!_eވ!MQR#$Bd;N$o;ґ_%4""}}$JA JI:U߰Vba+<-] ֓"jgzϱ-8dz㻧[G\/htn+D'ϓe. IǫEm~+U=7hjԔY'}2$U̖JV;iwiz>C흷'I6sɽf^Ch Ov/EeK]=~/DxrC<#JD +g0!>g#%SXb}?ߤ53&32m{|g*Z5/bMIjjWeDR3(&ݟet,[1aRY{6彈si{%ݻ +Y=*Ց^In$*^og.?kmWdkկl1}L9%闝h/U[T$WH|O=yؔH3㑡qJS2h9L*խNG][RHG4?}*w%Lja|ZpznKц,6)f2!콤Ιa7iMZ]\vot)D=[Y>x'}u&([XGv/HGxLY~K$V)H8iKUBo+n%>SۜбHIu2S3+R>Z$D=hI/Rg4lZȵϣdO,b,mQ]DJ߆'G=z*otzajYcŬhS/M W*󓊤ļ+YX'hEܕ*nŦ|Rҭ,}NIǡ-i&{m0 z7 .I꠴$5iiK0 Os=g.~x)D[KdY|MZ@}rMُQ[SrQ>qQCC^'Ώ^ꞖhTobՄ^QC&JP CG^ eJSWuiҁX3ɡ2liRfC<ܽqtg#L7k+4K,=7w:U=zޙJXr]=|̢{١7;:/H8Tr)e=.7"Y?3UVկc ]3n)eVyc/'dKtULJ|T^[dxH:tQG_2hBC6{XVݑIJ@#?O_rN*ч-ˬd{ ȝ6WpK⁇#9yɞXw&Z=;,5G[ٽv9i7 KGo)&;vdd51c#_I Մ~<вjI+ h:,]܏~KpڐvF{Gpi-IRn0<}P,$tg"˕nfݤ,e\[7Dezdv9*GgٕjI%ox&d=j:bYN<bi|&s&ՕEKϝ-e_' V: C+$@j/,wiH^ FAMKQ},ᤒF~QN89;q&s,p [8<4D *<  4 IP3ېhn! ̹6'Ol+GLI(Rw7fncTzKgUzճyO3;Cu;0-:j,ȂDxaTGYYbO.Qƛ`cyymqXt-:jz>ӡ6QÔ>}z%tb>zQ)/MG\L<15YBFoṖdcio[+Ui9;p̊$meg|y= M8g2=%;YY\72=,:E'vdUZqe!:/utYH{QFr=75IB9}&WeԎYʏC;7Y=q ACWUQJj]ЎQ|&h:)fGmSAd3&iggrqkbzfXbXhO&Iґs*D›‘0NV'DH3"T9 qjt$\F#땻Ҋt,ɤ ˖醶t|nW)m +}IWѯBCjA9ftdd|+`L䎇$;ֳ hT^H9DeīۑrYXbU; QkAΤme#Ďv|ccŘ5QCxm,h#٣S4k kxeMGϬW'2KwLA?6JtNΤ%ٟ_U5C)^vqoRudѤy59+ґSxgW]H&Gdԕ(`p5t[J"|VE9I42E:l2u9+}$WNUR0L3dP%A# ?iv肉2V;[\kNziI%ۡI$e:$)M5$'!Jvb><7+avњov\ǿk;U3m 6;jӢihyd^sեOs眼*Ŧy.׉;Usf¾=gG7^=9j6vn|ؔFս#Z9=6YJJN-f6F;zީ~B7r}>lu)d=Ҹn&IʇhDqxDFx읺\1юw9C3w^Ir>E =h3y2xL>y7 3wqgfcXSxئ|yy\P$ήjv&éʔGX#q|N't(;f +2-^cٟ'Pi)bJwkIG8Fgjx'*XCKB$.?f=1?}6Y5]5Fx̒|:#M$cU˺̟؝#BVG>E#tĪ*?}тH,jRX{6?ex[:4lOu^bkP(ISʇfbf.:|fH2}qډ +aa +J'IIu2DKNkT)Y<RwqaSnjcEw,\B]DX{3,3YM(FfsEttQ^dIt~JyɢgaQGbeFSׯHZT")Lj1iĐ*i鯎^jemZEFR;XB4R5tv[\s&Vj&b%>oM`)3P|:̥%itPwuN\V3ԏ$W:21#\YEBRDmϰ쬏kSRJ/KZ[̵{5cIѨvI{fTc1H,3є9̿-GF4tˋou=ovCĥL&φXO+YFmH7U2;!l,>;5DŽ=^?<#U2[CLW|d/Xz)3UIg6aP|hjӫ<0OB#>)}ҘݠQ˪m߿L:"t}]Mdس⺉L\bHiH9(uib]Z֜Rc}دYeͰZ:Ҫl&sH>YަlC)UQl$ywHa[ Joeܪ&oEc֕E9˙h:/W> O%,a'㳆oI2T=t͙d<hBK$ɛK$gPM)2It\dY9Ȍ7dlVΕDXxaX v&:c eVTp<|jU|"2*s gXdn\r\v5Asm)2Mt^}:y5l)Mu`z7c:eC{u>JInQϗYK,Äy*5y$o?4ܱw\DYQPR񾏐kejjVnyoQSgue-]{'qdX)5E*)Mgm_YS$~*D]}ǖQjƅl*9#\ :UJBqv!9Y +ʌGxXh[V~*asATR&[cUKa52TyrL̺0rH23l*L˹e*WV+{4)YʍX#iV79Wu\uI_;Zq>NVSdX/)׎R<Tݙs,Ǭ.rjr|Tsj"L+\C$RQ  ""  (HpA8(_(/-486"'^ \vNs#E.:<}GK;]6#~(>ƺ#m>&7~tM!˦ٴCQ)vѲ#l_ϮmB'qh4#'w:COa^kr}lE¼W)m7Mꎻ$L6m|))Sf卫\ͬ?󔹛&g)æWAWՁY$du;&-b5V{V6_R:.bz=++DnyG_8>$;(n3R:N8x)&vG;rG ^ꠗL'F; Vf¹g&cONR^{B*SrYf -lSr&}`9>"ҹJV7#UWRND$XUe⛵I-dV9:0=iNTPSJrKWBC|-[fcW˱\Az]s4ia6*BL ZF'tL>D(cBv6y\EJ2ns4 +w"Jmo uu7Qu4XW/W4 eXf5|yMgzXVo&MnlլQz)[e,yR?$vu< c˞¿Ɗ\̼-<dC93tvRH(Gv3eGA %ΙΖrwY$Rvc&MD8;6'E('s ˈ^hV\K2wjr4|gBJ)ޠYg?ǪM--b-ЖXo&amʮ)I k[2sCvL]%Q'2!FtfD!kYݓT}Yяe%; [ֱٙ9KVIF6%fT+GTHRc )H @4 QL~DZ4"1Gr!c L~LjTaQZԤ,~ypSz_¢;E5O5E#a/-P9e -Q}Rd%O8YUq͊pMJȅznڜPB|eWQ>-=k|3`I +$ X:WZUuUIJGhk[8Y)!n$Uts LGpGDqoAW yȘxQQ}y"OT„4kP7U{BU܈ҡӠ'L"~*d5i +Z1Ֆ6L3AA<3rFQ-[9- k^8IPd^Ds@C̒=Zu9 {AX8v;Nye'L). ϖ$,\6XdUٔ媗I$*otᨦH)slzgnyq($?U;_c>GH5RѝkBwEŬ7f %%A}B[ M?mLg^ XO^nWt~۽ :"bj] ؚZԴdwp-T4aYk TZP̉ޅ +đ:CgIn- P0dܨB>r'&Ձ,-@ !N|uTI퇖S(Jҷ*5\{R \[w6^/YHN$h-VH3vt,6(4bUK^2|[L2aI8bu$Ǭg㠼ydKK *@Sו6(u;QP7$ Y5R6 xyH %ziFbƔIm`ˁ&f $buEIq"ᔚ@]OP&+tRZ߭z2~dEH8wNqd)|i u A[4T"u3]o>er3Ѣ|FKߦ0*##y;)t|* AȃAKV/,L9c:@۵&^$=VuC8AV|âwo `Ea4pa`4c!ϳ{R42F5pJ C-X)F:rmOo-Td)`S71u(6>\htK M&B풠р:k\Raazb]lRzhoqB%ڧBozp1NDE5Q?to r&r6rj,Q +x0ML$ALXvJ@܃5?{;&}G;) V`J#<- 5V1Dm7:I!21 ?Ǟ=!K_{:ޮ<)~o Џ߄{0XbfS &mjڥ$,VjY%_Z$پʤlo%svl_;g|f)dd Tӈz=ڏșr.03#bl +x0)Topd2D`"'cjmAl&eRYIr9$[Xv+&y3(53VhATFbJ&0]9c`fO<a@ +Ty?sCNunBI2j%mCE'Qyr6գ, Sw!.6eB)2fMJ<|'|8vm~^m\.IӰjeQ+d[^ v< +bhv ut? 7g 2χQ>,iPo+P~" QCW%j +eV}9lc)H`oE0LP U:P"Ҭksbf-ᦲjge;nr!d\%Ł1IWj x}\:[~<(NmrȦ #Fe$}Z568".t;{࢘:׶a +=*>0{ϛx,+߲nmxi517nI31?}:604K,TތFZY3|`ZwGѴN50 b +iZ} +9;Q&E+%Vy َՓrAMzo lz67$q o=.RS"yWO|LFJuOF*yT)FHR&bVfc-ޡ0B2:Y:~I"8O9S<̱ hO7<Oa̭-"O4qfIKv*: ~ 9JoLM硠^k3x׎Z0,ׅWAp:`lk%xN'[Ww_[7!l.h't%!8D_u'UP^pa :tXv,{;lˆ!!kM + +ն@hA{,kD PP`?x,•wzi&^.FTmzEi3E]4cAf0k̯ XS3E\EWv};范9zPdC-P4۲` XQckfTa4Th{WA URiX;;J|pE#gB ~s .%b7`ؔ0D]g\6\~>4;ֈi\1Ip +&^֗QTS8u>BEpvq 17/ 2 twF([},O7[SvOEϡ&/S +* ą&tO ϑHf1Т:JI7ޞM KꥋxBҘAJknY)=OMwuOk6ĕGt*`Z24R4\PH~7WHXcF0'2#|RrL{߱I Ta4}[,#82 !=1DgՄo皖r" :Hy2x:s[}y_F.Sx'7 kI?G̴qȩk\a~$A-˪%Z/lGkۿ-ǚv+UhsTYf\DR N\2!'-`/ ZP,(j–&q WYBj6Y whJS6#|¤5n9I4;esA&O%TM ~jlX?\ozT'VOd~FC[`~dC#GG>.Z;#>&CЕRF.#,s;N]*h8gr׋FK?kis( '[8ú&(#}yդ ]@d]|P(\2/PVe*,=Χ&99m"4w>T0ռ,,"r_yS]8H@^\JdY}ir_cB Sm: >C1c~>ebEg('V6MĔ)Xz-̄v77Pe6~HY9DkY Jp#)waiXw5u 411e6eqfs VtVC+qmN> 24k/rBQ+CIpES"Ac!uDWpJY4v +#g]+LUs.KHu3,Z9Bo;W ld"諴c VIteрea>3KB $\9𮢑۴i[q(?#/֚.9nq? ՗Iss(԰"X/[%owŁL\ǕdCH߂oW(9t-H F^GZS"!ҧYt9[ <,0Gm+\f6ORP \ԇеƱ3K{PLjNh 4{4`}`9Wp Hvٺ3{͂ Rhy]_еqW2 QׇY&@Z l\U@ +^vj&RGа*]]رxV',XN8)Eݟ7bzveA/9s͡=.Xu8/yUE/FڊmeދKstN;X r0sIe:ذ/ftpm=a'!% +QK΂aOeêdD<ړj$EnBi $H6.9r.n^D`,bGcN2ԕ$/Z ^Ġ^6|Ya(<đ5k5Do1/9E dYP|C,OE%"Fg͎OP%m*E D^g[Ʀ=Q3Թ,085 +C0=(b*ukښQZ  hަy}0u>_W<c*$u|0 +HLwquTW71-ͬ ޯs Sō}˪I_v1-ρz2b#[/g3.J>A--]5/`L50WH*O-Fen\`Q6JU^ Yvw.o:(H ?fBvص2~ CןR[|vq;։ Nweln- +Ѻ fpY6BGEli>- cȤ5(3]Zԓk{g;jyMM}2)((:3SԔ"![w^vc6 !X/̣7Mߒxa (y$9NT@p'_ .4v>ڈ*gΥ1|9 Lџ(VȈrQe!b1$HypMDqO$Fws+Ncvae3x:>&=G[KQ|#g\o6Er@/ ,WݻКEgfIM8Xam[&^#.,U8+W=D@.2ެ0~82<8+[hSHt%}( i?n" Gf[v 0zѸb"z/g="p DkfH ӭd. 7f lP0{xC 8j2ȑ*>bL$F fI>9* X IEAmO8uZ(X-<|.4-%WT |l&.qWҖ:$HhRG^cB!W?UhI|n ZX usE_͕R>ZQ?#crE^czGN0BI#bFQst*rqa iΒ7 _ R/Ym\-K3 47 Sl2lp+|i=<%%z[5oy+(zn#c#ݠiݬ2 I Q}HˏB48,@Aۺ0G +_Jw`')OBǰ Z5*[B }-`\ i!,:Q'%QdO5+.s[6fm/f{5J/nPDŽX~= *4_FW4>^+ͫib@챧ыiE=k"Ϭ߰$s3Yo_1D(~J᠑U5]J3 $"HnW nCO쒩EZI3ER11%{ϣd㑁2+G9 }{K#p0m -4,\y(`26KqZȑy ˲H:2dBU|.8ʔ)xZl#(68NgAN !ž9e#OwChT.{.s"%濳}42%޸S{bcf'a7\qD3{)uXA@!+ `p{@8 XsaixX1'iK~L$RŤ[(6q B5P3撨|yTBMnWM:nCyBࣷMsO3R A $kW + +4a-"@Ε) C0(AAV; 'e[q%m|ɺ)upe IsGBEAR[ZwtKLFk<ըHD 6l(rALҥt#gfʟmyJ9Gr$[޴f:!+ejIb[q-?pg/zh_ᑗyUON:a]<1L֓ZV#Z,+oWMD8np@/1 +*Ii\6DͭRȎL(sD`#>`q a)k'҇D +S;T84J3ayVg[O$z%/TfYRbKh~p@Vo]93|,S9b(Ƿ#`4`?5 ZwV[cA*_7@vq~CZHҏ}mw呠xً$ "YQܫ'. KN-B#08%嗳\:R?~oo>(W|ʠX`z7;V$b&+#_.LƚxWnHU5}hyWؤ&AljR5AT4 ph樲=l4M EtZӉ"Ip7s{0o=;Bs/6Ujj\ G4͌[USGW2 YXp NvRpX̎e>u!X SdM)sP|Cn; o,^^KDe3dDHh~59gK&Z-?Y+t^ajgBa(G$I"e[AH_g&SLh=C? g%A䂂l1v~2fÖU_MȲT DS {϶b-6%Nb'=,6 sP!\ԿUw = z7/DUtEr.$0.11(hزّ{l9&E[0F%T>dɒXJ6"VL\`9 9nq SzB2Yi3@+ Efb俊$4`V +gܔ2O1*s3JkNE yl3@"4T'A33w"{FYFJ$ԜА.1kBe`rՍf-i~QMp^%ϥR[`5]qDQ= $o^k6j>Pʊ"BgfTsDG +ǹh,}R3ɉ] +y L-grA4w! EOisɺĐ#/Œۭլa'HCΘ̽pFQ3㧃s-Ԩt4)M}e031Km:_%`<(>lݜ?/l=6Im餎[<JJp e ɶz=g#%ȄtTQPq򂔭>o{yF׾%~lM6@@e#jM~̹m&r"|{W%Tb.~ ;'x0;}Ɇ)HZş#NH״~:֏A[vjJѣӜ>F_'/v{ԦKÙWni8SvY,c +.xQRyvt 0׸\K#Fo21M~:]7‡TXL +z7ΡO1UL> ?%崄0eXiP3=1{c*zbJԄL<{[?I+.u=x/>I LhDBߡnEtB.ͨtQD#V f/(tZl@[Pqh6|}XWh98_t O3Ye M4in.) +_:#*H[E1Ⲻm3pd_T1Li imKղ/\O+*dKx42wZb tz8q>jF6_C:YRQSɼ:׼ Z8R/lz +'<mߢ)3MZ5(hGGϕ GGc@8u\:5TMm$~la#r궴s`(.oNbg)m,3܀;~ЭlT4`/}6S]$*S XItM +7;ݡ${C˿tzw~ cS(laKuN[!<Oh'!@X +O6 zعhDIEM0(kC>ED g=G!E@PjV@ 8zu\1/#xDV!M2&BCZ/uT@) +:Hr4B|˖P 4gʧ +5Dl"RRoMbt ´"Rs(t2iK@ghOH%ZDG'Ԉ0 (a[r"W +}=rTۆ +aiHdr\,LB,Z!)뼧qBoT}qZ8)Z ܻx)Xv b qL+mdZ@yeZwJ s@kj+ IK}1e[mEa|E<SyeLFn5lU;KQ姐=D@$q󄦨Ӆ!qZO +U=QCn>I|a3FTQY(HJqi蜳2OqaHe^U::P=e3ɋH F* 9OpWM98pi87W'QX `)r"mV,@a !%8@B3OLkyz$ye=@8<G~6@ț'9}6 :KaZ9\;1m]%٦Ts0{Q^ig +w׺=wTZ/ ;B^ sE!TyP)amnz&`پkbOӬ5d̾M^q#1XY91cM9"aDb fRږhRJ*:NH -I@r+|,5 ~)jrܱ;%_u}?&BՋl%m3)TM!MiswGǴK"%'ߝ4e-wyP2*hjz`a#p{Ӡb%$H՜\蟅[-3*`pVp5WUv|C9*9HzdR 8SQ;hdPYh"[U7-R{rQÐMs1=5ӧ"Zø0JB4cdE n.?M\;> +nDY+6c@.~V:Q`%q웡j.P Kr{aV~cQ@*/^'ڌeVY6dpBX5fp@xc(-kt B +5XZ7\.a8AjOC0rG43OY_(bGΒG8XDK,$d5~,\( DI!I&8tg2Pփa)gu%PJ?ֆo +yWu䫄`"^2i,uK'j3"3.$x0fmO;(6,Z@tS.EoKi=A*ޘI5Sas8pM2>:~Q=cK +T~//Os|8SX2G߸pJ;&w<yri.pNker$ +?\Ӯ +&ﭔ-q; %i#:-hX7fǗ%GM|i +nU'avo{lN2h&>-"XXkfc7.Fe5I69 T׉(7"QĎ"ZB}/6Tՠ΋s5ºC *zBg1-roK&/K<rD]d jb{c{m7 .Q*Zv< 0(LE՗xGpԤ%{ixA"|*)4&ҙ?6?"}8"cbik$)f6o_@ZC} $m;%72%C?ݴ[䶔P* ,T 0n^6B/  v|h_^Fq8J%hRvCUc jBD 5nPz'[-GMcv^[M` 7Yg4BܒΆK 0dVy}*̾^xrE$|h +"Mw0Ql͸m2.NR嗧^'gIA +_*95`eV h@MȱV;U5,W ,Gs"қR+??*AtI\=%CXJsG'|LW +*@Tt³HģQюIq*  +tQ]Jt}Xx^"ŗUVҟ`>M[otw=t_RO7؉NqƛNfs1dM5 BU2Sz!d{o'Zv2Y)"ץe{\aQX #iE(!/RI$9D݅WVz0I_d" ]=Lz?R%z~QQڈhdT,dD@qj/hW(>`bWDz :ʇ_=q љ IBƄ'r{ (ѓ#UjJ2NԈ0#M !Ժs+1d7.*UC_1%EJgj~˵ߣ@˦ B+<"«NTRUN2PQn$C)QUNg[BzuĞ i =u2u r9~5 ep̟vQڍh5zL?ɶLҼ87j0V2XPqbֵIz4"kh %qM,#l܎sQc-|h&qЀ؀+ vVRi{x%ZR+V)^KNAb-FHl&BN[Ύc>;`#\|Mfm4o ã`}qWr~/DF'3 P`YtT@T Z ELd%SD',TMĄIՖ~zu8$zY{ |$CJD{Æ>[꫆cSccewXUԔI6-A)E#*vzq)bz4 a]-Vywߒ6/~|ٷS0e*> -EX, $E+u jۨȍsCέ۹U#?ڈ8"WCk͂jCL.{sft%0|KxĆs} 2lA~_+cZA^ +G+ +Vn,f{@7??`d ]/>{o:p=aQE$ɘ=Er^c>r+߹7Ҁ`.O- \A9h+._me#&^A[S'0y>^B7( IVsKɅ<rԦYSGpVId6!Ml(AK;^pS,u#D fN1W2ܘ ˭86L[-QpsEHRQcppof<8B|6tI +N笨d2_+WҸJ2RAaC:^"謵y2хh,~"HPi* +MqFƟhpybՏ, ,igoysbz. ;Bh.242RJxVݘ !Ь/>L׎uy|YV,^"tֿ[{}f2I_˓o iWwHu +"GnșHm]:p3&g#uwR\KȿP4 +Zz9T +"&F(g1}!8+4Cv*CL!AoL`#ٮINOIi+P~no1fI>/2̑R@,I}jz!VFs1s0TiYk;#D%g+1wt%7;&KN Q2{]C[JqљX[^|`$ +hO&({!? .rvq5f:?0=2eKJsl;H ci֐Qi{ٶ#^FnNcjfSs2ndW殆]*y) +(ê!C-,02_{ |F>ߡx4R|q6=5"gE"sC]-C̽(`81wnhKmeNsyGk{$bȽ֮eZm1J*y@<;V^Z䜱~A 3O`S9EA1jp|_3*3~( A\UI!!rST |(:f0O&;\Pq 1H#%"?eb1w}r#n>V=_ z3L6}Oq.-j$r4a+^ &Y&~_wBS` {:Vu)7,ҠX Mb*FE_b8"~h\ĐWZԺ8|ԚcFJң]LL16}t5;_y1!`| o +~CvkN'aC F Ka^ي{[ <\6 +>R1C*Mw/=T4!-Ǵ_PECCK|{fe&PмCCb*<&c0Gzo7ZdԣָIbοTKS8VQ vxҌ'\|tDF?FAVaYXI))(:%{{֘d&Jv#\nX[ylW@+8yO~hiO8ЦSLtSY +RwSSj*֏Őb0z'?̊Vx7*ؘ.jA:[ANn!e|n\_ZIJZPVj/"ȆS1yjEly%"u2=m))HGf/MFRY_C-P OPCl̊IdwKh:Һ g,LvZWo?%%ףFCa;J5ũ2y +Y,sG]IS7JB?$N*?oJMO"ek4 23v30Ilځz*.agڜWh0{Ȫ:̠$s?` QYR2/'[Tku"|ygONgh 7.FgXBKR}T~^JMu7Rť/U|U{uX0dBR@5ED5<O&7n$|Hit=;|hf{ߛ7.u箈Rϟ2boWRG>ہӂ\vs L-NP]Gt"V,9-Ѣ΀ZS,o|?GB<T,jтa|o߂i(~^4e R8K4YYC.ZgjUu7D| ,b qx};1JV:G 1Ϝ 6Ыq/0Vi.͂73 +dfx ߌJpx[0n.k;-b@ymRgl39r5=y pˠȶ0)h{)w&`Wb +-]6{.kѸM-7SAtf`*2i7` !KEvs1fc]sD%w-w6벆сq]x=aK`7;xN"iҸD9'zVoɰ/13O˒m''&,K6*hSaJD ϭ;'S d1f2,P̐4SoԙhDdJd7 +#@.?ҩnE*so[7VKTҏQtK8*4|8=\’>MB\e**Ǎr_'Mk{Qd4.a uߕH;A([h=AS:Թ}C@Dha>,djIfTi':| Q^_wȧCT=5r1ǮPySD_#SDshty =`ZॉoTuAx>UJQ*'H̓-Hl|8c+ !3v9 +#Q y؄ѿ~H( o]1XBZAѢBUV fE2aˈ]ьjᨺS1ɂu9QIwyw::ڮ!2QL/{@' ZGlaCC=I7?CaxM36]B$zaq6V8c@3)R̩Swԕ؄kӣ/3ϯkwm)H&67 )lXO\tl,ın\2 ZG@Ri@yba 'LfEK*AWI!7"DG$p_'v,qlݔc \JnhdY|n"%Ag+RU]~Y_r*}9 0A'4dPԳ0V4 eIP;IKaG}>xu$bplij}WL>~ +d?'}fD봞u\s@א>{[j"azu 3~ ]i<5.;"#%LC(Q!WμovdSIm^WZ(nvoܯe{Q ?RY clIui䗖{ܲ= } KfqlTёt\N/)M-$[7 ~3nxf_0U;dP.N9hZMegbR^?<ț1*A;UCg1͞4{ @ ޢ`z],ԃ\t)w2}ǛT]Upy˦tp~ɉ̎yt5$[& w OpDt1Z+bX +[|a1֬mcXUUe)2\2sI (J_˙1}bS͕ImG]gJdҭ}whKsj O TQ!QoE xѥE-PK$MAFf1<\GUbıo8 A j!oE&oiUz +P*b2 O |E7I'UW cTP2Mwi +wA=R_kA0 + kX>& d41LBTrB{O+"4yTɑ\،ҩFQ K> zzC?{v#rU鄥UEmf] ܳYkk(݄>m'R 9^(+IJ/v;tyC +h) k +Y?Cː]H"IPBW2_T2<rTH<(y'K_4|3FDU/h5u)[@Hb]"H\ku6sM '_VYNw1.>nFw1&XiXR +O6ٙ-6dܕT}{+k۰ӳ{0V@σ! Bn,rtT9x^KCcZG/J'!5 )vR1mDP1@u+i$#%:ZM򷊩U@k boz/N1 Lf~VoQ&2oUS fğjǞ?ZQv_wQж 9wF( +q @mr1$%ʒ  E c7VQ{=^Q\؀%ߵD`tT>P Ŗx`*0Kf 3 JĥNz!ظ +ljqo$O\ɯ):mZW&(QAZShev +ߊ*=Ѻ)Pzh g`` A'֠P:`PPJ$ d\7ȌC9>h%zշFlHPE$/=~&߻ELZlea'FgF8wq6!7OL]@EДC8_ѥeUC@"|.4dQFlu`/??ܪT<5M96nxcHp-x2&q:iTݔލ +f>cWBcy{8{J*qE/t8Qk:'H@H7e% ]lf <$rBuy_9c$zn05')SM\umiLqZI.0!svzGQê[=Gﰮ8svS4A Yœ܆ J#bi0]Dզ gR rм|]LX݁eMm_V)~E3t*tugFRS+c5åǦ`dSf&JW~^]ow(:?ڪz Ja~R !1ȽWIDJW`M +ipzx=~L2ɔoPEobEg^Ô`Rbx6;l\O=Bh#V7 VAQPmƪcq"*byDٻͲx5U:JR9ѯ;;!UXUC)2) y& Q} |pFәṗA8iC&UE5} 1hlzX旇t|ϕ  2A&P4 /h<Ճ3ؙ$?U.S' u3îQ̓9'&SobydDz}ӗy~zNYF9*#M+q%C'7 +e"D2.34qa̽ cY|fpD cK,Hx&@̇Jʒ5|Zqy՚JDJIQQDيA)P?%2]9Zsc]=!a]&FI򰠣qUSzcQU{n93.ϱs/fշKIPQ1umSC$\8.-'kڜSZ[$.z O. +%U #WF"AyJJ*()6UsBNߘ*dPh@% +)C9L}TN+WC(׹>5*̙0N>9jxx^بa»֭3yD0qhIm2:IeN(Ȼ;:׼\& 68Oe!OK{ϮSE#MEpDC<%FuܐҌE߈]qrkAvjziS S5kA>v6 yT')r+pt=LBeoM(52r!H2"SBx=dЎ6ӮbbN$gff,ԩPn8ՋD3:4C ӬH]hڼ((ۈO*wSǎ+jC&Nz up$s$6UGsiNKZ 2-3,X~( N 8H$ԷF ivX¦J3A2r[`V/Dox'1)zu!A443#'aFȎH$9:DI2ч^Yj,Ԍ3֥h!F?EGdq[hLRufJE7\K66D QBB  +3ܴ"KKC #J>QqvZ-r(5VxmV$d*Q  :@UXTX +d.ӮEhX    џa) JX)iXE$8DZ'Lia@($heDž 6 ^UA$" "+Aj,Tڅ Ԫ:XHE9U!=BV +f# aBA, 3 ]%?/t;C 1DjFDz6$]h[ڶ҃[SP5Sr8'0 +ac202w q yVQܲmUS[EE~4S2 C5b ӑ +#ܚ +˪dAԑxڜsI9|G2C;c,INnYLKB򊖒 F#2D#hsC$-甹/4B*jRTxQ!} XnQ>w%iB|TԔⱑF fn=]Ԅ D>b;8$ QyfT.T^e?wCg 5'>0p%d%"M&g쓤q*O4Uñ1E:?koc~BqhhZaк SW r*3PNSc h.矹<\F5{3;ASwIW5gQiѾ \*^8VIUlB!NH*gbbSg6<M?pfF?n̏RSb 㵞($NbbEf7Q*024˄" ͚ {Qd# kaM B!zbC,)dnڶ"7M_HJJԉ +E8KtQ8:V=UGyAFK +U$5Izy3E.p :vQA9e9t5"bDZ i+h^)ح3ƨƫ#^3Yx)5*}4"Ti!HKSTdµ,Coc.fY3lEa6eCR0uÌbHvc:9hȑ2# kdE*қf%oq%C<*xƝ"UbY(ԢcC' (\QXj4$f")y5V{JÜg<W!QL\9D]^-X*NDJT_D+$(3+##YH,3TlXϘXf:!Qy-3T(;D$bɇQF䭩@"Aؽ_EqD.wٰALcT|Yݫ橏[\F +ϧRcCSE%rcSs4Up!ܺtb|hrBy魞Wk:j8v EHYRbWfդTkC½쨗#\\-ªZBYɌd6k\EWJ%3a%)hCm2+b'vcID˘0+BW.Hg#cCtXRlHf0 3EB(;QhB9$VIRK + nHjpԃH$ N}>eS ҼFZ|JS Zi{*.%U#$X4Ruϭ55~F.y[߳2u'PpaSyyP#SCN)4ΈTItq*%qj j0F"H!2FZ"|E"0I%Ä4P!I5.*>OVvܐKպEFE "@PFa@bIdxBQ:#‹4UW[0 +Qہk. 79Y> muT +UYuG-9EFc@CBYDi=$\[OSN<FLYB7FfKr+ZX +m-aUDgxN?s1D!! .fBTMN(T` X?`ip3ʮ?j)KMYjF$hVˬ-SU G`q(kFj_1L-sX6i&e+a>3/JJ8䄆D/j*BMIF8KIMP$PP@D|$pv A4!r#|,&.OO\Či*W/zVG]"a`@9 +Mp.Ë$| 1R}зHȈ|BJ*@V`+  a`07 d# Q{x ;qdL'[}EBl8  -dTB'`@ C|t4@C"1(*Tdx It&h2O'z$.e阭̎pg<`NĂx ԬOBII#MPҞYD – 9_$B,l&ilKBN粠*Z7W1'3V\=) >xƲÒv$ܶ& aJPr\D9A"' lJ'z4v}F8 +Jd{fC5G ;G| ex!~GR}5`İqv;M3X$ W"IW |`ˢ9\A `1Op!O?+zW[x!vAYMț(I) "@5C7mS P|[ mgjUjԎC|jflWzKWZb6l7wqJggEzlܼ)sHBd0rˋpwl"FRد!sc-f|]Iqzr〭K|,I }T́G&&RR8H,l| ]I1 Ţ`yF囐! I][87K(藭>WROUV;f!7cyZ€]Z ͖u,m nEѶϳs[ybnd|١0-R@r6BK^Hjs?c^|joܒ1L[ + 9rWu?6j$?a|C_jVm`XkC1}<֟٠+6Q +@Dz1ָ_\ +4잷#5WpG?]mъ&]%=s_K< ~hRwA?ǨP-]7F)Cս'V/D2@*_9g$}@Aa{IQl &JE2*tͦG96}fHTP AX.6v[4Ιr{(o@!^ɇ2ա1aʬ~r͗'99|` _]>o8}3z6Ŵ7 G^ +$v =\Mht*P1s(z.?;f.l2 .B߉ُH h#H0G o# y)Z4 z2 j (:OA]n?πmBIG:u#:i-1}DWD!+`-)39~w]j " ofֲ9M!;L[Fr'\4'!ỲNUcō6-H>@Z!GIui#TG<ɚ3YwsG[CN"qF :tA5:V_jz2< +N\"8iF"p H]\oW(σT CG3lw5&(A0m#lWf'hx2NpE|2ܮ)dRe[Si]i1=|jQ߆rC (7y.u=2{1|P鰐ըR_G(3z5E&K7}G:Eѹ&..<]MU,oghvuC,`Gk|-@z77A`E_"" CiفS`І..ENPfgс?<Ģɧ^#E[:Ujm?Ny qX~dG<::P(۷p<#ev9 HEӨ0g4KsㄴF7!uA*YJ6(zZ)MM?_־6RB94~nb2eȰY38yKi<.jrePL+&CO O]Xc̬ sZ͌;1l+y-3#bpx [uǙJ=%a@ϖ`=M[ p*3Ap0c$HEn(-zb%wƆf-/;<0$-_Igv*7ܔ@( ,_\_Dx3R}ܭZf FG&@h'<=d0FrBӠ7bUg9bTT1Bc7$!?Nc 5%0I+ݻqK7Nߦ`Gꓖj]8߱X"g2ފqa/&LjM05Y6gV):FRkg, ;x@7]JzF^y4{Ļ7\oAΥT/ +imŠ$tƦ#ŕybXLO/l߻`(L<L20$Cx}SwUw%$=E Z^Y3Q?QM՘SG2գ4 ZtWr{R{mRTESJ2>kaUZ4غۤlRh`m:5KQO[۝ncrñ\g?I4FD8E5gR;aBGiDӀFPuXաdz;VB:灦_>]sp]:z) dcX3)dF`㯟zg>)s,J$E¯QA2'W}>yS'Ø?EV_ZyGZ %D"yxC&%y}Q0&O=_RI;~JDuX l-+N+41T4aEәՃEBōSzBᑌ~BU'xw&8@XLz/,^ʌXdIxn}_ن=}1o(׫)L:8أQZӖc-ͣ7\ pOc|ciǜ|t'Z ]2=pEp>TC܀pQ5  +O=xw/ ǷgEcЎɷ_ZWo=j~q7q:1k,umƁ\'#䘚]NYSb5̨ПN;,Pїz~:`5uh ڍT p=sj%C(Ku9ENIB8Q%[UE8b>yPX6c.Uuخ e(]~-rRT78b@K׾HF"űo?YNe%8TĬ\Բ! 씾׷bWbai肂 n0xn](,ֱBD b֦ WGȂ 2 +O۫A A'׬`snjPFpY?Ϛj3£CJ +MHrQ `xO8ׄ[`BؼܬGV uۥG'4Z :H{D3Wq&~)뭒fژs^h3⥠(sP@F9:1H5%$GL2<}n0Lqy߷K8}dCڸ^ӵ'dMՏחbN"` fY%<|PIS $Lr;%:K ٠QYnr8%#bRL `WFt!`Ȅ@uXԃ_S^r#3#b2~pJsp22%N=qr_nc$Sg> iD¬ӉRQo0k2㷣>(ݙtPP)Nn$4(i"i62rp g[x(O(s^ XB[RsG$uyh` Ԋȁѵ5)QN~){BWRF=wXvu%> N*QMgi[Q{vlC!!^4@]&&? Im0P~m @Joʚ!dRr+BJWN;@U=@PR_@/{-bheB AEX$3knoQB0u:[cJŝҩC-*l2odCɇ9K+W0hL5 -tI |̰ir0*kt؝-nz?ɠ sn$-<f4}V!PCOZC"O=H%pEO"!RrXiMS [Cr#Vhy@j0-5fJZhb<>c(@iM4887G/pGu@^H"%)iAkVXV1+^NwQAq +(b,.FiSEcUH1!?M7nAz\KZ`0m35V-c1#u݉s ߒI_9Bļ)֮4՘SQ_FI:b#O FG?6E]>JW WНk{6 [ڣH*Ll#Y-kn#׽X(48š3<G?]8JA=FMgѷ'LY/& >Q1/-i)Q!\K* Q *P j`(R @mA`2*y>b!ׇH #>#2\IHYExʄ-֕$*s>$ƈ]lt$8MҞ+/|SKrS# U#c" nzg²ƅzDSTn-Vl]jFS_TQH!Y;¥JzGX3 #"(}->@7y1\z(Ƭ*-z +{~ eDE~IIS!mT,]k}(( ٕ&OCHpըIGow ɐ*Z#hI09

EJ*GU-m-m_$ѱqg:a< +1b#^HkYB8 i>G;ɓzX+\4b7f"8LA\fro!,+x#"L)"3?d?'.xu$ dN =ǀ+eINЅcƯr7!]6ȵ9z܅ lMԏpxyY4d82~YM!殭˸{5$!ng{wÚj#%()ζr/"GmE Xgh]QGkĿFT.ku/4 +9۷&6\c!.` yk A΁7 "kDwD%VN9 ^`z@U`nB eɇcAQtT +ؚjGmM[H3KJs~\^wr49u{Z0^1gA!|8zb?q=^H,38]?Q{?@(u8 HDo뀇#п"_ZC׹[.oףQe+ 5wBTX0#Zmw~uEuŅ KUO?dJ/fBs@t t^z)<eTM*k=tW5 B!q0lz*x:=E0Ib3ٸj^-;d)ڦE:^bU4JK^?p3 ԇ#pDd +/T}h5cIGV3** 향VT I&Zna_ȂQ  H˰Ԋ1Q=*p%oIS;sPzU9RҜ@c2ˀ Û h4y@ .ڋu7 W,zSř1 '8%x@X)FLr:Dt8ڞܱKc7Kք .-4?M'}C-@Tm*#HalW+\G +H'Rsaq,!=', I[G +tL.(B\:/X YB0X 09 &?{;ŀhcH Lɐh?e-Df(#\ /eghJG \GwX+Y5|Հ}|Q~Ӡ)Ɔ{pIt<1o u# vh,ʏErFhX7s-;\A8ѡeb:8ѪCxɳCgq;xjFJw?<`YF,0pޚ,c=@F\Y |ȅ_AlG" 탓Bm@^i>A5?E筈v1C " ćS)"Z+> ";&7DցD0NC0QC  2E"q̈وEn7D!"3CFrCJC?$GC$zHEmDEQa!62$sq#q( liL$h<]0%j!sU‰c^ +,a/d; 8"_aSaQHc =!\L3l5&&!n8ᄊ9lu;QpxlA~MPAX PA:Cg=R0HCpA̍ Ha0IySE @Ȑ)89MAeP C +OQ"*4CHF ê\kʊUX@NJZ@?Z+XW3ۆ +G+p?ç +k?Hh/g~ha-Zթ-u!-c0 LCl\0P]>|>tԺp[> +VmM|pA^ +V&f_=E{Āً. ZXap&D.A BRQzDW]aY1~F=c@z-@c\hǢȰ<2 #2^+y@2x(aF=I!3X0j̀x3 ;4 }G3zL@*ޡ(1h@;r\!l;zHZ;d@ *c;쨤~dl;l5$[ak`L7l|:$E"0 Xm:LFچ#ַӑqLt 6/a H HS^$"::5Bϡ7>9( فs4A8a4GqLsH8Q`⎃!.^9 yrra)9q~LWg9AaqspⰑe6.e&EaAqk1[G u M Q9p K.bQ)Jb8|]?%;'GFGQY h::쉟 oH5:NO[dZAH7  Ȩ o/E:nTu/G_t4+* +nX6G}FTn#@l)60'g#c@ {~ I>F_ +x^` AY8j,w:]L|Պ5c YAj$ӪQa>)]@;H D`Ӹ@@;myZ20[؂rޏ8Qh$h{%e@t<5A3e;^g<<3pjE-7D$gpDΘo3܎ qfT;&3@ h9/.cU8شxe(X2lHL@k(#Ɉ5Y;1U@P dE|CFÙ@"e(p lPȴ 1Z;7OtGZ7s*㕆4bd^!0Tyj0k|kA}>/\Pak_dszGZ~/*l |q~n/hzJ/\Bl+yT/ +xA<.TnWv!@\0.lӳ C¹H AM.JIA@>,\x.cOf4-& >E}[4!k OHlQ 3($Yf-Yj`C-$ӢXkB쭟7Y57*VCQr>,^'}'ȡ<AOX;u+؉;MŽNL+h9]D7@q&.nI=@@# ;% +$MA2!]HZ Hg"LIeb0d Gb$0AD<>}rSEBHݓrK@:2ZQ/&U)d%lSda T?,*ZK@ ߒבU'Q$/IK &.TL*".w2$ʄb&l!+$&t%}7H&` G5#fyMn, 'sjqr,Y9C)7LxNو#N5"N N̈A;dD #bmzB"->>EOJ'Xy@;J`#[Q,9aB]@ 6Pr7y?x1jtQh#gsS QE Q0R\@%| +IA[;.N2Ye],NyP8Z3j MEd*̀ h[q\6&/~`a6ASΆ>6>06"ژ߃%چl{@6փz?ApyP8nzxЊV|~sƥ;9t7P؁52oNxɐ77` sBF%# @ā1c8(H wܠ]i*AJYd ZR|INpI,S Opl<|3G8X8WATp GIEqA -1!9_ 5N`x: "CU 19Y _rE/MX1`0=s0lV89d0hf#H9!o/ȥ@3` 6cI#ny#jZ dSy!D(l +;H*Ac;- +Yۯn\ + WY3 x T3'h +Ԉ'E* ZTcR> ] +Jw% + |)So5)9~fR0@qw[q'ze &%&풣bL +KMeb%dvACNH0 KL + l&Y,)>V39.) H:LPR0&OJ +FZўh  n' +x 7Q‚LRb;V& +*7meU(!];Q Qjq5G`s{)~2I":QP "#`ķZPk8`V_tSm"x`)u{'F0[|'08U4|{+=qAdbsdRO]`'hB{Fٲ $1_v8dq!qh@^ +ǀRD!Yyo_7++B2Z)`B%$psIV/Qş!Vn3|&5 @wtI3!QP 0  L(\3 ;}49Б@U k >"FX '=PfB ASvu/[AEctӖ +0j.87ow}HT/Rz+dI@a .y15=: s@߆W=t@fPs v沱ǁؗ@+E@hm~J,I6Iͤ% z>I` Y1?Ld\38 0 k43u>A3k+*K LrwP2ܾfz +"}:hJ$b:Àܻ  +jNw!P ^:/-cuuY(Xh~<}j\fh 6G!!5f(ϲ{ i/2h"&RlNpvk( +^Z.[ +0Pv/m4;` +u׶ vZ]# +6u'0s9O'`۱uV: Vh5N *u/k@& ~l C!w6#!0;4Dp]zPH_avZ4KoxftRQ +콘LxXQ>kY'E_j\GWDvb3?aRXwki)fQh3:&A۹ B/= 0jx?Y5^5?9%A _uIKoi"haJȭdΗ(Zn6'7ӀQvG \5U^@5*hCkT*B3_q[B=dRXPџowwdpj\(ҬQ +7 +Y?cM7Qޢ`/ +zwW~z , +l 'B7q~4WDTJ4ٟe#JbY qW_ JchdWQQIn~JQ U|L?6/ ?kv4s`a'~[/$Ìpnpms$*況'i1̶9.ȚX3FPO$?=a 70 nӀT-۽a|="z0\iDQjJH6L }qv*ؼܻ9܉~`W! ÊaRƒ!*-9WDlMBE)DAgbPe# ؈ӽDpݾQm]޲(>#P8"hk}=VR +3Tn<5 O!wlmԾ I BK'<'\?EHԾa'G+W_{8JQ86by$g_n_>8;#GyIO7 +|y}ϑN鮍><ߣac0_DVaXIP>`mRO {A+oYb0ֆ7rmj +``}`[tEɍ%WaƂ6\(-0Ҋ›P=eYT+2\ЬrM$UPIŲ5S}Mn+՜x$V*bz1JGJR&$Hv鳺"0!Ƿ((<%~K~DevE+џ*3Y9C)ޝ^AACqZQmK?56qx{h0A3u7ߵH/ <^t͇^;B|(=Tc|1 2bTQWwʲ} `j-C4PUހ 4K՞g O#Uqݐfݖ>~906QzuIp3#Yn0 Y|g*Kp[ tWCKR~rɒkuՆZ>[$|& Ewԃ>q7(B""QsT05Pw|=QX4: ǟ+\箷hϢA>J-/n%gڿ 'YA4.!/ +޲*_Ro݋H;_Ĥ{A>clKlj{h#Mt=P` gnkl3qK%qY%kמSY=آ-~WASbUGvKIzJ!ڸT%q\dK`{k-ܘN*(iZ(%'.c-Ovm놈ؐɪz1Y8{m/^!!4I8:j11TP ʫ>ǷRr"Uy(9N!٧HZ4x}&=b6.Or&p9 A^7g L!mb8q&<6OwNk;Yɛ9w eZ4"=֟>}M.l+ȱIyXzNmK˧uH!AMسqAO/ ҧKϰBG}շ҈*PS*toH'yC^eC-@ǛL_,k@pHu؇div[t9F7>TPn1 th=5E9iG;nG{wCgBdi7 Π\A9ל2H~HPv=++Y6w uF鎿ڼX5o|}h÷h^9o^fVf}Pd2} AY9 ̹GW:MضRb-ޭQtf{/}2Jw@3c Fh$8ݠn]r?g X\)%oa3q<6%/k$yL +y#iD &s-{<2aҥrR5_}u?AFqPe7D<q$#ta}!^UjcUuM: tvoQA +{1pAWy)T{kA}Ŷ Oрkܞz޹9+qG  5{F{ߔt1heσ:[@;gJ eɴ +>ZuF'M!xpa_qjW6C}$h!wAv|a7%!73,C3U/}{P,@@b +񘾧OP|>qGA1}F Q+~{,Mml]Y@ޑ(U'3r3o:P#<8JEF2޻v74xRlA>]~ eSK7) i27=T>{_wN> +(~uGB01W7G:[t>vH21zDG}>zaSXuVg]3M/3|D֬\Wb!7K"v=IAp?vn8l'}ޘ_n*t 9maJ?m ;#y96RWnL;_#Ptm^ׄI#,YrVҎS2rheT8[A{92ͳץ{5x^9= 嬦]ظ$aT+gDiFp @ck%d :qa8v3\'u録aY0*=^e2>=35$ED`7wSd4Znˆ)u="{e>ŋhn绮 e +*VjdN\SYBk<:h Uv}2D"Sʁl}-a-EY )h݆W/k[{lǁ, vZ +VExXΐL1wPӒyxW1W" eIIwO%FOX=Jwz9U'CB@S  %ͰSO20/AI׺UjQ(գᵨçh A +hC=R-DPo3Y`}Oi0q =n6&,LIMZo`ʚ#k30I^:IBf@ҕ`IY,.a2qJ|t~{5^^!.%1p!]Jo+,=WЫ=z 2'G,e)u!{(Ӊ+MƦDY[{T܅n\ ϐ&]p)[I$;~6Wd^’6:i@/$' +)v!Cx)e>g9Ō>:u@ ` %^;l\U-݈G;_@ϲ=!;YC:&A@޿T?@mb9wcG,rʧ0cdb6X5;8(zy ރ~  KBuЁkٰ/ݲRAj&31u;gY4]-Y +6z +3^s:ܙuѿUeb c.5!GfbcVp;p@<9\2/*!z A]>&9qye|ѵG[P['rY~YBahuʹqL^NOp9惤r4JS@ɳ#\Uä& B16j3*$?5 -bWjX`+\{Q_zo!DZi4#>t-zLwL)Ts~7(bZu`d\9 -HuO.rF@N>n1I`Frq8Z+}FEOx /?\~jǯqaL2q?yƛsGƧb + xe*gdq琇3j_ +'?G&+Myp#@09"ڕM[!IJyLngq[.Z1$A +,>V@uTOܑu \|:*Ȟ.&eB&,xm>zqbV/o +s\< Y$';ǬtpoEa"iUW#.. 9.Nd1vyUş<.kܘ+I^r;I;hm,m+noq{wkM+ZuVqv+Mq|/)/`rщϔJEQq(?QBp'woSv8;an&΋U^:@ME\QtB?) mNU|x|3 SCKuq Kﭦm/&\ B YFDml I{yǦ <7^)^iL귶.Dѥio?,TWi}O{ӹ ˑJ +ja 3흱jxW-CroD(,_^M@ƽ@$cyEr`ƟSm0sZ}ܛ`9QHӠjyHX7u$8ޔ/ `{sX%>١ 6'{v~ }轗$8 +K_{C3-~rDwqz%K !ym²[cN|mmtktV~(]#H"" vTMk%B1#_{U[posBV;.qV5aޛ\FQ{#"L8{ MᾀQk.{#7h6N:-arYtUpo'֓7{po-{"EuwCQ]zqDC{;],_o"Ą[Q/m1o=p|rG{FG& iqoI"*(R|'1Q x7滁}OwhVeȻ`ntYw]gԵ]E2v֡egwԳG&Wbx,O!ol| c,h˯Ъyd?P7#}_aqt :XgLxv2Z_k0XR;`2=CmSi[۰]-[+7Ik2o?~iV~ "7 sɡճաWdGg vO$q +}ݒ΃giZc}.!%gځޟ2<$Qb4(ps0],?~eFW䣣# +>a!=utb;Xd/3زq-xb_sH}.Gɦ?_fT*OW$ݰBdPy.s‹˕  +x ߍ:L2)@Č/h 4ktwyC4@) ۞@ SI\ tvq$=:msR"OIO[{qMA C5 zWUsk׊z|M;Yf/#{-|Xf5/pOlN#Q7 bsf"N^| $G"oWfU~;\"XkP8{1MpmS(Zfy2տ@N!;.ZWz]dL2lIj~w@z&Qܣ0-;8Yge?)Sf͠؅=zrpǨB֋HbXB(18>=Ž_p<|26j2j@;ՏO !mcMV Ӵ^²+I(p]UmgcB^f)b)N"S ":0X]OF4{g' +55cҹJھ051\`,5{[\?BhCh b䁣M茚[Q[|-:[0 K4vt)>Ė4Q0y![jB @,_Yiy%6' endstream endobj 12 0 obj <>stream +yI(]7nF@^ԤkMzTʨq)-i>|4"57$i&B/Oŗ4ThL;HG#"~ }K􊐶ȳD\L AOY;:Z[n?8?le6VE=BƼ{1vT^t)+lTW&WXEw##+ ^DnLZ-p(ȀDc ^Dg4WѷGLJLƽMС?3&hw٢ɹa}ôHq%Kݕزx)i v-NQ +=^q.H'uК?bIFo; +E+2\а)FtZb kS cX8}Q@k00iZ{5?Ѐy?11323Y&}H@ 'y`|׺7șY'l$'t L) +y#S]a癲%8%gZM_V=MWB6{>9l㈫xSՂێ%zB$ƖljKOͺ9Nbm,8]u煨Fu.M/`s/xgz+x*{.'9y8s >G& EQ'|mbV5lǬ+H\,u!0eZAOU{CTVهMT5K%p?Cx|/xg\sKt+ +w](WɁBJr~ +j 9*k}}+)_*F06'p"X* +{6RɧV>ݍBG>bΎع@](镫b,f,klc^Pt}6y!Ydm+6FjaBlвv :e}ػd0f;%=%l 6,S:7,ɳ>2a ,)V5jh2ϦjU6/xDoiaӞZWkJwjrm[V2^еRI7vZ ۶ec+9-vNh+:kOhlnm۽܎vB'0NU[{f7+}NM#5cy|;&6hqe=-^:.K&ِݙ`rq·uB(k,ocx.ӛ@q~P:A II׫:tQY|d;uvdj?qѮ JxڮWE]vͻl%һM`\/Gv4ި0HwA\I'|͛(j}HdGm8RM|$JE%/ H(>08{K~`g{pe*q s yy *@pZgMy W7@вt|}}q,J +bzq#8U &T! 1 Oc{9;$SS_ Mԣ8x1?`6DiNۀ_, `9BiL&s8#0gFV&, +fg̠C3GϻU@^{D4'!=,:"ODePpO vt=8;V{zXm&v9  <C/JgXQŤ*hJᬄ]#G6@p:6,>v3`)"Ldd|imgQ%jb,Ȁ= r!2UE3=r +[9j}+`A>fk&f%J#JmLX~[v6fA>V +|DZ՞M$|B+'3L[gBL MpY X^)9k"rMf \Fh .ucKV\ XO nYq .K; њ?]KxL:;aa[f3(`a pdc@?z,xrHkb:m[:#v4iN ?c-`. tc@;apuI?l:س0r +>q'lոT|VwQEkOvJ N,oi0wX\OlW!`Yig2 '܏ SP8rܝI^0cfa͹ 3[3@t Âj6O3K"1₲5RB" mq:PkÝ ̊4g1ڒVJ/ ^ʚpcJt} 4`;Fd1tvWL>3<0|V,y'{7g,2pP->F040M>DaCC᲏l#3GPROSIG`Fˎ91<]3kɿ(9&ZC[z"o7_`cAQ +Bd6Oe*W<fW#RZm\ME/%㤳\ >gqN_;ڨ2eOOQ>&*1M070q}yT’ h*/*JEt錜g;r ,OHf@/&EFWa%_Iy&C +vDQP5nJemELQ) +bN +0 l2{ʕ=L,'B/9(@-/c-"|!rY>'wY sQ@v{ X$&:^̼m_0޸/%'2CN̿UO]m晋X揝Z̊i.\R_{o[LzL!q=LU_{V@@# U0ΠZYfY@^&鸎fSgOͫ -IjuʠьYfocYOʟiV01J LmѼ$ZYHOkWhFLy7U74k4+mwJm NE!sOY4 ⣫ˊF3ǕS!y2ۛRg柰>xý3aϬQN%ufh`"Όt vaEg2 =Y[1OBIYf[pBf|fE薟f5>ͧ0XMiVЄk ahgӜ\r47<5!2=ͅRlʵƚQK°yt\ƒNd3)M-(-…tbi{tiYYaz%l*3]1f2ӊM5-M'OpE!|2@=yiNAQoUF0Q }K4@WAueOQT~\XV gWtC2OZLʐYGl!BkBI-ck*L[wTVk`G59gu93 |=GJצ2 ؖ(N7aKB]Nt%6WïU 3\EN]% ݊:#^xFO b *0C>v(bQ45SlnDZ·z76?f?NY4rza<llmk.ˤg&bO<ĖqJbO~D-6& ;mz[ƺ&vC5fs[NdgEaKG'}1&HAF112m`h uTO7 5!{m,vOOhIMO'5vgJG4/*Vc#>`Fr8F[B6\EKgab 6/™~(('TYX\y 0J\l3J `jIN>PnG%vyHX_eZvr;IO@m⨻O)` j [4{+(]9eXײ/:6cW٢iX{:ݷ$}{;yUeb$xNVnٛiS1hjgܞk皀pfim&@A®_,R0yWړ))GKiَrp]Rl#^ q۞-.HelK`_ "v l0:cy#-w6 O6y}`{mۼ)#Þm 4v +m_D-8m]7]< DhACѕJUJ|Hh;rWBc46| [8?nwG\mLSt<۱S1?'LnK\m-L}v8? +AN&v|\3`;Y"`{;E&G=+rel?g\B۰DGH 2,EQRBM>6.P:Dd;"ھtoW@ Ѷp m-MXֶ1w2m8(yǪvx[WNqn?gMUxC17 {@q;2f b8rnXFB}U{nY|,-F(z}>EhP4»p&:A~yfRqsOw熊-bBr+(1Âu󍢐!b}|+SDSjFsp7?uxm|gGOp: 7iSPG;֟Tn_p,| ԓ4\G_{SW(O6Qjy&Aq',B>J]q u,R_H  xs6NY<~Zx;V,f; +D]f`DC!BO`p+A&|`pON՚\L*j/7bU6W6͍eD͟# +t /؊:h$b`dxb pN],W^UltmݪVrgud݂AGZwBz2zW~mM(h-_~w29a'X؋A*ihǃeo:QsBQ;٨K\V^fvt.AI5"i{܉g d;Q0܅8}0"y߃}Acǟŕ!OTߋs76g]ǁ|YD#)O.9B,}/x-A1 ) ?e/oEn[?0t//~@~fT_Fop+! +n1#ž|W%_._xļZ6Xƚ'ymWfey+]qm}=Z/RVQz/?*z=\Ūx*rnaZ /Tk^U~#z9Ø+FLJɆ>z=3}z'zLaԟ^ݤ`ÈԿpzkܧtd_"nߔ:Ka󅫀.űP~4Ϙ < HY釧~J dhU~X!a~&b%'D:9˥?OpLjM '=4J_vz)Y +e/?EA;4^_| +XjV +_4D1w }Kc})\h [$ZR8N?ڈZ*%[}GzrfؑVJsׂ'ٌ/xE{hBO+-B7 T6Fܡ!]&a[Q*эSNc?<% +4lpv`-78ՆM-pzYLY:DٝRv 鸃cS +v5P[RQTMp0*I-e"j&}6Lyםe[ .j1X_o",1^ؓDJd> (2CtHb"p'oiSO_Re2ȌQad%Z!DJ3MkV2gϸ( HPOEQkk'XߪܾruRtґ18(4}OP[ +!NE*M&XvJ0tf]C=NZkm(2M $"$5PWP4_J#A콚鄳[>3]JjB@ BJ՘`?')ZZUUُ9]1f Acdž\haRg,`eg9Z!tla< |a'wDcJ(Y&xm;Qf}]t b'gU1u̗vƲG":C0zm-30 7vqLȸWR#$Q +C':;WUwYݫ^uz{{w?(Ow{;ӍU:ڟsѾ~nu/Ȏѵ'.$"@4%~TQ|p2RhbbJʜ_,-˅2ƮG Í`%kXqcPG8,ȋ|5 x{7qktY.cetaP2)42G&3,.c # J-ӎ$cOC8d7yþFe|/L cHKXeͻ*sVHj\U8siAy +`M>5SS5ћDMffLDMX%Ӛ0*fT(4fjFҹW߹sn'x&y7սk-Ӧp4h^yihs{ihr  Co@nsKjR/vf|\HBD% $ 9`4!lB'NTqEn,9><$Hw ptN}φd'=شd #iXXaI:Pkd nfNB kB-jэJZssA˵ Dx?rjH%mdmF fB#H'&У7@OR+>EB!+sQCfq⟤ Ob%F@W#Kw@L*P\ 2b0߄𧈽_b#EIEZhn hJ>~鹧_ٹ'&=Z0P$N&³sKt"|b1%~%xb/߼Q#1K[b6U`IE +p!"">|cgVXNA8Ik]4W]冖Mu&(n{dk54dKgs%·02*رb8UޗE}PGY.FZcbvLBl _V#"mVsc%` ~[0d"q.]2eUVIJ93TQ杻|p6-32T$eԣ8 +XJ2F$IRJE [MJĂ涢E˔ )@!l ǏKIl:f @> "v|&\Mx @8y/{I &T(IT 䵔Gf[{w}1N vy7mfBܑ ~AM26 Ieeg*mKaew^ߘNȵ53#h)UJS \=s!oc+"-2шm9zYYY_o+Dzm&O ZL!Vr%֊hH/y98GҷHj]2:|&F36&)\9[3bqxr7YZP49Q`hתK4}Fݪ8Vqt{gD2Ayh&ޔ2j;v +%sj M5rqM7e4âhuVyYPKbE?SA!Op< E4*obpS\T!:tpHNn)&+6vz{ d=.zI>vOlvP1 ȪU ț'N΄YW^H[žczIzh~Dh$xr0rX=V0+ +$[Lw2V$0 ^@vy;6b))IU˶3,wY +n:5J@ pc'_8%CZ̄3 lUE+:I)cUw0zf򿖛 ^巡r{ hg + +[$ȖvG^z }Sw]69<쐔IN}{$@cԛKh5? y+8_?seOEiڈkNx2IC|RlTmb[x1ӡ".2软NHT_._|`=%EI\(3t/Iqr($y5 4rڱB,0pzQYӝXҭ[Gneu5)R 1]?A_WCUb2ĶGT|BMPy%t +{P:0aĈ< 2x5wB ~gڊ}VEDݝލ Ǖ^D(Uvc^Qd:lOGVl㘎1ڊnqdYUt6A1e˺3đ| +%8eM/41'SVQ9<% Lńz)ZmAָʭt9Ox@PLFY8Qzq4R3Uo<? +%44?r*cGz5w~GUc9',s 8P*IDWerƊ&HNP}H8m1x [w=sU6םU t0Hm} >GKPz󿅌L UHݺ ^AS8,O-Yg/qe`UcluJ8fg\A}:1r#nqS(: 1s͠%}h,P$o@Cu"( oK ࿝ 9Vs4۷ ,j`H*JSkʥ`A>Ox]vgFgE)8?_FU&6\b;ں6A>^(:ƒ4U! +9Nr>z`%i+I/[x nB>)h7u^u}بY!nX!id?ugԈY](>zH+@b6`$LiBS @o&xcx%[hT"SNJ\ ỷ79DT`ÅM7"b-o:o{n2)IJc7وZeW )lp.8r5l_\s('9ҟ`]f4RJIwgim-2E$r=P@$(ZRcyWřYeCˬ̎a>v͆IOӇ-M_$J9KI3낚~D ]//Oq2̪mz%Mqps}(e]2yjN~/e]ϛz+e̼*0*13x6̇3g֍cygc<1=Uבֿ3=oK*0hqy=v!'5$Ss[M n_y4Ii3t +|xK oKNze>a rl4dU^h= +ҕQ xD < Io;)~Zk[e=ի0ei4>D I=ni[}=D$ngW$HJnһB2lso&d#lx? < ?9+SF$iC2#4rWKrt%GU_bd81èy3gqqڿ{.ܤkZw- ~.QâB$)mT)/%JY^IKp[bpPA@}} oGY~8qDuBL߾?S5.ۺI?:&FT_qC|&ֳFn5ixӉk toDC~1QMvشca!j(b{7It[{ڶH6{Eđm_eyV2gM='?JS.!~Z_O*H6)D~_FEnX5_agif/FVz,cMWE 9ҹHz&=F1*IX_JVc &`0D6)Z7]Μ.Ǭ?GUJ9ӛṤj*wI =RFiˊ3tOu0)I ? ҆rz5pruᭌ7Upf^ez*pϺ^APID^"QAJ?r%F֙S9~I iU2ff-izZF]CѾ#zOa+OZ_o_ۺ=aCyU925~&oH8%}Y1п9}?8{+1(h)h `!@O +v^eM<*vsHK$(y>%r(S4g <]dgW$q1ЋIS -3t^uÞCJ ik\q#UYgڢxG m;JȭfvMYVy7-4.3϶m*,4\DHgZ~U=v\xu> E}t~K)N؏k d?yq hxbwER^T")I17w x`>d*Mٿ"Kt~=v7GIdK cG4}?d"=ӿ Χ#FOeWj'!~# zQɑP\n2$nXuk׉kēRZK47dy~ᵜ3z?7T8,]\%L0ga6frwBU~ivYkYUcz.1wBOZV%FI^P:筄2U1찫>ު؂r#濉\HK>A;)j4;"^!i3HP_9Vg3q: g:`̬ +/hlWʘUl1èA8 =8Tl"b+4= l71(^OϸF9 P!nD4$f'.y +o=̽A@o%pq`QPQbd `D~/آf'hWn57)NZN"eU$q;v fDž<#p:$x#n|=~8 m>V-XSE0CDys*'.Ut Hf 0{yfE ?A4eQJ~5϶Oo5 ou鵞72 Y#B^*~I K9kEmzjΖm5+$.10,:>Ee=4gNR)O^T5o2y_մ4ìAT 0,fy*$ rR9C*$@Dqq&JUoaf]s#Tn*{{Ta0_%0Mm*]2T#@TZ[2۸K; #5x;W!6<ГĈW{0P^K·ez@c?@,Hr]Yrո@hgQi" {Z7pU ;.PS]:D z"f|i4Զ|]v5YUcӢ=g[idgԶHڦzE fFyv%-*ps^NJXt$-tm:$PꝀ=644U}#Dӏ__oTEivN4*3϶CO"4(e}9f-i"Vf^g`#g\ +v^ǜ +70+X !CٷC7nTY>HQf$px + XBN bt*$Ǩr&JUo^0砆qoݥωSYflIvg$9J԰Q?7^z$OhY %jCL\sacKpGi.Mo9mOgd-.ӪJWtcՠQڅ'B~,b]>]g!kXu=҇]~t#DUn&}Umi"oq7Q D/mۦGK^i[bbMP Yܦ81gXM&rXaԠ0爔])zbՉk߈;=EGIz&/.x*?!u^ yzXTec0忁A2 W}29Ӌa%έ: ;COOq7o5ovcYnzzh'@ɧ8Ui^)ed]PQEן$]} jҋDiϺqfX\PЀ J=I򔍯 +IǨCj9o`<SL2xI@"H/; HhAE ([!eVIȠQ /~5ypQ} |f*4$FV֯RWj`lK+d ^+jӣ{(@),$ 㙳YM)NZn†Ndv@O+/ 6tn;MnD%B'IW 60ϲEWq+NZSzI#@Կ┥U\di)}2M +1eQD`1e; B!": ƛqh.{*g,lVus$C  !6\4se%N,bKzr|+5AF*M _xF?usqLEs `X:.x[rQ_h%wI~_t;6O=+'̕sp9e@ڂd0w^ޡ*5K#znn7*Tzl?bq.Fp#=-%ڈ"[ʔRJR)?J-kpU;wk(W1ii[4k[ܰ K)K.Dn uow kXRB53 {l;f3nnQ%, aK\m|r'ҷglz>L.`+ȏzF!߼<8zbVG(o_ ҼBOq<td삯Aؔ4B ,3r{{s9h5G6q=Gh1prϿҮETMp'$Nuz"SCĭs+o¼tF>-!ƊkjSXw ¤zxl_yâ I/\I+/b4lYV|jcR?Av>XY gUt %q~!ϫfPT/bh= +$/|=oFΡ +_xȺ@EǥQ\}tM-X5 kX_glFl7uk)&BF~.7} cީƌ/D5nBń ު8S$~usC 1"hEyÀ܁>q=ޘ vLԮۊ0Frl9E m +nP(Y|~k`=$}C5r}bo}7NFhX-y+&.ӕf^*[W.LWVs#7a;f4P/)-uɐqP"w4tEyF|WB%P?`8&ߠ?vES2j/^ NϠǭ@_6"`7[_rp=pBs"fge%qg~-S`3~ JhFp9 kX܊l7I(A<=$q +߭`*(:wxBƋ4^ |1aC8W@Je$7s$qx!Atia~3>fa^ l- fWB8& G7(VĢh eʬ~}[ȵO|3lVBZT ^ . CgPWJ@#R':9h]Bц#80&MZu5^/c6J%@)ݣ—k3<8/A/#FE;0WJ<<%¢8Ւ $7aF +dDpfax6ހl)dN=+&0 .7`ˊ4=g"ZշY1q?wQg>$ڪ8VWD -:dv'$u ⱟJ_1%,E@y ݎ8S '8i7h{zƶn>1)(:OZpBǃ:Q sf49-] s AcLەY4A 7E4zOc?Q1u~cup3XTY˒`m_ØXT5nYo! qq#eØ"cP gЮrϠ7<Gij5oJ85l[t !?/!Q5r-'Ӷ@QcU F 47|_ _+R!IuDe\;.^ᎋS+2-DFq9S`ҝϒ83x塑{NaZ2h\ܮ|^C63hCi'=ja)L(FWs]œ944sXAZ\Suv +<<1o "G8b=A7_0]WFȶcHF{04uf| 1/Ɑ̋ p.T8;&!O(wB +h tau7Uۀ.q$ ; ,s{.qih {d_C ,qXIs w@ՓE8q֓ܗA$(|E@u s\V_jA+@! z(͠;LRqA +a5tYQV"kf:``8oxk5 mIo>߄~C"㺎 m=BBT$@Nc d|@mnGj\RnPMsԣ2|gA/ !zFQ$7b0c6Dn}5 +LL±荝hb7OfõcQ&NW+bA,}˹4ʷk|D;hp1D)#zryDRKTPyB(wh-Ý: UG8m_>F'Q%rj[]ٕs?G u^t2oA΋!<&B1f r2)B[_ t]2dT^̪S+k1A4yGphܴ\ r p \+D!m~K%z9 ֟qUXe z`T 8?R +{Z|!ٶcX9R䙤jiE4BNY.ZN2fTG0 R/Dpl8q`.qh'`XWra_wҾ~=F'_Wqi14myv()LL8y]zGOz `EX1 x9V*G!,JGa)]p7S+A(QCU]Q믊Hk8+㗢Dr[#,6)9f#)I0B ,~~uYqq\*sX;L sytQW&mdX +4E2n'B*~N]4'>&+Mzer;dbQ‰(; *;\wXM##VrꟼbJT7x8^[o*P;jy=:Cbo5p @vQOrc.B6TQ'䧒ByXӦH?Mҍ\V]>bgZ9eXF6r؋T P8ۢl#VpU R*gIݟ:}`9DƩx0@fOer"uL7CXmY4ȇi -CD$"v N0Qq5<"P')4R;$Mc?v^2@9iM*ZU(,f¹WuhQR2K].7u9;lH&m maO 1(2v׿COBE,I*4D8} (qׇƴC(A  R=q$>O޶ V6ȔoYhStY||GЌUWQ= 0 m_G@22z&M?>k!uo}A >QM6#R/2r_} XFQ=GmLN +o_%eyYWUFG})"Beg!U9EYEw .>fa +,o0:DGx(eeLM"2nE{? *.3QOԯ x*I(q+_F_x/@{T>^2X5ZR`u{ +)Ĥۇ]|?pn1"u דVgl"ʷxs5 "uBO6J#i#^#v<"(B;ǝrvi2cF<`7-M:Hض5K)m@Nqd]ׇplb#x=:-?8Fh&>F5yC8-Qj7(~CaYi겶 80:G> +8.7h9)r9Auc͂Jx1Zg _ }^~"sn0uC +7!pG@՛q7ӱ_'`?o;م^6ƪ7x8uܣu>puFQ8 7)&( N0'qjeQ(&ƔЋG=$PuJ,-2};ť$:GV<&(z،ΰ6xF@4  +r[߂\\F@g#7|ݭͷ%-qќ9="  u?o=c[)uRӑ(:j+W)]C/A:0بZ>0(XREZɧ ^mfٖ~&6+SXrZY"etE"$ee@0de7¹YmrnP+Zg(w3 pJ嫳|ZeTC_"I_ OM@y X@~Hq13P4O#9%HvGPH _P|- 5:Kmt]3s@]Wɉ" X+b{oUWԕ7uZr!Q*ZEyMBJz9+ۼ%M`&KjZޅIv\шL7r%uh.{go᪗|@E[ܮU ։"st[Ju34븚_)r`:.:H+?ޞl.l_Fݭiّe^*e!biuNDf)ztDu>H lؙD#*[= V{OvMXu7~R*(F<*wMX$Ya"YxC@) & 7pICXP,Zq`=Bw0Y41+YenU);pXT**GO>ُ`l-_yJ}o%,pck^!W$R-3wqrK>XHyU%?JKJ%NM‘<?<Vj +Jvi'buh)BfM޶q2C}DV9w[ 繥N⻴ \8KДiTM#UFJz4R }-`>,qŢxE:kF,\>IT'&=yY?qRNU(V'{!BRi7<Ԭ:TReII9="ͤiH>giLVxS%Z#S0H)k?YY Y*R'\5cR{j|=k\er+YfS^*evQtNnl,?Unij[W3 B%pe"El +PpC` Dnkm+P4:IsiZud&AnAyudDO9}]􌀮|10hc<ʬ.B$q6Bj,J%KXT5a"qE5m[tN`?Y77l5u[9\q‹VW K I5`mBebٖz*\r{S$> Dή"əfM¹#c'g"jGfr*)]5JRγkHqS-3RG"mwZ}&n~LRIRNԑ.:U7X|Ukmҙ (ILgV +AA6֎Mه\CZp%ʳОoN-=!}Ki6G,3L,F|eY4߷h]S_Y0xc  O{9offGLw˷-!>U`}<3PM5 XgCag1jmocVdK|IbBykQROnM&GO?A=uid#>g]N?zB-T&.wz?,2|Δ{[wBмPGD |d߾a7E=e?qqc*>\jEdBc9Z%JKۮ"5?C)[ݬx_m][I]lvԽ/.8]< wvS黈w \x+\4\ۜ AtZo=>قŜ =l8i9Dt.͗ )/gn@tAtB#bV@gk[Qlfn;wiAd[2#3c\2T.nM>u!\x.2nnQ M2Xqf8NTi=5&,;5D@Y8\bn=*V.,$vya\]BOTL}e֫3\maxh=tXaQSq +w\Y*&i~jޣbx`Z…R`GX!ZMQ&a\Zq V%K/ (YVřf /wc wK"{7$zizi{kn8Vi"ymydw xK"YȦ :*)f'(l: ò=.2pep: &VZVfUa@-+>@y`E)VY]H/](unS,H&>2gf}*U\tn+[L`H.0pkL*4El̴0lRG0hDT|i.k+ӵTZpxp/htH֜os}/;yy՞yGS}҃:{JhiÍ9d8'9;rpz -]ed'>%a*SpS\aֱX)?>q/| '1||| W&7=/‚X?*ǭn'>4w)][F0"e.9@5n1N@sEZЇDy6D>4O9#yW_J}S@flKp%dhʿq^ 6HpMq.[utq,YCF&k:/<ƚCғ +=_g~b}Wa^is?DAz쉧HTS?*ـ/{m#?rm<;)T i=^P9n F222>?5_}>6fWz F'v*nzUhKn{4{1spIoڰ,>T}9\z a˕ih42 r&^$8QlnzB0@fz~a_y~| W}0DL3Efԑҹir@6r8F4]_~|ӼüS2_6w>蓀WT}5 2fnWx]by*Vuh+!:!D)yI.Nåritcќ%D<+DvS56no*<o6o}c^|<[o}|O0o}[e~q>1hP۔8TcP4A9x#8XxQ~R`$:W|;՛8x0%l\!)}Ly~_}Xh(RHa1]!ʁ%˗idV^;XҰ Cw|ZT7I _P`k9'1 +{jpfUlII$ZSK6W9L^!q)y||TlX҅AK,.<<,N8 [y /QP] *],=e4NX"H= }7}!@&iCq>e -=į  +f%rӯ+ingpIGb_z. !:d. SiAx4 >EGa)SHBA*,xTH@?BV) <@<| 5m;.Z7GҾ+c5Nr#/cxYv;Rw<ICYNsQ/2.ل*z? = EoF&Q_ xȆjTE4= dxȊ7_UPn -%;s@=D)e0" @BɿL *j[R,SN (*D!*#x b|<5(CWC& 0mneoWmd%ﺑ|Hǧ( Lװ8 +fC, +~A,j]EIX ++$& _?58π HC5zC@g1\BM`_</3OJ:ECw11/"A"sSlGQ ^*ZJ'ԮUP]oTM*zC?dh/AI֞gB8k>oky"@ρ'/|:ﰨ~XMB &'Jau˩=rĂxieլ*B3}+IC՜y_,u6c{,$LI.,$_S]QdEiʟ)c/ gi0),B*e59@Xu7Һ.()؊ 0 +b)8"ΚgIu)Wju.~eWT+KZߒ %u2!}RWF賖$ܙQaqy+ +%Z-juXZ8srx"PQS ŕSgb%י%7i5XQQ +CgR"C7Щ'閌7 Ðh'W XAP C-S+z@W"Gp84ex-3`|ǁyFYQ(a_\ R`*Y wh#,t y.L~fip?rMtU Y 2PTj%V[:X}KԋUہ-P Zk x\pڳz]1"^k]hMBBTs<ȨpRw '&}%K7nVqZٖ AQF&;m]cr ;beu) vM.XҴh±e7̻%XS#d@,d#͞Q,~z%wKz 6j F('P`Ak W#vŽ 4xEL0H$H 2`"Uu@@;gQJ``D +,Tftҭ$3 A +ް)*Fq@[-ʨ)}G<³(KSt\oFZ׫-iĒ4ڀW$1˅}~6?/R0_ i _q!l$0ƕ5Cx=fw ;15@=[ 35܂\뛍ʼn7>Ch3j*{G|x1աLoD!w fDBT$&>IU:tAcq9p@/b͈~x騡U/ǿ^콥)%){TY +:)Z64U:/☸.wW}z ۲ŐKD8+ %+l Jl 7P58XVnN܇CxUuMU$gT }jf ۍRzPG7vj"ٱ]dH ­_HA"'T3/vBIsva=H owj^n˽v zٵ6篩[9RUIR wi]haJbxDͦU`s;,x_qX֨)|z%)* +qՇDIW,Uׂ sX'SU1s=!qq!]Yk4_α,$#hPW5mZFOK-l6 + + +C +wr†WK VHa-RK +jIdfH"[q餷|zJbKHuI`[I^gTc+>gOEZ+tC*f',N:RJ{Aa{r?u^=60<;ypb1a\/%"48 Je61ȿb-zjmVDN!`ȸW*&ŢV…υ!Yv1נԮe-R웣 %T ftvdv &^ʊe0$ %SuwfX a7*-G:B~-Oq(Uaۼ ˓FX*(]0˴uC]Îę <GyF??#j,0j(`'p"<\f ++~Y6pٞE; +_S~cgH0$]fLvt\8$ߟ®R@S)H" BH_rm9WV̝84/Y<\* > +%-Y$'Y ڡJvەgU*aW0S(r]ϕV$RZI1EqMBs8s`HnqRW,'}pbڏJnfoxYNa(S=0B%A1:|F A۸n^m)g'%4^]AskO9;Ɍ.[$6{kK?;JeBIwouabZX]v6|j?EQc´\uųVWcx\YX\ꊊc8{)D/u+?S2P| ]$ +I q`Q\4g 4}͝ڜ(חPL XGCc]d<[#M +cACvG=&T9,5<Kh9We@O/%3) Io -Axsއю :؃*_]FcZ\8}K!H'D{"ݚ0e> D9t@w]簭++dugZzMPK>6%rWKyJ 20AO!D }JUgz"I3=;H^m~Y7)w-52GYr(LI(g!^čub߭v6yg̙s{eԼ ]&8G7Vt#Pq=8'LuY3Oa p< ,QRM Z8ab.~ŧH4q&|j&We;/0P_8 tB.R`OjS#p*w3XcI"a'N=8ſbKO.r]1^SQ#eꚹۦNf|źWlqh>_i%NIɉWIAɼye1Z-lKCg&ЕoYqx8 +jaH,6C:&Ɏ.}Hsǰyiaj.>{uG 4WKiU^.v -DR)bqAuø/$/xM:* +&͔/)qVsGP~Ǽφ & +ϡnɼ̌yv,(u+~5QB6U,rwI~rRe|%3ieJbiQye0^ Uc!0+ Ε J.tlv@=4wZn-@{44/uS*x:zo~zF<_Asߧ _4'^B#:&K*:j~eW) +\ֲn6i^"/X"<H :{ +P>UpC@5p#R$^J$XKVp& ŸڇGn + Z[ +,~(hD\tT,AeC::&Xl%1)/JL iل pay4J<:3f\[Di4B{^B߀Gfu*ߝϩkf}I7 v~ʥGJ`KE )sMZ*K&}]!Oy锏x ɼڦ/iƼKˆށPʢC1kޙLī);q>A݉ ?^S,Ah0awHQu'0BD:W?]ɔpHE\:}϶[u SKZE*_M'L m0_u̹ cb +!cxf +6Tb 4XHE^-j%(~M`[,9̘5@~Mu)g|~2wZ!ߒK1Gn1|QQ,\SY w:)lx}?Ed)XykPtb]3K +׋t@ +9 {WzeP Z=66[ø22[X+qhZMR2r^[ ˼2ahf՚iC1[K}ٔh V9e(4e˾YGM~ 'G5UῨ30 策N䇭ć{Jj))v- flJG&#Js`[W`F/Kqz]*t̙ +:ϥ53%46~k͎)[3Zf+lL7f0+ȠYF=V(dS:'t;/  EZirī}oG:@V^:i. UrD~u +n vQ\CPԳ}[6B BG Uɮ wЍk\[OW.5yI hb5Z&L #6gYl̝6grZ`b62E|}`,FeC_}l51i^qab36|v(Q_`N{"j"H>.Ο[]SgV1%0$\'$Gm#+A;bka>ie2%` < +˱S24sd4mf<$)XE ?0|*̥Ykj~'k e 0I : H2aK!Ho D.caf +ȸc%^Y&,*66 #/R垟WHK5H>~uO [¤do=nRvT(mȺCAy(#`2T)iN]g['&MHGځz+ԡe߂g`G^jr,UDrixS 򰐻ef3* yh9v5t ܠĽ.uSFBz ɈW"(qgNxC[&!5A̞yk;n7Z&-[lsm ~ygN&moN ͳWW|^k,S=q}e--ZW!\?/%* KǷOmK +H7wmJ%5U蓰Y̫%H4Wȴyu~c3kbtnwg{[sc+7h-;|+8R#&^gfbM M#wۚ=3r+*,!G:2Ϟ)ؕHX]6iYC ºGzX)wvudLct=Q Cabod6Z%=.~ ~F3Tp$8G1 4rdNS EZѯ&ܻ+pq(<_65'/FΌ2i`2 ۇQg̛l(ym>&E,Ez-OEoN"} @E?/'T>๮`&E޽4Giʾ(= +m@Zh3k``xH>1l:pM^-s-gf]38F/A N ԺDW3kXW:1H*etug[i&>ǘu1_^\͡Uu|T"6,ϏqܫE诨ߛKGM<"|pG5 APP+'2~jedZ-`=ZF-lr:A7OS'6Èq3[إwl=6{kBsxg6m-.u'Pek C)]ڜA0 N|1[֚I" 2м2ҨZfx,2cKyNP,OC +8fTJdc~ 58sĺ[q\+T3i%'t +19g./qijwX|kߦO:ot8@Z lI ~g#_40c-"C=^]a/}_h˨q#g'x;r$g "D~8w6~gT@~8Ii>byMA! 2B"0cAH0 B"H}}ݭGm2le@٥-qXO9ڞM)ulzBwv70+3ܞ;󵁴HgY{D8.©:nOm`t)W{)_3>a&Hi{ڎ+sI*y*zr煿ş=ro|O4;4/]n%s/͋f|I#PXI {YR0HUUPPPU[,րiYՍm7+z 0Löm)Xd˔RJZEV:c@#(9h[{OjVVLk+vKʴ&}M%Ģg1H` Nەn|OזbjCo _'\G,R,B9wRÇ>{Y s?Hpa͎Փ(_[%5`I(Gg U}cd[ +''G40dΝͮrw @&Ԓ®@>I_wj]#P$P4t|cK*^ȏtj*ՃoSH0[##${ErӨq-GƥuA6G=|U3 \/VZF۬QZdOɰ*eVS.-D(..+=IXq̊Ht'O|ŚY 6GD (J>;&~;C!GX4~%58ʚ0YWJ4)UIuœ1XdO,)/KL{]"Ƨ\]9r 3Bj^`p`X=P!h|J*y4~D3h9,䧭WZv5eWXtJ u]ix["7'`9S@裌&*(Bh:C%AGo< IBOP9sѥX߹~D:sT>C @i {#\ C-p#̕fyS!\B=OO0ȄZ!KoS.?#m' MuA!_2"{3H [=g,?"mM$)gYY̚\$KOG/ϋ,׀nH@:B tIWZ%%aW]ތg[{?El31n7*l;cj]>&ErŤ}mΚ,*B5cWUh#'M06@da#H2%bTˇxnA[]h登.˛hRN**n36NʏdUJr2:PF??rO>U/X(3Cnd)nS Dk:tӏ'oR/9Zr5_gmWM]SFdAB¡8oiK9g.a?gqjq^)ʖ2fE5K5mivoNd*ou{1W*M+ ^!2ڰB.U.D롐 e@[Кt[' |ZRM,>6r} Wl0/Bv]оbͿ"uP>҂I3@㢒dO 6qeE)Ē@&tt"F\6j?) 4œ<S=pgBp@DOy^D ,C.D/ԾZWJ|G#Įf = 8^Ϗc=}Aٿn+%qr;lCrߐ{z\AJZrCt]/oW4'.TcrW"hⷓfyLϞ#hQaz\ZSM>C:Է}vVњ~wܠ9C]#m0 nPޜ$D0~nZHK 9>{/;ePQ(X~VqQ+oPF#mfYOÅ+*RURO!|A'XO ‚? +$iI!'0QFkC~ʢq 7 +"6Fg o@o.!_ADv{e5 +d|?4>|+(u&u G];j- +*dM u#]ރB~?IFfj #uK:oׯC|kߡKy|jILfkz *T*9 Oa $`GwbwKhRj;ؚ1 ם yRO!lq܁et?LDAH.qdwBL;Pa?xvC@~Z9 d\K2`qm/|5KT%hdIplq>Zμ`L{ E1gYFk>Ʒ87z8bƵO(cD +cD/!O`SU\\)QY2`;&s{萻bv5iEns r h$?P0-t~j3~)GxFcHroOZ߄_L!cM˰59Ll7x+ H5´N]M mQߠyTDl^hP̓4$| ;hjmg&|t֓fu pcƗ35G50Ӱ]s{oHUpЅvkyJD!Ae=yQkNke=!|vuTC]8zGJ~)X rWW \l=6 + ڨ2Jx:e:䴏űW)W;y~A =zVgiy$į'rNC;/ׯ2^\xo9jzힽ-gzN;!zJ÷ou^\H +84 d<']i%͊q >!z{ɆmGM;9h@ﳧ c},K2V'gVJAncLgRVVPrgNe. 5L +<B9C备>e|52 @~joV@~Y$mCa1o`@G=d:j{$|!edm6nϞVדju5neY8rRc)WM u!l3ڱNF>.ӯ͂ĝۇռ!G`Lü օg)3jb]'o OZccg) +Th&c#1s$])5u0цqpeĢoA?Y ?z e_^oiUroޤL7'v? >v#yus 0 +`>Jr}r5G`q-!x#mnȰՌ +hX4ʗZ9âyמ4#)S6^\Z}n]aۋ֋.)GX8_1#iNA%~& -z?D=DP_]'%+I`@l.X(Wj; +$~="{tq5R n+h{"Cٳu\At ktR[?5ϭdEܡ9݅h ux!%` g]NXMq?8ӕ3uc<K5o#?6B^5`e8P&ǭ}a/FVU`^qDŽ%ga |  %%EndData XHRaster N0.24al (c7ea85e6-4a38-4265-9968-cda6cdfb741f) /UnicodeAI24 ImageRawDataUUIDd130b5ba-640d-4e62-8197-5fb136258cf5Alpha/XMLUID : (Layer_0_4_) ; (AI10_Art; %_ 5231 76518Lq37% 1G=L+$099lZ_.vdgYϩ"؜\  o`t/~2-|Kdzĥ^8pao1wiICŸx^cc4KEZHA^[0kd;o˨S 2ShF$IJcH`@P ƊgE` Bcp@h  + ICQK(@ +iԑCœD8%bUd>GFV hFKhr,(5!q#P~VՖ|[s-9fm$w3vL\C3wBj|{?rJ!y{ov # r9gɻZ <:2nVErqa&g_n+ T^bz 6z:g&UUZεխF5hAh +f8(P'Ʋ$UϜ1a424jErukOa]KU룀 MswŒѩvyEZ,`^Qa11BsQ<>wRn(VzXvoo i`|@N1cDs +y#[l,)bgH:7ܞ*j \8᭥]35"KҪe& ֞Le&A2u?3(Z-Um*gv״+CpdRQo'o`Sgr x:eO"W\(Tp6I|B΍q39rbk!>8Gg;]1ßci2 +PvO\7؛Tf%Cbpw„!apec#fGr +ǜMG (K0@&*処/Y(L_NI8ؿUcCm؊8dHz}x”BD/MCyf[TlN6{?4K達Ka#ލ*_v(. I]ʉ $@"v{Êv^$j!xUV-W! }4!$Fd[Z+S!z{k>&gH +ȯhcs8aC2A1(b0; +R +@TZ"H:)^)>̫@qB60DP8ĽQ2ڼec\Y@Pw +VAFrqs`G?ap;ÆLOiz@ G2,zt58$;Y݇B2RhySߵV$^8rp:` G؏tHH7WZ xZXs}1L6 No4(<1b3]PIoKP-5NSKk: +2LiZM9C}[6@ePb`< Nn&;Hp3"V @y@5@'n@Dݴ/V$ZФcʥxY9o; I+*ZrdWaXW&*R\Ԁ)DݽU>K)q慉O6p:7"oSUytH|R3 {jHQxvϼEZ qcyyH!똵M2|<1 EWX9"HNN&mF|~|jESlIa.ޝ^{u+J UU˜eB,WNbRmDrl6\$nd0*^05W7TUi?*^ϊF`EيQE)F -f3I^3.q17<G0%5GUR~> GT^BFO놬oGHRNC4(?Ypd!P򶾓Waƴ{_٤ѹgdtY>9,sLe8&lcslk::l@BT)yGcEŚ-ltip.`GQsz ZKU* g9Dij/b-*'ᢣQZxki0A 4 +6x1/0'wa h*+ ,Kɕ,R\F|]n@xIt,,-v9GzE^(R6$3 ^z>&PTiL,u|:1Mȇ# }FxZI !>S},&2 -"A@KZ LۇSę vϪ͂ERJEHRg<͛|P\n"!9x`@aW1{OBaBTAq~L /*`w]j~p&r%Ge4l_ȗNTY"ԏ0KN3N#xM[:j*3;CbL5HMJԏ(J:>v" 1,-1βןDȍ#S1|"uf  +`VT'gEUhۜl"**mM/l'V+FΛ/ *2NY2x,ʟ<-=OeG]j(CՆbf!oᗺa)0Qp\fg객s/$~}8w.QF~~RIv#+8x<y epimuɠ夦킒D-|Pj"w#p  ;afOsTѯPPCVt^ZzV8߽/FYgOjdž^ךo\Hd(5A9ýi|Z1CE6-fgSXaS5hpKPuJǒo @;6LаU K-%f5KjEA(!P`pRS'SOgYM  +Ju[PuD>fdn`ax PDk`f.7 X1JrN-WAˁ@R*Dz_IMDԴ-m=@T T [l(tn#G0|vB]`[ wHb$ OPƨL 6LE!AM+5s6 + Ea`v >RɾJw!xa ^P/S=dHICl|n#}G=c*frу@ؗ\j?ǂHX(W3U_1߷u$ D>!1R[0rIpR$ ?S̈283.(y'fHX AM5n  +b|bv WY3mbd2ܘd1(:RӳM> Ll'_p@ bZ@kwI3L< c]_!_sF4?$TUK7G=&%ov{| ty9d*ȸ7`KAAA1m߷,EPA=dEDD$!Ňq~ |hØƂS?a*̫ ׺΃ /DˀÅ + zHlvRo4XXWCE( +tdЖ(;2/j9\wwg2C]OQnNQϋNQUJaٰ6ч]d[!,Zk]nfams{2a7C\Uen>^uQwk]De[ucҜF}pDLpT4H82K c=>AJP#} teVSfA(:2R_U 4}V9U@рPEăA%tMMweֻH!RlH0t`΢|P "+t_tX4^a's)B-\tnC6ZZ$AˡTbBSB$EovҞeU9&˃d@'`@% + 8$,\ą H~a52⯦163͛c"`PFy@H8$4"B DHTXpD<4P@Dzp:l蝈Al~8ϳH K +Roni6ix:OA])[!v%(˃2ע3lƠ)|}4nZ]br +Bnh9thUjü mղk^iL>Xˢr1E@EsE2uL55r4'c:P?uA닾ͿHhy^L)ŐϪ}}t|Qb\3<ޛƏ .S_Wܱ-Q=TL/";tJEA DA֥߸d)}ƀq! =Vvc) (f\BT7=5VL$Ը2?M3m[#V=)( c9E`U}/T#CyQJlk+ EYZaDarYsǂ`0|]5׼9|o)OzP8j%=XDu<+F4pVߢW݌|mp|/YDƸvVz{`y»cLRܡ7IeF+EC_8uu]LGZ8jN㱌+5GI΃.kL FiZs۵,Y Hb>] tR 2Haz[e^\7|9V]ƀ|qCoC@$I8fEn#Cɮ· W=1 1EPhJ偛Hr֩)N3{֠ +5@ְpC: ԝ-Au771H?b \Ꝟˉ+;~o)E|P1%Lvݞ' +@h6TDr8<(V20痪f̱]k/ +[$Y_Ȗz_q{8Gh#q$,T6ZO-ގj4A K::.pt;(tﷲzA~P$Pʘ2D€n,նA2h=2֠Oٮ9]dPF)Lʂ*unUy սV7@h, ^ekk.#V5?/ز/F'RuP&pʔG; @IN)n]*\Le`4ӥQA|2bolN 63نĈXz@4g7Ck6  l\N@ w'RvZ/W~9TPB`fxyZRKG[xk4I $4|M;sah ̅=;uq u9e_^ aj>-=f(Wd&# +i)yŸ~`O`Gp(PB'\!R_e h& P(`"/`0:oJae~Aa_>8P|qDQ5%*fEV"œt4?N:ګ.5K"zVpzxP~Ua'z!'v[ĦE̽ +.;eZ+ݶ,ou@9#UN1궭m>8~L?ZgNS+y;cD)M,X* ] bU̦ WilAߵ -'pG-plDqx_|vRDW,)u<0bE}M!i]!&l]ɷ'&V ) iXVC:&tt䝁4SM Ԥ5*""1! +BL,| *;iNDBЯXnNe_qvh~2<>/6]яͼ$s31AӱҤQ)Cq1=4\Ui:g8_3ы +qNC43\?9g``V9 ,pHIDm)pX*mZFU`;ha:N4D*8cF6z;TR08%Tx}Tr4~?*S˔1],C2*݇C~ nÂS%AZz~t={D.BkU)?so&o6|2Ƶ@=M aKyQ6tw .4٢ +1,&{H^A*B*M&h?HK|9?O^dXagAfG'-a wϺ l5T7\%5'퐆4pZ̘cA-ԣ71jncy/2SQ(>?2*__}6zrPg;ߍ`@J'KS萁80:1KO%΂+H+0)-:oPQ#:ENu9+FpZͦ0Aq ʎ\c:!Qf5A9z@zAw9icb<ijk|*8NӁn8bt0h>bv<;=~$7 -݇Fh s*Vr*]NMW&svM"w㏲'[! ?P}7qY'UnC>8?0}>u>ĜHj]$Gu@q+vvKQ^,LL^cűfx]_#vθN#@2Mb cv +i`t opWH +QHRT6"(\}xd)%ՒIZԐ[z^-Qah#0{W@-A{C?EIbEq./@Vz0ǫ%Z[O%^K&9-9f&M}ڙ*y`hd@jIKd\u5o&d# ntpLJJWEa]=gZ-+Y 癜jG0-Α3]@vO7jdmWR(6Ѽ',ģ'#Ѓ|GgX3\hl"JKW?ǹL[Q&!4FhgS Dbzm~  +7ɉCCq^BP:7c^*+Ɋ%,'|J?#35RJ.T/ݐ%9c,$5 a!$AB/a9 @`)@E$"aPG7jn"93]HxFԞbxژްEDٹKš8U!{:]0c:-kbھ%}WeC`Ok[?4nTG/|HIV8zf>@݃zEe&ib̉0ՀNR?Z +FX<Jot?ǐ3|E˦]_@7-@CPxy<9h>5*l SwE4Q"tSɀ +JN;C s@SđGv %ڳD2$C, 'k!)9ġ|ZIW#@MXxjd?^gJ9~=RZt. µJ1 d p[7ڬ *CAc<@#d3BLP\=0Q^M.66 b%G4_ϵL*m⧠2])${H zscByݻ@/qjDa@}u:UTD*h,䒉 ( +,jBFq~'P5qˁƳs )74PD/>r +MA!Y%ЖI4j +h +ͤ[QkڙhNVF| +vyVdY~V-%Y۱'@3|eɏ i,QN0pb#T[ Cm4Ee}W&KRCW S``q!Wy%n7HDOZ_C-ƪ3Aǘ:K@`V{Tnl+O(W8 Ƽ*(R)bk:ne:lҤo`'}Iw/Ea=PK~ ]t!/180X{":P~!b0t"7EsWv~XJ4]-7O>OaOZq +mS"Tz`HT0>"2~#l?Z7cs2M2%.2j6z^G~O: A!<^%#!i$Pe5ftJ#׬du`f!@@0")A7.Yxh8Fg6&5imi0FhR˻UDffIE=RjlVI&?yc59Gcp#fc|bY%A9,Ix9He  j; o?bis(vlcX0VxXڼaB(Xe̛٦m-^}HdUpd(fCsrO'mz~$QeXN՜U oE8H,eD/t& +*?Aࣂf "CVQR +I4TgJ$;+.l9e*zI&VΜ,KGQy Ҭxea:p_uD"QꑦJ%DKe +،0@lDpd@Y bGYUgRdf +IBEn۞|,ixyfba 3Qex0A2(jJ[T.N&N>zkЙgǰ랱n9νt?k=ort͐5{n6{.?[7l5wcнw1x3Н_,:^73kwܘǏ3ǜ5~/^"6w^fFLVW X:kk߹2[c1:1my߭fd@ˡC!Y@ JB*e1ΊesN:IfcIV&NrCCMt?CM Lj9jQ^ O0yg6U<%"҆6"wsyrvO:DJNAۗ,D䰶sbE2Z1~ߙXm͘Ŏ>4e=yڐx#9 ,9$GjP.ԏ 2ސqPĄ5oom (}<ŭ( u 55rIa,r";c|wzeb !uݓWV%mB5==P;5%qGI|DQ̊b[CPax{o"lFDf)Qw \dm@[yz]Ůtr wH0N7<u7Θ&p$? @ʱf|aւ5-yG>zjmZ1!Ղ(mdw\DZY'jNY*̫bC'z&,#w-\_L!).8L!]1x4NvSal dS/,5Bo32g9^[{$F[qXgBJx}FOdEǵ&33aG9TӺK6)cdƪ B@I&m9+| x|z endstream endobj 13 0 obj <>stream +d6V V+;9DEFռϱ΢LS>I}3kdC+!M'$V|A$x|ȠAOǑU`醚"&kzv +ZSUwԆmj6&)&j~u ϳp]̱y7U(``@: a˳ ܍)1Al-Ek2c"K%# +qD=D1 +,Ц+er #([n@4^шnIa WQ$TZ$Q1> uBa7C~Zp^󱃲=' +nL"4n̝3`qn/*Ҽ,8ҵ*doqe[~mfOJk뗐to +] sca nUVMTpNg K +bƳH$)U#)` 7?BҐqkD5_!8{͵AfԲkJPV"m,wXr`;l|QqGwWRPfx݂DoINJjj-&j-LZH8,ZWq}JJ}㭮RӻUR0Q9-+^r3V[Ķ0ҞٕjgU\dzWJ+mąI} #]qXRɘָzkƶo7áhȳRɴZ_@̢]jY^%/s#UL5ڴ+ODfsiuH8a8,4Y 4 0h4 L@>"j$@B) #)>Zc/VKtyaRHSj-3JٮT+cm]*zqIx_~xY?ƬL_R}5ed6þ[eCc6]17^vd,]{aR-KS.-^#_xYhO^Ȭxnf»Dl¤ą63U)fV":0 ʺ i\WTąJaequݽKV +wso_tWI//Lj3U\_*6_l?ZY*ig PGe}4E8 ,&¤*j[?T+msZZ7r1e{o}ډUhZWֺK1{\4dc0eJq}lr݅I #eRɬuƺ¤Xfge}vU[j !=h@$ E2 ä+UX\o~oVu #i_ngmޟG7Kʙkߵ9ٮX po_hz_Iv<._70D̮:UWVڽ)jNQ&<@P!pNEha& + R y&=Q)I$N}$=%H"MTIH$ 5U&J"Dɉ@$CDT< @ h&"E@d"B : Y?xDу9"B n1- + +*g InU +d0u_F5hWޢ$ 00;`méfmw* `"`ŔhE} +* ~:"AWzNY%Ћ}4aK3SQW]zY )l|ۚQ<3Uto>@ 7E-sl?]hE4pbPN֮fw@|x +uJNy̥aƛ-PЯ^/̷O +d"Bh#L1TZ,R8OF*-gSOcuf|Si\w?Z3*dM1aYv/9; RHu= }@{UF)o/dܧGIyТïсRV 5e^MD;|nGT2?dv5-8G! C+@9H'ƝHgw ^>:(_Q#_0L,<xVLhÙqy~XE]ux?d䗔E-ڙQhk(XA[ϓjȯ OJ.(CB퓇!z}قhE)/ldd]mqưT'HA(24 9v(΍|no-I)V$Q84B|rDVr^!}/LěI1=2K 8Rq2Ktk%vI |;hkğL Y DGls1DL/ #o s<[;G@~OYqV%RWj2TQ4L> a ԹƕY%>`R`yv +ZrE;]V\=$da&NC !ᩪgؖ:ɠ*r  풦PK u +F#.Ej +ѳ6,(&8x %B#4^KPUi5J/b4̢bޕi d AR/j~Xm-Z 2n+89 ,3Z`t^d[Z4pj,YNg,mcC9y2Uq?ɾG{"0XqnSyЂ_>HDL4i +̗3dv;P93hІxȓt^! ;շ.[jIm gVV*klDll?!5ɴՂCz1T(^|ᆆכ`KD˕HUrdX9W;&:Ļh0+o H>xF;$>g *NЃaOhybIzz8aC + +^vO۠i':Z cv)Y=3D$4O*|l趵(vjD3f>B)c\W6(7mӷ hq +ΩIcYjVH ֶGlĺ~1Js@QmHxij,51{ҥ2L s?Y)'; uDVm'xC)ZozS 쒪0gTN/}'4D?l?l5^DT,Hpj.3mZYC8S&KLa ;ߜY{r9\'30$>Vۿ7W-=pwl.v~;vrU*ZSu,=gskGsk=;tr9rƾz߹/~E9vݮKMj\+nQijzv\xl$5w=I)GQ߾o7;ώk5SZ;\emozڎiN7tSo88168Kѡ#h4L+[1Lp: HIXG$49(q𰐘0Gذ$<H&f6 +iFz`rlkM\͍,eRG4g6 +"̮ٸhRrAJ #a 2>LR!JۄEKEThAJ%#O`8)%~(&{AJYi%' M8*R*4@nRN^;'Zio|1>0d@)O4LLzDvl@, j1`(ͪrvDT~JR0.PIr"ntIe~m1\,T뱶ASaIXtzL@cE0FAOS^=0?l; gAYeSs=Λ_guF rr̲,((MpmWU tQݕy)V)xX3c?7V "n嚛^( oe7kf*w:@!S'|2|~\O6P~MF˸Jaw(D<޹J ߃DWX"lq>\cr7&[@ei(K;hLXD]Z '#Kǘ Y)̠t.'}s:%ZT :ya,@l?$&(=7ɌέIvEw|Y +p!(8)x]E(#I!#QIȪ{99GLt5jxW67D)u|]BA6Bxf1%?P\ zI&c T]7i-5Fs(3֓Ԡ5k&´)#N/MgwCgL3G'/nLK#2/AHU ɐ[%P."BL$cik΄! +i{jdp-9QQcclt /J}z{:GJ7h<#ie#++*j^QE/1/7x|ϣ` ʼMmE}lw.)üէƪ׿#Evxr3I!ФT>PaJtjrM"HډvdmāFX܎H&Q*jzK\:7JKl)9xx!g!'[_AR I;5ESeF{+VhK +s7 -^,4RU~F.uUr{ +q0䣢ɮ<Hbcc2Wo)ULsdl;ơ4S!=l&ݤ{["ݫh BH҄"ܹb%d 1aP.6Rs+R9{ OFEU8vtgoXkB$rEI :L>IIV\D[#$WM*gp#,e՚,^,,~U&`F4ƣڳHUy`4®W恱A*{ʐ)s#2_Lom+fTc}4yB@ͦW$гBpa1ʅQlcz `[y Crr8kAiɒ!(Er4vZ=9/娢"ĺ7ܩTcVp"Wyӹ&n^:߄'M_6dMCYrN'. S޷Gn^iLdgIDqnTuK(h~2XpFU 椼+j{p a / vz#FM7SUƖ^YRr1BtbCB_8Y*L +3ןm4Ӕ?kw;[lDaU>)ۿҒ &3փ *2 Smӈڠǃ9iyIn/Zt`[+xAkny^|/d yޭF+Q݂lEG/xTq<eMOWGɚ;-,nx*6tdob&LJpm6 + !@p!/(TG +–C>Y*T4*Vi {9.9SL|t2y@ߝ۶%R9~WJcK )YvsZ4K0\~u5—h_&\Zrc|ީ=gW./PQ4Qݧ"#sp0U'S,mAMD>NZ}Pйh΄Ky,(< %$a>gI>Bµr̚aO3x +H +F +{R6\A*&$ia,4ٌ1 RN Kz9$|ON}9j^>sQT62^U7*#vc}5N!Ga_u +-ǿ~LY{Q,I}6*Gy)X,Q ^ wL +@*>zfxЬ/ +pς U63wNʆLbCj`!WiP?,TZ +uB,i*\L%R\^* Z_>[}(N Z/Z0r 8f':ױY(vŮ0ڪd2U"T!W*nl$GWjr()("HkN[uWed`ISow@60iQJ)"$!D)`F3|fh"qQ&٢HdbI"Xi5&B"6m`PL~} Ǥ&4(KGTx:>N.AyGÊ<'eldnҴ +bEh +*5dDN@af0/`RZY~XLg).)3MGoCHZR`l^f@ @BrHTJzA<Ѥ)TRj(W{2Hɏ&ϼGHּJbNM9N_밻9Rݎ{~ z *pbB h|l>-: +en%v8c ) J#BsD4[c&JÜ.ݜY(+X)+vONLE? b F( a#nybCB]5].n@$Mu{Ej(mܶ!eKD,ΡD$:M8&8l|XxQ`q-p!BGYI:|5tPN*uC0C N\1]fN -#`Y +S)SڢеPdP`A<.B'P'YBpK0J'l!!2PjWWD` ' l&} .Cx TJ$P&|gA 2WAA_G)-aZ +PhL)@LE 4aehHmA Z@*T(v[u(%I x`Ʃ[vtQ/ڑe`Sdt<}Y~YSxѿ"şCjߛyj0ES1T7Z&< T*0Bdаɋg3H \T8ؼ!54!qFcˤbݱ0C֏]aiDD4Ɉ[>9Mk<|D &,$WG* +qb>(EZm? ;n>-nR@OC-Nh i_qGnWAuU>%*,c Gȗ/#sOc /W5\ 7 +q8GaU<60lM! 0uy>K7(.Lt.u,'ґ(Eo1H ٱSn\wG^bQK\!NDK4B*88-9y.6fAʹVIHwǥ!RV#G@R$Uiߒ ?INYq xVazN }1C$ sb9S`B]rt8 ]tf ^!t5b~D{@,vS\.N,8SۊPP$i.Bg-TeK%Ȇ/mz,IMdr9!3ZņY4D5R AjNCS): +-exf29+!ԱU0h^XE!j>[9LT3_|~5 irumZ,Z}^&!Va֩7X\RLlQ +sTOX0t--=u(($]!" Fwc#o.s\+2m&;! >FBәQGvcg}q.mh/ rK")3vc$a' (7_T4]ŭ:ֹ{c=m/my{| +`a_:;lK{&i}5*>`7F9)quE܆#nc)B @pb)/WU/jU:Io$ Sk2 7&~ sa17Azw6iA"/$Y |#`V+R‚5al ` ܐ(j͆C 5#@Q̇9Z#  @y"p AL7W,@t+K&Dyi|p5A +{]"{$:wehlEK/J^)U*B"R0PSPO)[h - K + ')0\ I!B]gi&+M+PX%`qlx)NEjD?>&&*ML$r=_,&٥Tw7;&y"D''M ,-9;Ru,,G69g-.5\"l(`0?;1]A])H6Z<+Sz펡>myT~rB60Ń ؁Hi`GJn`dk &mQʤHm$_ɤ`RwfI;d?zfcl. 8!̗ޓ;2U;Q0<"_x%&Yfr̞6@i?![E ,R)ڶCUf) , ?}B%xӑ~ ~zKE}p6cd#o%FV"Ќfbu4-̄CRHdL,V fbQބ + +w#r祯HSt +lPLrW䭗UHސ!%-lYN˼ 3ց~¼fal( qeh*B{Vp&M  CI5*bC:o 3_g4),-os2<ǐD8oLhE470ZC4hƈu*VwcL¼:o4)1G69 4Ñ.2fDY=>CeA+^ G $Tϩ4[\ >i.co$UOt@tg(7"BJ"}9P#1Æ1dҩ|{.hY68PG&w̆G8_y ゛UŶ]j(čeꕣx5WQnh@@[`NkGl^OrC(ZJcqvqKnPSk(lrs~9P_ +&NgltZŏo4r`fj͡pb׶ Pg 'bn;tcg,1΍H 'B%|)0wQ> ;yIQޭ)ag͆=B=m^!}NbLb^،LhN؍k |'-w e-q}K|?1SBb"=tdۚn~۝H~j`CQ9F*c#aF M0)ý&r TH|~jx>A4:5Ou,˂761rc-_xMl7b>Kw|X 5&&Мh 9?IQ_ZRY6t}kEB; +g_)>hX-#Z'{H3 >+7>\!;%Ȕ,̑~Ā>5CL03mL iB[yyFcxh2Q$*bغWH`ʹqC}'KZwx<nB/།TD %xM3ק9c <1"pP(E'Ab"uI:4֘HVjR Iotkw/u\l͑Odg*-RCJ5O&񄺪~1kSC2Η.wR Ad_?:<* YOg;M=u6P୔#zkoq8%78ƢFM(/f-s2a lCg~Wpdd1_j+lyCl2u +|Vq¤(i!C>3m JSl[(]e2אּ5KwKKRn'Cmq۹(qR+5rhs$A 8!g*ZeCw`j$8`,AFio +&u;=p(b)'e\)5!)vNW>!y.L I:TtC-"*zD;J$?c$/: OP9ߙ/Nr/u-G?v"RQ>?ZCMozMΨ6̸j`e S8j zYp}Қ0?S~!t93Rh gв9JfV Aj^k; ;>[l1arvYӕc=MH*?q}7 +[=kbv X6(E1>cLhl \twĀ]yz8RhLӅ(^!*(<}مf}np&l}c߁Ezqg$;çؿ{q|aNb'Q[v3 \څ6H kEf_{帷6)5s<`AEbXgve($LhasqX%|&b!L01gt~WB%7OΑΪP_z~b`bQ}Ƹٰ7r3|%GaoQ 0#&lFfՀE&U@/gVL&"+Wxl +KpD:A@r 9!څe +pݩm]of~x YDs[a-I2),XCxL6Iﭟn<ž}$oZ%Ɣ˔,2I9&?k.dvsvv㰏2EjЦE#G"6B% -N)@X"و\$l<'i/ۡ~d) [(qZf;>bcC]qkyD9 +7cRR>ϽL%~Rt8(垼n'$fvTR'/ [P쵡 ks_oFH;/N8Ե}R:&<@'9K?"S{&W-F&(0DS+i5G DVDe?ʃ&bJ˜;LҡѡLѡLU3M/쐋}F Ű*(4ˢ12gSlϬ>5eɪ p-.~/.žꯊ͞^#{Kd}F=VX~5s冄 |UX~&GQPUqt/ҚTmPzu_cjzUm4-yd?+$4aP&TZk:s&4kL5f-|M>,*C8RY꯱I9&uշpYHToMHQJ2Q>p1^xEtnʑ[,XL蒄M?7`r=f0iڈFtW?W_lllvߙ:7iҝ~y祏tM++LCb~áЫgBeL,!\tZx00D -N0: L?`޳|ƚu ''M[½'Mk}-d9:E&VF]nj 32jh7oailgGm;K0aިjyt.C?ʮUmgRGa'W.a6}Mpg8/^0"-dLX3Mg_é+Ь+ccWggI¢M[6j6Z0[EV!@`$P +*)$Pk - aX-!fbZx 3 fb͆g',>)S, x%XX}uzYügVЬuȞ4)_s 8¨%fdsd%fdm̠G30G6Yb&}泑|3lQn˚8r8-U`ʧZQ>&i&I|.*)ˡ> rQy\ w;1-t\.@I pjnh!i-dv$/Nanٴ#.h320]@8劔^B +QNDO6>iyݹ4~'S3' ", +p+wj4XXOLxph BS’vSݭQj[E0.d!^HHayaDf@TjiQ'~iQOY߬%WbsiO (bصnp_? V곾% bLګdz[qe)vyy~X`9kD!C?.gf#y I^IZgO 6/!;iA($kTw| l~`J@'&_al, XSxЕYRѕRǨ;Ij&cZt^oj:#NEĺH\>&c[[qhgGԇo\ ވ@%ِ֤}ͣT3>>}7d.hgdSmᥐH9?̤{ys +g!Ʀ?9&Vr<D5h?h m@5(;X4IH=aemJeL z2&*f-?y-L }'G:P Ky#b3GWp`|BBY `, 'aӗKH%`'A~D?%SȓQOWO6/g\|HB(`dEL#hVD>H,!p JT6D'|lhcm2HtҤ(Evg@;̖xdl8McМc,E6Ԥ [L?@Bf*KWE ԇd\t$lF{``?paj5Atd`Z66\H 붪^,8S2V9@{P6)ܛsJu*mD#*KHPCihSggdORĕRJo,-d M`S|-+BeQjdƆ!D_B"!Z\*9RVʑ';g'(rO*h .!n!v|62†Ȁď,$k6ZeCQ3MU6uu?jѝ>!sma%[aсt(c>a"NG}\}VGbFDhHع;FCA`H7HF&>K(i0@zȼ,Y$jlyMm& 4IBϢ5hD&9aC4}w~!zip5"0ah>!Mâ,)ԌŹM;GBgh 4RNd^541Z b2@U"*/-xRO(,8! *f "laQ%4f6wx}yIw:%L#uQ9V$G&F1'tsR9KeE#M 9g3O H" +(ɽApm3ќY-;7¬XN,dC "aP" ~مZeð*uuh7QY[ `eU߻pkA:%}'L Hڷ,>j@x\rg·BˑͲ[]"G?r߻l8\wCK;S#Mϝ + qW6 Eb84ZvJG_[o4`z'pDid>>gC9[ IʆdEeC5>ٳU|Q|*43!Sܤw26gd:Ey}V,!JWT$`C) ++*7Q,3F32!',  G% +%#2}v>T6<\q6|l .TNP$,a` ٹ\Ͼ[կU6\<3 "B-KLp3ղ#UGeC҃Ϩ!ra5ƖuAr0fY 9ro@V#fBw`Li1 X+uQurwQ^]ZR"K+[56$ta@ >3x>3T%H Ga&u;̯R3ʭUkZIt&G9 I[XDgNs]9vAU\#3~77tI@fC(N`N M>b|<+Ndkj]ux˘0]p(E)j|3L,Y>>kOS쨄5%o<.wr0mI왔0) w5q7dUVl<9/ȟ<,Dkps9]E7?.qe-*la~IUt?D|E..FI%-TSe +jK_B?A礃ϱ7Fmir6Iv`SFwQ*ۊ7~_{K]N"u`y7 4$l@Vp#oSӳ++ih$tCWORfbҊ=7h|@X5>TIDA& bj:ysAK7~ah`sx !!p(FnQFNPfs"RU~/r6`c"G{TQeĵiYI25E:hŕ`K+8Ő +3F9Hqp=J6VYOQR, #Ye!Fә~L>x R2FFu.Ý D-j{!eۙB(Hurv=YNU'h?1ML,`;@+TʕvEaQF'?Xǵu=0{W} 8ALEzo*uk KN;Zn^]omTHi142rNZ>)9''övDnl 9hBWiij7u.lnYn:B8cxŤ'E-+ڽ솴$*>,|ԑNnB{e7£鈢̲.30 ߯[$N(iAD~tۡZT}mFqǽTo;m݂W_lIb|@9fN3sdge,GU)'Ѓ* .3`zߒtUGeŠvRO~BV=I\LP_}::D5q9WF$1j"&Ω2vo(7I.o12opCfj'k#7hg2oƔ?!: j. @`'TM-_O.o:̼qGyNOph9HU挲\QzBcB'TZ 6Jп>^W΀ pbXS3'f h򖟉7ѨN*\N&`^4x!vzXt*'+աɥa>eOoabg B5 I,"$wS,Q7&U[ +'TP3->,gEjt,haTw2GY\3㓈x_ۦ^O%p) [m v%ڒ>iH<)( ؏4$> N/kbY!NR@G\7|RŃ)9ca5j*8NOR^dA?1UxlsjăFn]DLFq. )w^UE^\ldrJqU[*1ǘ )q-,xC me '~P r:QkH5^NC>OV%_H}_X,3U%bъsG_)`Z 6qh,`J"i5ў-LjZ9F#uU3톖O,Rt ϣC{%Tj2'72o`ۓ^&/\/F."~=Jh7Wͪiań=9{Jﰘ)(&Ճ.]n`q/nx oAgoq CydE\$aA2O#ĜGVloJJvRE"V !N!S >3\LC73 4Ahcoi #0n&N.݊A ;h!@B5:1;Ff;6pD[OOa@+M4ti@_ri fwxP{DE1]`Q̊`KzȮgʇS>ű8i W!Kl}.#i}IRm/*g*Lۀ 'Ay%oQ"W\\>3Z8R !l撻6(x[5*Ex?u#.Pt?0ڲ$n^7*q."Xq=]In[ӄ쾀Θ==U/lEhnڋmq$cM~Neơ+sj׳=脌m gSWtV7|`nŪbem\(#o.%FjO: 2^U`!nk_ht[Tvm"[XBWC;#/v|xb%!W~G߽ruz~D3T"ز,O{dHGDfS,`"cs HHI3<sdƽ&hP! &Yt +-OIͧMYZ[-0u؎>WL^X7TɦK]^-ak(wfB!qkQcf7g⇘;MHKmD@֞kjdGuı#:&CW+fMk8ڌ.=!K(u z:gH|_|~8_O D//Fx4OZVke͎^S+?ģ&$Ú0 0re岆9fܿ>V&jHMƺ_qn?^4@8P=G0::=~;$ wBHd}Zٟuqf@]%UG@s8(v7nK1D#M nDEzsg,<ӣeK^xh!U=rch3*UHXfy!KGpGn +Z%D2{W1-Gs?nѲΝsEPH]9hL?[$)b/)I.g b^PQ2PzrXێkD}%<) 6Y?wUt=TVT+F3(e _ oDvI +EW5S F \GgmxxurtIZ_mT!ei9 Gc}uLkR2,hL10OJH>qدeݾ۲]=Xqu&g|D#5"x m r!X%D(Hi;Ժ98lr:昐-&D[<݋r>#.c*.YY\ "Sy6$k8G*x g&+ӜwOL @Csy^MC=-m8[׌n-;!]MץofN)B-s"ZZr" \rs@~fj+9P:!|f&Dr}̍w8vLɖ.l~URmyp3Z ղu[nn2hRgoc:n9HM{1 ׹b'_BڷT +ŠRr\̿:]?uQ.BGȦ^:3v» dwt@i",ى M1 Bq@Ή0QVXMٿe zc%z\huGNk5OFz+ʘyp[Qaf&}2Hz„tUh!'2982,/-[۩T7ӬZU' xܤzvnb&ϫD4])Fc]/}Ėmgiטj@{潠(uPøF*=.JFo$a.ŝtm&8qUF6M*EZ0ŝ3^ۖ&[Sܙj"LCL=N6]܇' +YM7,D-bRg׭bSj%'^ɡ8Pt2j "\H(? :pX,hn+/;YmFkY'T{hNB]D?6z +IOȰKm*ebVoq]Sj<0I|A4tsDp1s-B4DŽ}*, ϛI~#6N\2g("0o놈&QZYT%hpa-0'"a}rA g:ƻO֧B* os)m[v`)zC&G(o!?`*h}S$:F +k: T)A9'zX@vwef2yygO=ʣ2lŻiGz&ŸڧfZ[%7]s$u3?ޤenJ Lt-UdTuŵ5M | WNi/[M=*>Z-әқ+˧,\v9Z779D(-rg` ˅~ʦNbv9Eˊސv\8Ύs"sLOm<\nGw\[[?rQ2 ޏn İN 3ɍӱL]_(%~+rfX]%ù֐tjv +ˋ10!!xLseIӗR][ƪkBGG$я#g\3TEjϴout15oE?|q}/ okzg]{%&n][2 WY^R,f!R_b̠>PK>kReAM=-mE R<=(DSD ɁnlF@ wh8i2` mJw}!0ir̰HMYٓW+b]h"ԊHE@]&4H%nC-*B(u0νfnaT俏y;k.Wnu;=(P!{rqPQi LmNx&S3({MbKfJ./Fk%T^b{Od FA;CXmneȡyR.KG~F.񅸶٦+ AP4r~V4Nt5kwJ W4ȋ|āT'2^d/6K7O*3E;ih iobMgGX쪞}Xo*3Æ텲Ԑwl2\C5 BƱr+Nϴ  +wNsCؙd5|q*+1FB:k~;n7Q# 9%~㕐 d#MNRKBC_eEF<?R$$DBѐ_ZISr.nA-DuDY$wFx0gbLd[ӂ WxV<z C IsgI>.--9M* +@j +qNqh/DHo4L x/.f; ݩ;]]_ѭpEJp e(eO^eA*",\` !mm*X%]l,H '{sqھ?)^6 I]Y+\%:x'VgO~{6F |axuC]_v>pm0@ 1d:74!3]Ӊ4CZb [Lj\!c-@z<߫(?eN($V8WC|θ\9w@ r m˫Bjǔ0b՝XB O%OA\Qx8qPT}X4`5iɁ^c" /JEe&N +FmfTD_[@c]ګ[@vW2c@3ec/'Z.C@ӎ]goW C4bB ڜ/ DM_O*f<%S›wLNC$B{+? +Kn՗񆑪Gg59we-Bt=&;Ћp/RvGR `> uD+As4^p6iԳXN1AZ"A_!pt{Eːoz:Oja۵LR"E_u8oPL ^ ?D@0CwVBh1"),޲%G2CCQ 9SA= |XH.)M+KVꖙcKb2E07z4"x +-<%8T.7T&p_e [6sFRT +E + ᨧ#; lMw\]%"m'k0n9iKO?UՊL. o F*+^_t*ٛb*굝MN$y:_qB{6f ADP̈g[RL$6 /k#Dzn`Yp%@afHnU.oVʊF\@Ə +Q6̭4<4h6ׇJic!J-aӓs0 iNF1|X<K|yņYвV׼\\w3ơYzp%Yz(^LW?e}fr9pM=]VQTBV YhV:ԃEB,KBD;DŽe?sB3fنy{L {B3Cd_N}fdA[u~Vwya6{ ل 6TpKGx#E+~:u@9GP bĐ6ifWLwّ#FtYd V=ˋ(X "_>ǎU|I* PB#AXj>0+'B%4@N6( +0CbTqhyIC^ΣKޮ[z15,b+Ѣ*$ !q@ δ%Q(Vk_Qs8Xf/M#>Mk+A7ϴS5ܼe {V:)4-Ίj0jYE0]+]  B^vK⅂_5}+}1+cf}us0-Xv5D;=Uۢ#^o fz6Fئ'ǩ G*F *Ps`gٮX;RPiv8/71}s{2bRˣ s.ɜ& tWk.'FE4 ;d*p&1y_CXea[4sRfSur/ I.Y f7 QVBKge:tumlAͿ|90zpGk^ROd5P9%gC3e{QVV:!vQf W»=DLf0ẽl=QjrI2|/z9b6o!ܒI̤܄B1k;P]?_ Z|KY1Gh̹ +5haGf5 + ++9;.X/?|z-?u2׍`ϭG~< 6Zn1=1HZ~GòH":nl'mDt̶LEG=րף^תW0gT&zq)ۮwLrIa=*=9~.@ub(䜛5J +MC_}]+eH0zB}ypȏL)D2,Xlrn}wȾΣzw̜Ysj@2X^E-:*xڝ@/ZpN٤[5O m`63g8xpOw}ɍ$_EE4 ):?QMY9Y xk'{)p!Qnhe1z&g2r{S!g.A'2=s|;H#~:-,ljO(tj4$o_hwGIeAJ0TcM|Kˑ WA3ٷ>gŲ4N\gvTQ iRǁƐ\ĮL+|EׯU*ϦóvHXWh`F lfb‘T +C L0 Α(2 G4a ΍P> +`˅zHx輈6D1`3 d'FJj)cDI <B˃GBX8 + + + FG +b6 >&.  ЅNC& Q8 C> +\0)$<@!H􄼠kD*F¦C93Jx( 8)z& >. H6"v%qxlDЄsP.RNqP: CqPBI*Z>D@@0^|CBu(QNXED(@IDIDID h*sJ ltD +R6NCB<<# yAY'#/eᑱX0!A<45@MI$DRRABbl`ipt|&p#ВQNQ@HpC 00HL\(a4jqP '." 8^8"!IB$@3"8+ +BsRX }Oes F(f\4#,gCd HdmBB4T#"$D#46 6ʼnXUrb:&hL, H/27Jba`BH%6 3,@1b=`F3 + Y @ZX`!a$&gz `@$&;`Ё'3<3 6 + rD + CL,Qy``Hd Z0AAЉ@1dž + T:Xy9 JfF8  'I-J T 2Q5R<, J8mt6R.SY0cdD+ !1("!"![+mjZϼO|)e7jT3]|zgɾLyOɽZMʨ Uf7/?UvjESs]v7M_oo5e\\hn:D\hXQKb!BG#JR ) yYЁ"4YDI$$ F( XNd%G +"X"Dadildz,ƂT g388 |L 0D(kDaѱ ݂XgcBFb;FI+"Á $(" §#ht&Q*; +$nAGI$%uq 3A("LZ!q`*1 Cb!t;2,1 c%XEiQ Є6"pbf >M !E Jzh lcaIGCbbQs.BzPB. d((6RXaC!($|2TD +4h9ƀ K"I%m(BbfiB"d*2b"Ylᰭ=5MO5n6n͵Ⱦx _78wMo/M_̅i7%u'o:*nzo{>ut]{/QLnY߭ڝ=_'D6g[n|l}~|]qĩk_6[_wf:u2->;b< uyڹ-cE}oϖ=VoQVS궛Wn7=d_ZjcgXϔ]-:-WK)s=tKeS3~7V'Ldt_[M߆h}zEv\5[}:Vu]hl+?f훈ʮw-YK)mUOyߪٽξߟ_?Ok~Zg6i~kS)דK)]f}55GRL<]x ϧ;ncm_z\J8 'tKd&<[Gecs>bc/TR&cWӕՔ疦ƽ^}WC[Oku_߼ݯͶ}Evg\ڦʫ_meoeRʤoںط>DLմwo9{I)k޻Й)m~Bդ|ϸR5Mm۪n>W;TvՄ q1ח/l;5RG(JRY9y K̤2rQL*+'/*2'JNbV'#TUF2oOǫ}{1яrVSk#ޭ&Ӕ(ִ{Ysj.]跨z؋Y_}}.k}>S̝ qYضywtaOh܏kq^P>goKuvne~+]{}n5-fOjjlk'uj϶/woho[Edݻlלwij5w#/՟2Vgr%r5g^0E>VS''>e6ʽg|w_>=O5L74մ{yOoY%gM\]n߾ɸL<}mf}ȏx>)&^nsv[M>}-w}1o/dtCUp;_r+Ϫy۹'<ϥ_kjLױ5}+ROzʙhk[~4e5?co^?-icR|bZED륞j=Mhhi q|=s9bwݻn1{z/Qp7/4Lmdӵj<>]jh}[^>h712Do[۱5l]ֹޣSlZR7Kc5Եzuc5 +2ea0c EclMX.>4>$( $" a@$pHq(Kb$hHf 6]]Q+өˆẀ̨{,xoiP_0]dCyaK`WtXE~+^cVZuJ_x~S﨧"Lnxݴ)6tQT>C$EQ2i{v"\7h@GSRg?Y6O z EuMqǣ]g@'VFZJemjt#ls_Q.CLdBDC|]7чˣӄ@Ɂ#]6$\v| (~Vhߘn}hYTz-ږlo65E:qRN=VvW1ß3I !%a[!G\F:z:@ p82@iFNHuwp̈qdD1w$16q `z*M蚗ͶK FFZLJoڧn/zQl]V;]ׄVPÏR '}ԉVK12S-Hya2bL0Z>g +1o=} s, +<}/C?q-0:S_tR::WANo 24<8+Jץ+ / +q3љDZ#mE;h++הw}X?.>Vj(X[Zy5z؏ܬYJ*cQfhdqiXd,tIKЮ/L~?Ex1u( P51V) ]1x.#ߺ;ɽI%lv9=㔬>W_2.s|l"4wf4\.bAm$ $8:a8wf +u>Ь 4f{01*Q#ZA} 0ՅUk-z$c3>k_<vi="AjXڪMhFݖǏa1v!H?<[0($$ƛR+=;{Zm5"0Y Cr.9uAwAAcG ;B7E+k_F._c.q>nbvoMo8R88THetbC!ydznSMZ1O'֦0.zX7|kYWUz:WB?T3qLYg;/tcdfwTKZ_O[lqUpUK9th18sEs`x *}x7բ~'0bޫoE;{f5.,qxcnܢcЬzBe7/8ꤕb++wc厙8kY%q~mB+Нe ;Q꒰fR:E lڥ3OiJfEJF_4M;u^:֪CE-BEvO\A湣x~w=^ܦm9oVLjUTхQ(V%-It',8D7 _Kq`h 63&lAn* +:)2;]ƺKn]w +F. +jUH{Ҟy"`_}NJR/4Q(F;3DA0SC<9pk0v`ZEq⼙_j2gjb7_h6"[i+E*LF!Uζ&U Zx`| rs39h(ՁVYĥ?q~F6;uUgؚ) +E +V6Ը✮z7ᗛ뎶 d2[+"fSeJ˧W-::gj?Y2rqICYK3~#) dd`xipgX.B5U@mSTIOPh.(A + $CeF_kR mA!bÈa#왜"ԮG[/96$vԴ˸'bxjPԌ~-BmTL}ȼ\s*ZP-$`@왨Oc֟#e)P)J+{:y*[?B"T[Frznw]5e{$}_I&ٌ*F2N7n< b_4(b1%:*D0uݸ6(X>d HZ%C9>#x&|8 :qp8AxUĮGZ\ !Y$XĤoSJ F1_w'tC\"Qex˽_yPJT½*E\-`v_%ڤC֗#Mx$&Tewusu(k8)H"0]cZNt=?T, P|;wEиDP#BVυu0 ="s'g>uͻ 67{V։&)*c*%-yp_w11w>(Y(3{Ed'hpwY^xW0Cd"B{ ngC CO0 +\],Cq~o6fC||cקZ6sVnɥyEn՗H i mH'S:#xG'qEPfeab/"=Zy?e_[M;1g36#*veرdWwIX"K7!$RК첋Q9dg(SCMҴ8ĜɊ6uWǾMsR촰暱 r*NqŒJ9DUу0 ϗfǤBgFP><먐L<@1J,zx9_xpNm W^sIJ33$ @"1Y$$pn$kMgs}~MfpG>)" GwvP4XS鿞] o@>S'vZSQGy}|rY=8Q{#^Ȯ\oAڿH:yԖ Rww*5\'*VOvjKn/x/:D|Ub_"9w+h"^s*S[%0(W 7+f[En&rgӰZ[}MZl$ QP۾ym^݀~u?v[_9Z04vh0.T/| gw-iuLC?HޢҪ\dMx&W+$5\´Z+=)Z"*쥚9%a]`i2K^~Liks\w$rxVQh\^YO-ekzWA0ѽm < +nY>M$?\?Y9nQfCskJ\BfN{>"ײt݀"Y6%g5_3ӢgyHǷEzWj~\ip0!Y(p5j˪iNf18 ! |~'2$.rl e7 r8XeqUwHuX8n VbJJ,Sł'"vi~7SDӧyRAdR=&ji[cKgΩBŅcX[v;vIGf`gfc`8'}CڻX4饳'l7Lu@W^Z0e@ĥ2iW`r_\ψK NFg:}I"H-k% +p[͛=TuLM1R۵N{8w\[jXуB{Y(e:qy7 OwJɥLk2QXIቶ[}ew&mh9&CEtC>dr@~U*  +Xf F/8WG_֗+R,7\nO,•GeBT8u a%$Y?#ܱeݥ? 3MB7zni*K"4vVG"t_THHDe-({K+\:+9'ԿDkׯ NiLe_аyR/K9 [#Sz=X ![_TcXM*.h4qǍŦ^钂7~~Fqu}I:vgڦGĝ*sQpi-q͘킵)EL17!b+⦩SbV/J" hX)oP=jǝdUA}CXĔD-2|ײcY/]`nmC XN\ e>_OHQQ35uߙ]/`z9|Ljm1(,=%un/NPFsśb&3=& +|tP2R7ܤ-$s$2un(2 wd^H汁uRW>'};OHK-KUuo8ȽP߉6h4386r}= y>QT\ +0*QR,=2͑rA0\ϰAY$7ߠ9b7zߠ{:j4OPN/W& 7FrjqM *ˊDkۺZo m-n%q;-0yv%"MIqaK鄖G%ZjɅh;Dbj_Z!%Y#hytӘҮ?Z[t-ƎȎHSf L HOj-f]b$!qN 6S8 5K\т%""ix5uG)jM] +@E-t WΈoYa©o4H~eŝ`3rq |3Z -/3DA"jj.'+Q| qhyv|cL+kW"-);--fCB&#俁 8l@{r@\*Vrjʗ]Iv V=DLP̪1 -~O9^ca(+wZG`A ٘ btːgT5P_;bz CZ*6-۷ n%%E/q5Z"\Pɽȉ隥*R +_ '#>x-(_3ƜCc6ZE|\ER!` _R!IV,uaܷUK9!roF`z,w!]ٲ|e?*gj&t%UUigsgO^ Aff;.'a;Y0l"/M9i^NgS*DI $#yX5~Ujv +| CAC>9~>P>nu!C]/:9!b$N{ܦeÜ Q* pXd^H +@(:=rU  D*Ao}cA:<*J]ڹj,x ?n;":_X|},soݻTXy&4ю ٷCW/G,t嶩?P2uVB(2ѕC!-Ƀ]om FmMή\+ߚbe~5ѕy2IrM)_Wr,[x֦d-!G7+M\| XW6{pMz=N+'d^,t"FGC4\N,Sm>ӕ /Oخ|IW^*AWjەҕ:fOfŴ\KS3e +)1_rtlv1񡒡BWePĮQ _d#F=D׳}W#V4qǿ;r%e>7/m^lr,˘[ҕE]mW+RM+GoR=r^3ll+[2ʯ@]9!c6)IDWͶو&l9hV+XPVTF0 h|ѝB)Čhit&Xz-8k<3%(^e: ()_%ug]K$-s3Fn뿶uȕ`8e { cG̮\7qd ތ}7xJ[W ,wnS7 ti9]T +U?upf1KJ!/s&8^?X8"zg+ +3ö< ZE98{B\x嬃["*0Ch/.pj$c}#y>83U: u}Hߐe@?Br^j

h0 c7 "Yv|c?#hELx]h%3{Ph%g5B3dW׃Sf͠ &4w3¯J̑;iqc[ھ/4M03K24qy3n.4#rVgG +u6cn 0y +??3{mT |{Kh4(L+ >4[ ?>e0mIqd4ڎ(MR3 ,p=eX|6/5(BLz̙Ӈ9"ܓc$TѠ,f2+%\Gfi],]M\P}S_@TfAn8ׁڶ&y[%?gˬ,& iG &\l?F){;lxkł] Z_=v\}]F]5Hyǽdb0X ˾eˎajIUW"^͞LDz2}'ְ6ixQ̩&] 9r^WtEdA  +Wl'Pr){T1]GĂYVSN `\{VV,C*޾85Ty\$YSd3KKeDxhM6D;P#~E|m{7) ̈́4ͺ\ٝQ.fW+b3y@&d)^EM$)HoK>f%Rcʫ:;ĕ4oY8*>9Ms \ׅ)R@'%2ޓtq;8BhM +yaxkx`=(/EB&MQE8ED. a(a0/8:ŤeF|+f, 82ʼnAWVFZq2NEyfn?ْZ-KfAp6l[t+ye߅hs7ĢDY;6l\mHi$]bӜV1ܧ>X:TPvFlk25kR EߒoY%G?l[c[T$$n0QQۑмLD\ S6q67? +Pc +8dz]T?3a$onԂ+*{)-Ƒ'%_a%NL$n.4yjUFZ3cAw +'r9|"*@YFGU-qJ^DYΩIj?vβk {(^ChmMYj8* jCִ۔Gn%.̚a+I{4e6B?#N?ƺly%no\ط `%}< ir?j|d%-[ : +HxԆ6n%(vIÖVOi,Dx/d<ʘnU\[j٪Ҷ.P}-Y<Xy4d}HCRJOLL%4 zwLb%i?* +M}k )O8Y8? `qO'IOgT1-;0ܻ$&.6bzߊ<~;f= +!WfE{>έ-aE\@JE_qFQSK @ÆmE)4$&V7KEm=LV+6Um}QނC V)M(+bKLӢl~pf=+#Y;{1G&ъC%2n\mг{yrR܊=ǫöL>~jwn 9F9;IPR*T_1oLYױWrAhm慳cSU>\>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX$6Ra!<<'!!!*'!!rrmPX()~> endstream endobj 22 0 obj <> endobj 30 0 obj [/View/Design] endobj 31 0 obj <>>> endobj 26 0 obj <> endobj 25 0 obj [/ICCBased 32 0 R] endobj 32 0 obj <>stream +HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  + 2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 +V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= +x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- +ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 +N')].uJr + wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 +n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km endstream endobj 23 0 obj [22 0 R] endobj 33 0 obj <> endobj xref +0 34 +0000000004 65535 f +0000000016 00000 n +0000000147 00000 n +0000044956 00000 n +0000000000 00000 f +0000045007 00000 n +0000000000 00000 f +0000000000 00000 f +0000046575 00000 n +0000046647 00000 n +0000046842 00000 n +0000048369 00000 n +0000113957 00000 n +0000179545 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000229550 00000 n +0000232542 00000 n +0000045406 00000 n +0000229850 00000 n +0000229737 00000 n +0000046259 00000 n +0000228976 00000 n +0000229024 00000 n +0000229621 00000 n +0000229652 00000 n +0000229885 00000 n +0000232567 00000 n +trailer <<8A56A6F188E7499DAAF7B91AC94B751D>]>> startxref 232778 %%EOF \ No newline at end of file diff --git a/static/images/.DS_Store b/static/images/.DS_Store index 962d4c375c3e105d646e6d95fc7ef5934f62e21c..3d7a2676d8f0ff1858a65f5194ffead143d58d54 100644 GIT binary patch delta 144 zcmZqKz}T{Zae_Z%;>Lj8`izp384X1?D;V^1sBrY_6kVZfG$1zm)N0 vM{7w&hRxB|{w$mC*iYvMvP--bm?zi!Do)n(jGO#R*KM-8ee>o!{u&$rGNmb^ delta 94 zcmZqKz}T{Zae_Z%!p4Bz`iv5j84X1?D;V^1Oct^d-TckMlxgxSDUZp;){=~jlMAdx wCYLJmZ{A?7!!mh-jm73i_7k}o88?@BD=<&C_w=3oOV@33qZG0Q>178UO$Q diff --git a/templates/.DS_Store b/templates/.DS_Store index 8ee2ea314c35ebcc2d0fb29687d9a94862341c0c..b8d2b48dfcdda21d164ce3891b471e8174848497 100644 GIT binary patch delta 122 zcmZoEXepTB&uG6fV7EM@+GZYw-)z#H3>gfG44DksKr)9Ri6Nh%B&9exCn-NaXYxfg iah|f^qP(2^ymSTz2FA@S>W4TMvm3CJq-!#Z`hEbHvLi?U delta 35 rcmZoEXepTB&uF_bV7EM@%4Qyg-)x&b)c12tY;au6Zood-L;WuR?Ai?h diff --git a/templates/haikalbot/.DS_Store b/templates/haikalbot/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + + +

+
+
+
{% trans "HaikalBot" %}
+
+ +
+
+
+ + +
+
+ + +{% endblock %} \ No newline at end of file diff --git a/templates/haikalbot/chatbot.html b/templates/haikalbot/chatbot.html index e6f2b228..05de8859 100644 --- a/templates/haikalbot/chatbot.html +++ b/templates/haikalbot/chatbot.html @@ -500,7 +500,6 @@ $(document).ready(function() { return isArabic ? (obj[arabicKey] || obj[englishKey] || '') : (obj[englishKey] || obj[arabicKey] || ''); } - // Copy message functionality $(document).on('click', '.copy-btn', function() { const text = $(this).closest('.d-flex').find('.chat-message').text().trim(); navigator.clipboard.writeText(text).then(() => { @@ -516,7 +515,6 @@ $(document).ready(function() { }, 1500); } - // Initialize scrollToBottom(); }); diff --git a/templates/items/service/service_list.html b/templates/items/service/service_list.html index edbe50d9..5f2b152b 100644 --- a/templates/items/service/service_list.html +++ b/templates/items/service/service_list.html @@ -34,17 +34,13 @@ {{ service.pk }} - {{ service.get_local_name }} + {{ service.get_local_name|default:service.name }} - {{ service.uom }} + {{ service.get_uom_display }} - {% if service.taxable %} - Yes - {% else %} - No - {% endif %} + {{ service.taxable|yesno }} {{ service.item.co }}