feat: pallets and boxes for deals

This commit is contained in:
2024-12-09 16:45:10 +04:00
parent d56e292276
commit 863dd226c3
14 changed files with 631 additions and 44 deletions

View File

@@ -12,6 +12,7 @@ ENV.globals['now'] = datetime.now
ENV.globals['encode128'] = encode128 ENV.globals['encode128'] = encode128
ENV.globals['format_number'] = lambda x: '{:,}'.format(x).replace(',', ' ') ENV.globals['format_number'] = lambda x: '{:,}'.format(x).replace(',', ' ')
DOMAIN_NAME = "crm.denco.store"
API_ROOT = "/api" API_ROOT = "/api"
APP_PATH = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(__file__) APP_PATH = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(__file__)

View File

@@ -0,0 +1,65 @@
import os
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import SimpleDocTemplate
from sqlalchemy.ext.asyncio import AsyncSession
from constants import APP_PATH
class BasePdfCardGenerator:
def __init__(self, session: AsyncSession):
self._session = session
assets_folder = os.path.join(APP_PATH, 'assets')
fonts_folder = os.path.join(assets_folder, 'fonts')
font_file_path = os.path.join(fonts_folder, 'DejaVuSans.ttf')
self.page_width = 58 * mm
self.page_height = 40 * mm
pdfmetrics.registerFont(TTFont('DejaVuSans', font_file_path))
self.styles = getSampleStyleSheet()
self._set_small_paragraph_styles()
self._set_medium_paragraph_styles()
def _set_small_paragraph_styles(self):
common_paragraph_style = {
"parent": self.styles['Normal'],
"fontName": "DejaVuSans",
"spaceAfter": 4,
"fontSize": 9,
}
self.small_style = ParagraphStyle(
'Small',
alignment=0,
**common_paragraph_style,
)
self.small_centered_style = ParagraphStyle(
'SmallCentered',
alignment=1,
**common_paragraph_style,
)
def _set_medium_paragraph_styles(self):
self.medium_style = ParagraphStyle(
'Medium',
parent=self.styles['Normal'],
fontName="DejaVuSans",
spaceAfter=6,
fontSize=12,
alignment=0,
)
def _create_doc(self, buffer):
return SimpleDocTemplate(
buffer,
pagesize=(self.page_width, self.page_height),
rightMargin=1,
leftMargin=1,
topMargin=1,
bottomMargin=1
)

View File

@@ -0,0 +1 @@
from .generator import ShippingQRCodeGenerator

View File

