from fastapi import HTTPException from sqlalchemy import select from sqlalchemy.orm import selectinload, joinedload from starlette import status from models import ResidualPallet, ResidualBox, ResidualProduct, Client, Product from schemas.residues import * from services.base import BaseService class ResiduesService(BaseService): async def _get_pallet_by_id(self, pallet_id: int) -> Optional[ResidualPallet]: stmt = ( select(ResidualPallet) .options( selectinload(ResidualPallet.boxes) .selectinload(ResidualBox.residual_products) .selectinload(ResidualProduct.product) .noload(Product.barcodes), joinedload(ResidualPallet.client), selectinload(ResidualPallet.residual_products) .selectinload(ResidualProduct.product) .noload(Product.barcodes), ) .where(ResidualPallet.id == pallet_id) ) pallet = (await self.session.execute(stmt)).one_or_none() return pallet[0] if pallet else None async def get_pallet(self, pallet_id: int) -> GetResidualPalletResponse: pallet = await self._get_pallet_by_id(pallet_id) if not pallet: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f'Паллет с ID:{pallet_id} не найден') return GetResidualPalletResponse(pallet=pallet, client_id=pallet.client_id) async def create_pallet(self, request: CreateResidualPalletRequest) -> CreateResidualPalletResponse: client = await self.session.get(Client, request.client_id) if not client: return CreateResidualPalletResponse(ok=False, message=f'Клиент с ID:{request.client_id} не найден') pallet = ResidualPallet(client_id=request.client_id, created_at=datetime.now()) self.session.add(pallet) await self.session.commit() return CreateResidualPalletResponse(ok=True, message='Паллет успешно создан') async def delete_pallet(self, pallet_id: int) -> DeleteResidualPalletResponse: pallet = await self.session.get(ResidualPallet, pallet_id) if not pallet: return DeleteResidualPalletResponse(ok=False, message='Паллет не найден') await self.session.delete(pallet) await self.session.commit() return DeleteResidualPalletResponse(ok=True, message='Паллет успешно удален') async def _get_box_by_id(self, box_id: int) -> Optional[ResidualBox]: stmt = ( select(ResidualBox) .options( selectinload(ResidualBox.pallet) .noload(ResidualPallet.boxes), selectinload(ResidualBox.pallet) .joinedload(ResidualPallet.client), selectinload(ResidualBox.residual_products) .selectinload(ResidualProduct.product) .noload(Product.barcodes), ) .where(ResidualBox.id == box_id) ) box = (await self.session.execute(stmt)).one_or_none() return box[0] if box else None async def get_box(self, box_id: int) -> GetResidualBoxResponse: box = await self._get_box_by_id(box_id) if not box: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f'Короб с ID:{box_id} не найден') if box.client_id: client_id = box.client_id else: pallet = await self._get_pallet_by_id(box.pallet_id) client_id = pallet.client_id return GetResidualBoxResponse(box=box, client_id=client_id) async def create_box(self, request: CreateResidualBoxRequest) -> CreateResidualBoxResponse: if request.client_id: client = await self.session.get(Client, request.client_id) if not client: return CreateResidualBoxResponse(ok=False, message=f'Клиент с ID:{request.client_id} не найден') else: pallet = await self.session.get(ResidualPallet, request.pallet_id) if not pallet: return CreateResidualBoxResponse(ok=False, message=f'Паллет с ID:{request.pallet_id} не найден') box = ResidualBox(created_at=datetime.now(), **request.model_dump()) self.session.add(box) await self.session.commit() return CreateResidualBoxResponse(ok=True, message='Короб успешно создан') async def delete_box(self, box_id: int) -> DeleteResidualBoxResponse: box = await self.session.get(ResidualBox, box_id) if not box: return DeleteResidualBoxResponse(ok=False, message=f'Короб с ID:{box_id} не найден') await self.session.delete(box) await self.session.commit() return DeleteResidualBoxResponse(ok=True, message='Короб успешно удален') async def _create_residual_product( self, obj: ResidualBox | ResidualPallet, request: CreateResidualProductRequest, ) -> tuple[bool, str]: try: existing_residual = next(p for p in obj.residual_products if p.product_id == request.data.product_id) existing_residual.quantity += request.data.quantity self.session.add(existing_residual) except StopIteration: residual_product = ResidualProduct(**request.data.model_dump()) self.session.add(residual_product) await self.session.commit() return True, "Товар успешно добавлен" async def create_residual_product(self, request: CreateResidualProductRequest) -> CreateResidualProductResponse: if request.data.box_id: obj = await self._get_box_by_id(request.data.box_id) if not obj: return CreateResidualProductResponse(ok=False, message=f'Короб с ID:{request.data.box_id} не найден') else: obj = await self.session.get(ResidualPallet, request.data.pallet_id) if not obj: return CreateResidualProductResponse( ok=False, message=f'Паллет с ID:{request.data.pallet_id} не найден', ) ok, message = await self._create_residual_product(obj, request) return CreateResidualProductResponse(ok=ok, message=message) async def update_residual_product( self, request: UpdateResidualProductRequest, residual_product_id: int ) -> UpdateResidualProductResponse: residual_product = await self.session.get(ResidualProduct, residual_product_id) residual_product.product_id = request.data.product_id residual_product.quantity = request.data.quantity await self.session.commit() return UpdateResidualProductResponse(ok=True, message='Запись о товаре на паллете успешно изменена') async def delete_residual_product(self, residual_product_id: int) -> DeleteResidualProductResponse: residual_product = await self.session.get(ResidualProduct, residual_product_id) if not residual_product: return DeleteResidualProductResponse(ok=False, message=f'Запись для данного паллета и товара не найдена') await self.session.delete(residual_product) await self.session.commit() return DeleteResidualProductResponse(ok=True, message='Запись о товаре на паллете успешно удалена') async def load_receipt(self, request: LoadReceiptRequest) -> LoadReceiptResponse: if not await self.session.get(Client, request.client_id): return LoadReceiptResponse(ok=False, message=f'Клиент с ID {request.client_id}') await self._load_receipt_boxes(request.boxes, request.client_id) await self._load_receipt_pallets(request.pallets, request.client_id) await self.session.commit() return LoadReceiptResponse(ok=True, message='Приемка успешно завершена') async def _load_receipt_boxes(self, boxes: list[ReceiptBoxSchema], client_id: int = None, pallet_id: int = None): for receipt_box in boxes: box = ResidualBox(client_id=client_id, pallet_id=pallet_id, created_at=datetime.now()) self.session.add(box) await self.session.flush() await self._load_receipt_products(receipt_box.products, box_id=box.id) async def _load_receipt_pallets(self, pallets: list[ReceiptPalletSchema], client_id: int): for receipt_pallet in pallets: pallet = ResidualPallet(client_id=client_id, created_at=datetime.now()) self.session.add(pallet) await self.session.flush() await self._load_receipt_boxes(receipt_pallet.boxes, pallet.id) await self._load_receipt_products(receipt_pallet.products, pallet_id=pallet.id) async def _load_receipt_products( self, products: list[ReceiptPalletSchema], box_id: int = None, pallet_id: int = None, ): for receipt_product in products: product = ResidualProduct(box_id=box_id, pallet_id=pallet_id, **receipt_product.model_dump()) self.session.add(product)