feat: residues accounting

This commit is contained in:
2025-01-14 21:35:39 +04:00
parent 1f26f94d96
commit d609c10edb
15 changed files with 776 additions and 10 deletions

192
services/residues.py Normal file
View File

@@ -0,0 +1,192 @@
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)