from abc import ABC, abstractmethod from typing import Tuple from sqlalchemy import select, func, update from sqlalchemy.ext.asyncio import AsyncSession from common.database.db_session_manager import DB_SESSION_MNG from common.database.model.models import supplier_items, items, suppliers from common.enums import ErrorType from common.logger import LOG from common.utils.gtime import GTime # 협력사-상품 매핑 CRUD. 스코프는 상위(협력사/상품)가 company_id 로 이미 걸린다. class ISupplierItemCRUD(ABC): @abstractmethod async def list_by_supplier(self, cdb: AsyncSession, supplier_id) -> Tuple[ErrorType, list]: pass @abstractmethod async def list_by_item(self, cdb: AsyncSession, item_id) -> Tuple[ErrorType, list]: pass @abstractmethod async def get_by_id(self, cdb: AsyncSession, supplier_item_id) -> Tuple[ErrorType, supplier_items]: pass @abstractmethod async def existing_item_ids(self, cdb: AsyncSession, supplier_id, item_ids: list) -> Tuple[ErrorType, list]: pass @abstractmethod async def find_items_by_names(self, cdb: AsyncSession, company_id, names: list) -> Tuple[ErrorType, list]: pass @abstractmethod async def add_many(self, cdb: AsyncSession, mappings: list) -> ErrorType: pass @abstractmethod async def update_type(self, cdb: AsyncSession, supplier_item_id, supply_type: int) -> ErrorType: pass @abstractmethod async def soft_delete(self, cdb: AsyncSession, supplier_item_id) -> ErrorType: pass class SupplierItemCRUD(ISupplierItemCRUD): async def list_by_supplier(self, cdb: AsyncSession, supplier_id) -> Tuple[ErrorType, list]: # 협력사 상세용: 매핑 + 상품명/코드/카테고리/제조사 조인. # Row(supplier_item_id, item_id, name, code, supply_type, category, manufacturer) try: query = ( select( supplier_items.supplier_item_id, supplier_items.item_id, items.name, items.code, supplier_items.supply_type, items.category, items.manufacturer, ) .join(items, items.item_id == supplier_items.item_id) .where( supplier_items.supplier_id == supplier_id, supplier_items.deleted == False, # noqa: E712 items.deleted == False, # noqa: E712 ) .order_by(supplier_items.created_at.desc()) ) err_type, rows = await DB_SESSION_MNG.execute(cdb, query) if err_type != ErrorType.SUCCESS: return err_type, [] return ErrorType.SUCCESS, list(rows) except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED, [] async def list_by_item(self, cdb: AsyncSession, item_id) -> Tuple[ErrorType, list]: # 견적생성 모달·상품 상세 공용: 이 상품을 취급하는 협력사별 공급유형. # Row(supplier_id, supply_type, name, supplier_item_id) try: query = ( select( supplier_items.supplier_id, supplier_items.supply_type, suppliers.name, supplier_items.supplier_item_id, ) .join(suppliers, suppliers.supplier_id == supplier_items.supplier_id) .where( supplier_items.item_id == item_id, supplier_items.deleted == False, # noqa: E712 suppliers.deleted == False, # noqa: E712 ) ) err_type, rows = await DB_SESSION_MNG.execute(cdb, query) if err_type != ErrorType.SUCCESS: return err_type, [] return ErrorType.SUCCESS, list(rows) except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED, [] async def get_by_id(self, cdb: AsyncSession, supplier_item_id) -> Tuple[ErrorType, supplier_items]: try: query = select(supplier_items).where( supplier_items.supplier_item_id == supplier_item_id, supplier_items.deleted == False, # noqa: E712 ).limit(1) err_type, row_list = await DB_SESSION_MNG.execute(cdb, query) if err_type != ErrorType.SUCCESS: return err_type, None if len(row_list) != 1: return ErrorType.DB_INVALID_KEY, None return ErrorType.SUCCESS, row_list[0] except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED, None async def existing_item_ids(self, cdb: AsyncSession, supplier_id, item_ids: list) -> Tuple[ErrorType, list]: # 이 협력사에 이미 매핑된 item_id 들(중복 등록 스킵용). try: if not item_ids: return ErrorType.SUCCESS, [] query = select(supplier_items.item_id).where( supplier_items.supplier_id == supplier_id, supplier_items.item_id.in_(item_ids), supplier_items.deleted == False, # noqa: E712 ) err_type, rows = await DB_SESSION_MNG.execute(cdb, query) if err_type != ErrorType.SUCCESS: return err_type, [] return ErrorType.SUCCESS, [r for r in rows if r is not None] except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED, [] async def find_items_by_names(self, cdb: AsyncSession, company_id, names: list) -> Tuple[ErrorType, list]: # 상품명(정확 일치) → 상품. Row(item_id, name). 이름 중복 상품이 있으면 여럿 반환될 수 있다(서비스에서 첫 매칭 사용). try: if not names: return ErrorType.SUCCESS, [] query = select(items.item_id, items.name).where( items.company_id == company_id, items.name.in_(names), items.deleted == False, # noqa: E712 ) err_type, rows = await DB_SESSION_MNG.execute(cdb, query) if err_type != ErrorType.SUCCESS: return err_type, [] return ErrorType.SUCCESS, list(rows) except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED, [] async def add_many(self, cdb: AsyncSession, mappings: list) -> ErrorType: try: if not mappings: return ErrorType.SUCCESS return await DB_SESSION_MNG.insert(cdb, mappings) except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED async def update_type(self, cdb: AsyncSession, supplier_item_id, supply_type: int) -> ErrorType: try: query = ( update(supplier_items) .where(supplier_items.supplier_item_id == supplier_item_id) .values(supply_type=supply_type, updated_at=GTime.UTC()) ) return await DB_SESSION_MNG.add(cdb, query) except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED async def soft_delete(self, cdb: AsyncSession, supplier_item_id) -> ErrorType: try: query = ( update(supplier_items) .where(supplier_items.supplier_item_id == supplier_item_id) .values(deleted=True, updated_at=GTime.UTC()) ) return await DB_SESSION_MNG.add(cdb, query) except Exception as ex: LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED