diff --git a/app/models/mysql_const.py b/app/models/mysql_const.py new file mode 100644 index 0000000..b7cbe0b --- /dev/null +++ b/app/models/mysql_const.py @@ -0,0 +1,27 @@ +""" +مدل ORM برای جدول const در MySQL. +""" +from sqlalchemy import Column, Integer, String, Text +from core.service.mysql_orm import Base + + +class Const(Base): + """مدل جدول const""" + __tablename__ = "const" + + id = Column(Integer, primary_key=True, index=True) + key = Column(String(255), nullable=False, index=True) + title_fa = Column(String(255), nullable=True) + value = Column(Text, nullable=True) + + def __repr__(self): + return f"" + + def to_dict(self): + """تبدیل به dict برای JSON response""" + return { + "id": self.id, + "key": self.key, + "title_fa": self.title_fa, + "value": self.value, + } diff --git a/app/repositories/mysql_const_repo.py b/app/repositories/mysql_const_repo.py new file mode 100644 index 0000000..e8be1de --- /dev/null +++ b/app/repositories/mysql_const_repo.py @@ -0,0 +1,34 @@ +""" +Repository برای جدول const در MySQL - برای استفاده در خروجی اکسل. +""" +from typing import List, Dict, Optional +from sqlalchemy import select +from core.service.mysql_orm import get_mysql_session +from app.models.mysql_const import Const + + +class MySQLConstRepository: + """Repository برای خواندن دیتا از جدول const""" + + async def get_by_keys(self, keys: List[str]) -> Dict[int, str]: + """ + گرفتن const ها با لیست key ها و برگرداندن dict به صورت {id: title_fa} + مثال: get_by_keys(['nature', 'type']) -> {2: 'طبیعت 2', 9: 'نوع 9'} + """ + async with get_mysql_session() as session: + result = await session.execute( + select(Const).where(Const.key.in_(keys)) + ) + consts = result.scalars().all() + return {c.id: c.title_fa or c.value or str(c.id) for c in consts} + + async def get_by_key(self, key: str) -> Dict[int, str]: + """گرفتن const ها با یک key و برگرداندن dict به صورت {id: title_fa}""" + return await self.get_by_keys([key]) + + async def get_all_consts_dict(self) -> Dict[int, str]: + """گرفتن همه const ها به صورت {id: title_fa} - برای کش کردن""" + async with get_mysql_session() as session: + result = await session.execute(select(Const)) + consts = result.scalars().all() + return {c.id: c.title_fa or c.value or str(c.id) for c in consts} diff --git a/auth/utils/mongo.py b/auth/utils/mongo.py index 334b168..9b80072 100644 --- a/auth/utils/mongo.py +++ b/auth/utils/mongo.py @@ -5,8 +5,8 @@ from core.service.mongo_db import use_collection def get_auth_db(): - return conf.server.is_prod and conf.database.get('mongo').production.db or conf.database.get( - 'mongo').dev.db + return conf.server.is_prod and conf.database.get('mongo_auth').production.db or conf.database.get( + 'mongo_auth').dev.db def use_auth_collection(collection: str) -> Collection: diff --git a/core/utils/te/engines/Mongo.py b/core/utils/te/engines/Mongo.py index 9d4fd3e..354de9e 100644 --- a/core/utils/te/engines/Mongo.py +++ b/core/utils/te/engines/Mongo.py @@ -17,6 +17,7 @@ from celery import shared_task from .AbstractEngine import AbstractEngineClass from ..utils.Models import FilterItem, FilterScope, OutPutType from ..utils.excel_value_maps import EXCEL_VALUE_MAP_REGISTRY +from app.repositories.mysql_const_repo import MySQLConstRepository # def zero_time_data(date, is_max=False): @@ -39,6 +40,7 @@ class MongoEngine(AbstractEngineClass): rows = [] stack = [] filters = {} + _const_cache: dict = None # کش const ها از MySQL async def exe(self): _f = self.filters = self.set_filters() @@ -54,7 +56,7 @@ class MongoEngine(AbstractEngineClass): if self.input.e and self.input.e.ot: res = await tmp.to_list(None) - self.input.e.d = self.generate_output(res) + self.input.e.d = await self.generate_output(res) return {'query': _f, 'sort': self.set_sort(), 'path': self.input.e.d} self.rows = tmp.limit(self.input.get_limit()).skip(self.input.skip()) @@ -257,11 +259,11 @@ class MongoEngine(AbstractEngineClass): def set_joins(self): pass - def generate_output(self, _f): + async def generate_output(self, _f): print(self.input.e.ot) if self.input.e.ot == OutPutType.EXEL: file_name = str(uuid.uuid4().hex) + '.xlsx' - return self.output_generator(_f, file_name=file_name, cols=self.input.e.c) + return await self.output_generator(_f, file_name=file_name, cols=self.input.e.c) elif self.input.e.ot == OutPutType.JSON: pass elif self.input.e.ot == OutPutType.PDF: @@ -275,7 +277,7 @@ class MongoEngine(AbstractEngineClass): os.makedirs(temp_dir, exist_ok=True) return temp_dir - def output_generator(self,query, file_name, cols): + async def output_generator(self, query, file_name, cols): datas = query file_dir = os.path.join(self._excel_temp_dir(), file_name) head_cols = list(str(string.ascii_uppercase)) @@ -293,10 +295,15 @@ class MongoEngine(AbstractEngineClass): # ساختن value_maps از vk (نام مپ) با استفاده از رجیستری value_maps = self._build_value_maps() + + # بارگذاری const ها برای فیلدهای خاص + const_maps = await self._load_const_maps(headers) + for row_index, item in enumerate(datas, start=1): for col_index, col in enumerate(headers): val = self.get_nested_value(item, col) val = self._apply_value_map(col, val, value_maps) + val = await self._apply_const_map(col, val, const_maps) worksheet.write(row_index, col_index, val) workbook.close() return file_name @@ -328,6 +335,58 @@ class MongoEngine(AbstractEngineClass): return mapping[str(key)] return val + async def _load_const_maps(self, headers: List[str]) -> dict: + """ + شناسایی فیلدهای خاص (مثلاً nature_id, type_id, budget_id) و بارگذاری const ها از MySQL. + Returns: dict به صورت {col_key: {id: title_fa}} مثلاً {"design.nature_id": {2: "طبیعت 2", ...}} + """ + # لیست فیلدهای خاص که باید از const خوانده شوند + const_field_names = ['nature_id', 'type_id', 'budget_id'] # می‌تونی اضافه کنی + + const_maps = {} + const_keys_to_load = set() + const_column_map = {} # {col_key: const_key} مثلاً {"design.nature_id": "nature"} + + # شناسایی ستون‌هایی که باید const بخونن + for col_key in headers: + col_name = col_key.split('.')[-1] # آخرین بخش (nature_id) + if col_name in const_field_names: + const_key = col_name.replace('_id', '') # nature_id -> nature + const_keys_to_load.add(const_key) + const_column_map[col_key] = const_key + + # بارگذاری const ها از MySQL + if const_keys_to_load: + repo = MySQLConstRepository() + for col_key, const_key in const_column_map.items(): + consts_for_key = await repo.get_by_key(const_key) + if consts_for_key: + const_maps[col_key] = consts_for_key + + return const_maps + + async def _apply_const_map(self, col_key: str, val, const_maps: dict): + """اگر این ستون در const_maps باشد، id را به title_fa تبدیل می‌کند.""" + if not const_maps or col_key not in const_maps: + return val + + mapping = const_maps[col_key] + if not isinstance(mapping, dict): + return val + + if val is None: + return mapping.get(None, val) + + # تبدیل val به int برای جستجو در mapping + key = val if isinstance(val, (int, float)) else (int(val) if isinstance(val, str) and str(val).strip().isdigit() else val) + + if key in mapping: + return mapping[key] + if isinstance(key, int) and str(key) in mapping: + return mapping[str(key)] + + return val + def get_nested_value(self, data, path, default='-'): """ دریافت مقدار از دیکشنری تو در تو با مسیر dot notation