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

View File

@@ -1,10 +1,11 @@
import datetime
from typing import Union
from fastapi import HTTPException
from sqlalchemy import select, update
from sqlalchemy.orm import joinedload
from sqlalchemy.orm import joinedload, selectinload, noload
from models import Client, ClientDetails, User
from models import Client, ClientDetails, User, ResidualPallet, ResidualBox, ResidualProduct, Product
from schemas.client import *
from services.base import BaseService
@@ -15,9 +16,35 @@ class ClientService(BaseService):
client = await self.session.scalar(select(Client).where(Client.name == name))
return client
async def get_by_id(self, client_id: int) -> Union[Client, None]:
async def _get_by_id(self, client_id: int) -> Union[Client, None]:
return await self.session.get(Client, client_id)
async def get_by_id(self, client_id: int) -> ClientGetResponse:
stmt = (
select(Client)
.options(
selectinload(Client.pallets)
.selectinload(ResidualPallet.residual_products)
.selectinload(ResidualProduct.product)
.noload(Product.barcodes),
selectinload(Client.pallets)
.selectinload(ResidualPallet.boxes)
.selectinload(ResidualBox.residual_products)
.selectinload(ResidualProduct.product)
.noload(Product.barcodes),
selectinload(Client.boxes)
.selectinload(ResidualBox.residual_products)
.selectinload(ResidualProduct.product)
.noload(Product.barcodes),
)
.where(Client.id == client_id)
)
client = (await self.session.execute(stmt)).one_or_none()
client = client[0] if client else None
if not client:
raise HTTPException(status_code=404, detail="Клиент не найден")
return ClientGetResponse(client=client)
async def get_details_by_client_id(self, client_id: int) -> Union[ClientDetails, None]:
details = await self.session.scalar(select(ClientDetails).where(ClientDetails.client_id == client_id))
return details
@@ -101,7 +128,7 @@ class ClientService(BaseService):
async def update(self, request: ClientUpdateRequest, user: User) -> ClientUpdateResponse:
try:
client = await self.get_by_id(request.data.id)
client = await self._get_by_id(request.data.id)
if not client:
return ClientUpdateResponse(ok=False, message='Клиент не найден')
request_dict = request.data.dict()
@@ -124,7 +151,7 @@ class ClientService(BaseService):
async def delete(self, request: ClientDeleteRequest) -> ClientDeleteResponse:
try:
client = await self.get_by_id(request.client_id)
client = await self._get_by_id(request.client_id)
if not client:
return ClientDeleteResponse(ok=False, message='Клиент не найден')
await self.session.delete(client)

View File

@@ -105,7 +105,7 @@ class ProductService(BaseService):
is_pagination_valid = is_valid_pagination(pagination)
total_pages = 0
total_items = 0
stmt: Query = (
stmt = (
select(
Product
)

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)