@@ -0,0 +1,139 @@
from io import BytesIO
from typing import Optional
from fastapi import HTTPException
from reportlab.lib.units import mm
from reportlab.pdfgen.canvas import Canvas
from reportlab.platypus import Paragraph, SimpleDocTemplate, Frame, PageBreak
from reportlab_qrcode import QRCodeImage
from sqlalchemy import select, func
from sqlalchemy.orm import joinedload, selectinload
from constants import DOMAIN_NAME
from generators.base_pdf_card_generator.base_pdf_card_generator import BasePdfCardGenerator
from models import Deal, ShippingWarehouse, Pallet
from models.shipping import Box
class ShippingQRCodeGenerator(BasePdfCardGenerator):
async def _get_deal_by_id(self, deal_id: int) -> Optional[Deal]:
stmt = (
select(Deal)
.where(Deal.id == deal_id)
.options(
joinedload(Deal.shipping_warehouse),
selectinload(Deal.pallets),
)
)
deal = (await self._session.execute(stmt)).one_or_none()
return deal[0] if deal else None
async def generate_deal(self, deal_id: int) -> BytesIO:
deal = await self._get_deal_by_id(deal_id)
if not deal:
raise HTTPException(status_code=404, detail=f"Сделка с ID {deal_id}a не найдена")
buffer = BytesIO()
doc: SimpleDocTemplate = self._create_doc(buffer)
deal_link = f"{DOMAIN_NAME}/deals/{deal_id}"
shipping_warehouse = await self._session.get(ShippingWarehouse, deal.shipping_warehouse_id)
warehouse_name = shipping_warehouse.name if shipping_warehouse else ""
def on_first_page(canvas: Canvas, doc):
qr = QRCodeImage(deal_link, size=30 * mm)
qr.drawOn(canvas, 0, 30)
deal_id_paragraph = Paragraph(f"ID: {deal_id}", self.small_centered_style)
deal_name_paragraph = Paragraph(str(deal.name), self.small_centered_style)
frame = Frame(x1=28 * mm, y1=5 * mm, width=30 * mm, height=30 * mm)
frame.addFromList([deal_id_paragraph, deal_name_paragraph], canvas)
warehouse_paragraph = Paragraph(warehouse_name, self.small_centered_style)
frame = Frame(x1=0 * mm, y1=-7 * mm, width=58 * mm, height=20 * mm)
frame.addFromList([warehouse_paragraph], canvas)
empty_paragraph = Paragraph("", self.small_centered_style)
elements = [empty_paragraph]
doc.build(elements, on_first_page)
buffer.seek(0)
return buffer
async def generate_pallets(self, deal_id: int):
deal = await self._get_deal_by_id(deal_id)
if not deal:
raise HTTPException(status_code=404, detail=f"Сделка с ID {deal_id}a не найдена")
buffer = BytesIO()
doc: SimpleDocTemplate = self._create_doc(buffer)
shipping_warehouse = await self._session.get(ShippingWarehouse, deal.shipping_warehouse_id)
warehouse_name = shipping_warehouse.name if shipping_warehouse else ""
total_pallets = len(deal.pallets)
elements = []
for pallet_counter in range(total_pallets):
elements.append(Paragraph(f"ID: {deal_id}", self.medium_style))
elements.append(Paragraph(str(deal.name), self.medium_style))
elements.append(Paragraph(f"Паллет {pallet_counter + 1}/{total_pallets}", self.medium_style))
elements.append(Paragraph(warehouse_name, self.medium_style))
elements.append(PageBreak())
doc.build(elements)
buffer.seek(0)
return buffer
async def _get_boxes_on_pallets_count(self, deal_id):
stmt_boxes_on_pallets = (
select(
Pallet.id,
func.count(Box.id).label("box_count"),
)
.join(Box, isouter=True)
.where(Pallet.deal_id == deal_id)
.group_by(Pallet.id)
)
pallets = (await self._session.execute(stmt_boxes_on_pallets)).all()
return pallets
async def generate_boxes(self, deal_id: int) -> BytesIO:
deal = await self._get_deal_by_id(deal_id)
if not deal:
raise HTTPException(status_code=404, detail=f"Сделка с ID {deal_id}a не найдена")
shipping_warehouse = await self._session.get(ShippingWarehouse, deal.shipping_warehouse_id)
warehouse_name = shipping_warehouse.name if shipping_warehouse else ""
buffer = BytesIO()
doc: SimpleDocTemplate = self._create_doc(buffer)
elements = []
total_pallets = len(deal.pallets)
boxes_on_pallets = await self._get_boxes_on_pallets_count(deal_id)
boxes_without_pallets = len(deal.boxes)
for box_on_pallet in range(boxes_without_pallets):
elements.append(Paragraph(f"ID: {deal_id}", self.medium_style))
elements.append(Paragraph(str(deal.name), self.medium_style))
elements.append(Paragraph(f"Короб {box_on_pallet + 1}/{boxes_without_pallets}", self.medium_style))
elements.append(Paragraph(warehouse_name, self.medium_style))
elements.append(PageBreak())
for pallet_idx, [_, box_count] in enumerate(boxes_on_pallets):
for box_on_pallet in range(box_count):
elements.append(Paragraph(f"ID: {deal_id}", self.medium_style))
elements.append(Paragraph(str(deal.name), self.medium_style))
box_label = f"Паллет {pallet_idx + 1}/{total_pallets}, Короб {box_on_pallet + 1}/{box_count}"
elements.append(Paragraph(box_label, self.medium_style))
elements.append(Paragraph(warehouse_name, self.medium_style))
elements.append(PageBreak())
doc.build(elements)
buffer.seek(0)
return buffer

View File

@@ -1,52 +1,16 @@
import os
from io import BytesIO from io import BytesIO
from reportlab.pdfgen.canvas import Canvas
from reportlab_qrcode import QRCodeImage
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import mm from reportlab.lib.units import mm
from reportlab.pdfbase import pdfmetrics from reportlab.pdfgen.canvas import Canvas
from reportlab.pdfbase.ttfonts import TTFont from reportlab.platypus import Paragraph
from reportlab.platypus import SimpleDocTemplate, Paragraph from reportlab_qrcode import QRCodeImage
from sqlalchemy.ext.asyncio import AsyncSession
from constants import APP_PATH from generators.base_pdf_card_generator.base_pdf_card_generator import BasePdfCardGenerator
from models import User from models import User
from services.user import UserService from services.user import UserService
class WorkShiftsQRCodeGenerator: class WorkShiftsQRCodeGenerator(BasePdfCardGenerator):
def __init__(self, session: AsyncSession):
self._session = session
assets_folder = os.path.join(APP_PATH, 'assets')
fonts_folder = os.path.join(assets_folder, 'fonts')
font_file_path = os.path.join(fonts_folder, 'DejaVuSans.ttf')
self.page_width = 58 * mm
self.page_height = 40 * mm
pdfmetrics.registerFont(TTFont('DejaVuSans', font_file_path))
styles = getSampleStyleSheet()
self.small_style = ParagraphStyle(
'Small',
parent=styles['Normal'],
alignment=1,
fontName='DejaVuSans', # Specify the new font
fontSize=9,
leading=12,
spaceAfter=2,
rightIndent=2,
)
def _create_doc(self, buffer):
return SimpleDocTemplate(
buffer,
pagesize=(self.page_width, self.page_height),
rightMargin=1,
leftMargin=1,
topMargin=1,
bottomMargin=1
)
async def generate(self, user_id: int) -> BytesIO: async def generate(self, user_id: int) -> BytesIO:
buffer = BytesIO() buffer = BytesIO()
doc = self._create_doc(buffer) doc = self._create_doc(buffer)
@@ -59,7 +23,7 @@ class WorkShiftsQRCodeGenerator:
position = user.position.name if user.position else "" position = user.position.name if user.position else ""
user_info = Paragraph( user_info = Paragraph(
f"{user.first_name} {user.second_name}\n{position}", f"{user.first_name} {user.second_name}\n{position}",
self.small_style self.small_centered_style
) )
doc.build([user_info], on_first_page) doc.build([user_info], on_first_page)

View File

@@ -48,6 +48,7 @@ routers_list = [
routers.statistics_router, routers.statistics_router,
routers.work_shifts_router, routers.work_shifts_router,
routers.expense_router, routers.expense_router,
routers.shipping_router,
] ]
for router in routers_list: for router in routers_list:
app.include_router(router) app.include_router(router)

View File

@@ -7,6 +7,7 @@ from sqlalchemy.orm import relationship, backref, Mapped, mapped_column
from models.base import BaseModel from models.base import BaseModel
from .marketplace import BaseMarketplace from .marketplace import BaseMarketplace
from .shipping import Pallet, Box
from .shipping_warehouse import ShippingWarehouse from .shipping_warehouse import ShippingWarehouse
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -104,6 +105,9 @@ class Deal(BaseModel):
manager_id: Mapped[int] = mapped_column(ForeignKey('users.id'), nullable=True) manager_id: Mapped[int] = mapped_column(ForeignKey('users.id'), nullable=True)
manager: Mapped[Optional["User"]] = relationship(back_populates='managed_deals', lazy='joined') manager: Mapped[Optional["User"]] = relationship(back_populates='managed_deals', lazy='joined')
pallets: Mapped[list[Pallet]] = relationship(back_populates='deal', lazy='selectin')
boxes: Mapped[list[Box]] = relationship(back_populates='deal', lazy='selectin')
class DealStatusHistory(BaseModel): class DealStatusHistory(BaseModel):
__tablename__ = 'deals_status_history' __tablename__ = 'deals_status_history'

58
models/shipping.py Normal file
View File

@@ -0,0 +1,58 @@
from typing import TYPE_CHECKING, Optional
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from models import BaseModel
if TYPE_CHECKING:
from models import Deal, Product
class Pallet(BaseModel):
__tablename__ = 'pallets'
id: Mapped[int] = mapped_column(primary_key=True)
deal_id: Mapped[int] = mapped_column(ForeignKey('deals.id'))
deal: Mapped['Deal'] = relationship(back_populates='pallets')
boxes: Mapped[list['Box']] = relationship(
back_populates='pallet',
uselist=True,
lazy='joined',
cascade='all, delete-orphan',
)
shipping_products: Mapped[list['ShippingProduct']] = relationship(
back_populates='pallet',
uselist=True,
lazy='joined',
cascade='all, delete-orphan',
)
class ShippingProduct(BaseModel):
__tablename__ = 'shipping_products'
id: Mapped[int] = mapped_column(primary_key=True)
quantity: Mapped[int] = mapped_column()
product_id: Mapped[int] = mapped_column(ForeignKey('products.id'))
product: Mapped['Product'] = relationship(lazy='joined')
pallet_id: Mapped[int] = mapped_column(ForeignKey('pallets.id'))
pallet: Mapped['Pallet'] = relationship(lazy='joined')
class Box(BaseModel):
__tablename__ = 'boxes'
id: Mapped[int] = mapped_column(primary_key=True)
quantity: Mapped[int] = mapped_column()
product_id: Mapped[int] = mapped_column(ForeignKey('products.id'))
product: Mapped['Product'] = relationship(lazy='joined')
pallet_id: Mapped[Optional[int]] = mapped_column(ForeignKey('pallets.id'))
pallet: Mapped[Pallet] = relationship(back_populates='boxes')
deal_id: Mapped[Optional[int]] = mapped_column(ForeignKey('deals.id'))
deal: Mapped['Deal'] = relationship(back_populates='boxes')

View File

@@ -15,4 +15,5 @@ from .billing import billing_router
from .task import task_router from .task import task_router
from .work_shifts import work_shifts_router from .work_shifts import work_shifts_router
from .statistics import statistics_router from .statistics import statistics_router
from .expense import expense_router from .expense import expense_router
from .shipping import shipping_router

128
routers/shipping.py Normal file
View File

@@ -0,0 +1,128 @@
from io import BytesIO
from fastapi import APIRouter, Depends, Response
from backend.dependecies import SessionDependency
from generators.shipping_qr_code_generator import ShippingQRCodeGenerator
from schemas.shipping import *
from services.auth import authorized_user
from services.shipping import ShippingService
shipping_router = APIRouter(
prefix="/shipping",
tags=["shipping"],
)
@shipping_router.post(
'/pallet/{deal_id}',
response_model=CreatePalletResponse,
operation_id='create_pallet',
dependencies=[Depends(authorized_user)],
)
async def create_pallet(
session: SessionDependency,
deal_id: int,
):
return await ShippingService(session).create_pallet(deal_id)
@shipping_router.delete(
'/pallet/{pallet_id}',
response_model=DeletePalletResponse,
operation_id='delete_pallet',
dependencies=[Depends(authorized_user)],
)
async def delete_pallet(
session: SessionDependency,
pallet_id: int,
):
return await ShippingService(session).delete_pallet(pallet_id)
@shipping_router.post(
'/product',
response_model=UpdateShippingProductResponse,
operation_id='update_shipping_product',
dependencies=[Depends(authorized_user)],
)
async def update_shipping_product(
session: SessionDependency,
request: UpdateShippingProductRequest,
):
return await ShippingService(session).update_shipping_product(request)
@shipping_router.delete(
'/product/{shipping_product_id}',
response_model=DeleteShippingProductResponse,
operation_id='delete_shipping_product',
dependencies=[Depends(authorized_user)],
)
async def delete_shipping_product(
session: SessionDependency,
shipping_product_id: int,
):
return await ShippingService(session).delete_shipping_product(shipping_product_id)
@shipping_router.post(
'/box',
response_model=UpdateBoxResponse,
operation_id='update_box',
dependencies=[Depends(authorized_user)],
)
async def update_box(
session: SessionDependency,
request: UpdateBoxRequest,
):
return await ShippingService(session).update_box(request)
@shipping_router.delete(
'/box/{box_id}',
response_model=DeleteBoxResponse,
operation_id='delete_box',
dependencies=[Depends(authorized_user)],
)
async def delete_box(
session: SessionDependency,
box_id: int,
):
return await ShippingService(session).delete_box(box_id)
@shipping_router.get(
"/pdf/deal/{deal_id}",
operation_id="get_deal_qr_code_pdf",
)
async def generate_deal_qr_code_pdf(
session: SessionDependency,
deal_id: int,
):
pdf_file: BytesIO = await ShippingQRCodeGenerator(session).generate_deal(deal_id)
return Response(pdf_file.getvalue(), media_type="application/pdf")
@shipping_router.get(
"/pdf/pallets/{deal_id}",
operation_id="get_pallets_pdf",
)
async def generate_pallets_pdf(
session: SessionDependency,
deal_id: int,
):
pdf_file: BytesIO = await ShippingQRCodeGenerator(session).generate_pallets(deal_id)
return Response(pdf_file.getvalue(), media_type="application/pdf")
@shipping_router.get(
"/pdf/boxes/{deal_id}",
operation_id="get_boxes_pdf",
)
async def generate_boxes_pdf(
session: SessionDependency,
deal_id: int,
):
pdf_file: BytesIO = await ShippingQRCodeGenerator(session).generate_boxes(deal_id)
return Response(pdf_file.getvalue(), media_type="application/pdf")

View File

@@ -11,6 +11,7 @@ from schemas.client import ClientSchema
from schemas.marketplace import BaseMarketplaceSchema from schemas.marketplace import BaseMarketplaceSchema
from schemas.product import ProductSchema from schemas.product import ProductSchema
from schemas.service import ServiceSchema, ServicePriceCategorySchema from schemas.service import ServiceSchema, ServicePriceCategorySchema
from schemas.shipping import PalletSchema, BoxSchema
from schemas.shipping_warehouse import ShippingWarehouseSchema from schemas.shipping_warehouse import ShippingWarehouseSchema
from schemas.user import UserSchema from schemas.user import UserSchema
@@ -102,6 +103,8 @@ class DealSchema(BaseSchema):
category: Optional[ServicePriceCategorySchema] = None category: Optional[ServicePriceCategorySchema] = None
group: Optional[DealGroupSchema] = None group: Optional[DealGroupSchema] = None
manager: Optional[UserSchema] = None manager: Optional[UserSchema] = None
pallets: List[PalletSchema] = []
boxes: List[BoxSchema] = []
delivery_date: Optional[datetime.datetime] = None delivery_date: Optional[datetime.datetime] = None
receiving_slot_date: Optional[datetime.datetime] = None receiving_slot_date: Optional[datetime.datetime] = None

94
schemas/shipping.py Normal file
View File

@@ -0,0 +1,94 @@
from typing import Optional
from schemas.base import BaseSchema, OkMessageSchema
from schemas.product import ProductSchema
# region Entities
class ProductAndQuantitySchema(BaseSchema):
product_id: Optional[int]
quantity: Optional[int]
class BoxSchema(BaseSchema):
id: int
quantity: int
product: ProductSchema
pallet_id: Optional[int]
deal_id: Optional[int]
class ShippingProductSchema(BaseSchema):
id: int
quantity: int
product: ProductSchema
pallet_id: int
class PalletSchema(BaseSchema):
id: int
boxes: list[BoxSchema]
shipping_products: list[ShippingProductSchema]
class CreateShippingProductSchema(ProductAndQuantitySchema):
pallet_id: int
class UpdateShippingProductSchema(ProductAndQuantitySchema):
shipping_product_id: int
class CreateBoxInPalletSchema(ProductAndQuantitySchema):
pallet_id: Optional[int]
class CreateBoxInDealSchema(ProductAndQuantitySchema):
deal_id: Optional[int]
class UpdateBoxSchema(ProductAndQuantitySchema):
box_id: Optional[int]
# endregion
# region Requests
class UpdateShippingProductRequest(BaseSchema):
data: CreateShippingProductSchema | UpdateShippingProductSchema
class UpdateBoxRequest(BaseSchema):
data: CreateBoxInDealSchema | CreateBoxInPalletSchema | UpdateBoxSchema
# endregion
# region Responses
class CreatePalletResponse(OkMessageSchema):
pass
class DeletePalletResponse(OkMessageSchema):
pass
class UpdateShippingProductResponse(OkMessageSchema):
pass
class UpdateBoxResponse(OkMessageSchema):
pass
class DeleteBoxResponse(OkMessageSchema):
pass
class DeleteShippingProductResponse(OkMessageSchema):
pass
# endregion

View File

@@ -10,6 +10,7 @@ import models.secondary
from models import User, Service, Client, DealProductService, deal_relations, GroupBillRequest from models import User, Service, Client, DealProductService, deal_relations, GroupBillRequest
from models.deal import * from models.deal import *
from models.deal_group import DealGroup from models.deal_group import DealGroup
from models.shipping import ShippingProduct
from schemas.client import ClientDetailsSchema from schemas.client import ClientDetailsSchema
from schemas.deal import * from schemas.deal import *
from services.auth import AuthService from services.auth import AuthService
@@ -331,7 +332,13 @@ class DealService(BaseService):
.joinedload(DealStatusHistory.user), .joinedload(DealStatusHistory.user),
selectinload(Deal.status_history) selectinload(Deal.status_history)
.noload(DealStatusHistory.deal), .noload(DealStatusHistory.deal),
selectinload(Deal.pallets)
.selectinload(Pallet.shipping_products)
.selectinload(ShippingProduct.product)
.noload(Product.barcodes),
selectinload(Deal.boxes)
.selectinload(Box.product)
.noload(Product.barcodes),
) )
.where(Deal.id == deal_id) .where(Deal.id == deal_id)
) )

121
services/shipping.py Normal file
View File

@@ -0,0 +1,121 @@
from sqlalchemy import select, and_
from models import Deal, Pallet, Box
from models.shipping import ShippingProduct
from schemas.shipping import *
from services.base import BaseService
class ShippingService(BaseService):
async def create_pallet(self, deal_id: int) -> CreatePalletResponse:
deal = await self.session.get(Deal, deal_id)
if not deal:
return CreatePalletResponse(ok=False, message="Сделка не найдена")
pallet = Pallet(deal_id=deal_id)
self.session.add(pallet)
await self.session.commit()
return CreatePalletResponse(ok=True, message="Паллет успешно создан")
async def delete_pallet(self, pallet_id: int) -> DeletePalletResponse:
pallet = await self.session.get(Pallet, pallet_id)
if not pallet:
return DeletePalletResponse(ok=False, message="Паллет не найден")
await self.session.delete(pallet)
await self.session.commit()
return DeletePalletResponse(ok=True, message="Паллет успешно удален")
async def _update_box(self, data: UpdateBoxSchema) -> tuple[bool, str]:
box = await self.session.get(Box, data.box_id)
if not box:
return False, f"Короб с ID:{data.box_id} не найден"
box.quantity = data.quantity
box.product_id = data.product_id
await self.session.commit()
return True, "Короб обновлен"
async def _create_box(self, data: CreateBoxInDealSchema | CreateBoxInPalletSchema):
box = Box(**data.model_dump())
self.session.add(box)
await self.session.commit()
async def _create_box_in_deal(self, data: CreateBoxInDealSchema) -> tuple[bool, str]:
deal = await self.session.get(Deal, data.deal_id)
if not deal:
return False, f"Сделка с ID:{data.deal_id} не найдена"
await self._create_box(data)
return True, f"Короб для сделки ID:{data.deal_id} добавлен"
async def _create_box_in_pallet(self, data: CreateBoxInPalletSchema) -> tuple[bool, str]:
pallet = await self.session.get(Pallet, data.pallet_id)
if not pallet:
return False, f"Паллет с ID:{data.pallet_id} не найден"
await self._create_box(data)
return True, f"Короб добавлен в паллет"
async def update_box(self, request: UpdateBoxRequest) -> UpdateBoxResponse:
data_keys = request.data.model_dump().keys()
if "box_id" in data_keys:
ok, message = await self._update_box(request.data)
elif "pallet_id" in data_keys:
ok, message = await self._create_box_in_pallet(CreateBoxInPalletSchema.model_validate(request.data))
else:
ok, message = await self._create_box_in_deal(CreateBoxInDealSchema.model_validate(request.data))
return UpdateBoxResponse(ok=ok, message=message)
async def delete_box(self, deal_id: int) -> DeleteBoxResponse:
box = await self.session.get(Box, deal_id)
if not box:
return DeleteBoxResponse(ok=False, message=f"Короб с ID:{deal_id} не найден")
await self.session.delete(box)
await self.session.commit()
return DeleteBoxResponse(ok=True, message="Короб успешно удален")
async def _get_shipping_product(self, pallet_id: int, product_id: int) -> Optional[ShippingProduct]:
stmt = (
select(ShippingProduct)
.where(
and_(ShippingProduct.pallet_id == pallet_id, ShippingProduct.product_id == product_id)
)
)
shipping_product = (await self.session.execute(stmt)).unique().one_or_none()
return shipping_product[0] if shipping_product else None
async def _update_shipping_product(self, data: UpdateShippingProductSchema) -> tuple[bool, str]:
shipping_product = await self.session.get(ShippingProduct, data.shipping_product_id)
shipping_product.product_id = data.product_id
shipping_product.quantity = data.quantity
await self.session.commit()
return True, "Запись о товаре на паллете успешно изменена"
async def _create_shipping_product(self, data: CreateShippingProductSchema) -> tuple[bool, str]:
shipping_product = ShippingProduct(**data.model_dump())
self.session.add(shipping_product)
await self.session.commit()
return True, "Запись о товаре на паллете успешно добавлена"
async def update_shipping_product(self, request: UpdateShippingProductRequest) -> UpdateShippingProductResponse:
data_keys = request.data.model_dump().keys()
if "shipping_product_id" in data_keys:
ok, message = await self._update_shipping_product(request.data)
else:
ok, message = await self._create_shipping_product(request.data)
return UpdateShippingProductResponse(ok=ok, message=message)
async def delete_shipping_product(self, shipping_product_id: int) -> DeleteShippingProductResponse:
shipping_product = await self.session.get(ShippingProduct, shipping_product_id)
if not shipping_product:
return DeleteShippingProductResponse(ok=False, message=f"Запись для данного паллета и товара не найдена")
await self.session.delete(shipping_product)
await self.session.commit()
return DeleteShippingProductResponse(ok=True, message="Запись о товаре на паллете успешно удалена")