feat: cards, attributes and modules

This commit is contained in:
2025-02-19 14:46:31 +04:00
parent a509a3a586
commit 1af78ce08a
61 changed files with 3212 additions and 2795 deletions

View File

@@ -0,0 +1 @@
from .handlers import *

View File

@@ -0,0 +1,2 @@
class CardAttributeException(Exception):
pass

View File

@@ -0,0 +1,2 @@
from .card_attributes_query_handler import CardAttributesQueryHandler
from .card_attributes_command_handler import CardAttributesCommandHandler

View File

@@ -0,0 +1,6 @@
from sqlalchemy.ext.asyncio import AsyncSession
class BaseHandler:
def __init__(self, session: AsyncSession):
self.session = session

View File

@@ -0,0 +1,88 @@
import pickle
from typing import Optional
from sqlalchemy import select, and_
from card_attributes.exceptions import CardAttributeException
from card_attributes.handlers.base_handler import BaseHandler
from models import CardAttribute, Attribute, Card
from .card_attributes_query_handler import CardAttributesQueryHandler
class CardAttributesCommandHandler(BaseHandler):
async def _create_card_attribute(self, card_id: int, attribute_id: int, value):
card_attribute = CardAttribute(
card_id=card_id,
attribute_id=attribute_id,
)
card_attribute.set_value(value)
self.session.add(card_attribute)
await self.session.flush()
async def _set_attribute_after_creation(self, card_id: int, project_attr: Attribute, attributes: Optional[dict]):
if attributes and project_attr.name in attributes:
passed_value = attributes[project_attr.name]
return await self._create_card_attribute(card_id, project_attr.id, passed_value)
if project_attr.default_value:
default_value = pickle.loads(project_attr.default_value)
return await self._create_card_attribute(card_id, project_attr.id, default_value)
if project_attr.is_nullable:
return await self._create_card_attribute(card_id, project_attr.id, None)
raise CardAttributeException("Required value was not provided")
async def set_attributes_after_creation(self, card: Card, attributes: Optional[dict] = None):
query_handler = CardAttributesQueryHandler(self.session)
project_attrs = await query_handler.get_attributes_for_project(card.board_id)
try:
for project_attr in project_attrs:
await self._set_attribute_after_creation(card.id, project_attr, attributes)
except CardAttributeException:
raise
async def _set_card_attribute(self, card_id: int, attribute_name: str, value):
query_handler = CardAttributesQueryHandler(self.session)
attribute = await query_handler.get_attr_by_name(attribute_name)
if not attribute:
raise CardAttributeException(f"Attribute [{attribute_name}] not found")
stmt = (
select(CardAttribute)
.where(
and_(
CardAttribute.card_id == card_id,
CardAttribute.attribute_id == attribute.id,
)
)
)
card_attribute: Optional[CardAttribute] = (await self.session.scalars(stmt)).one_or_none()
if not card_attribute:
await self._create_card_attribute(card_id, attribute.id, value)
else:
card_attribute.set_value(value)
async def set_attr_for_each_in_group(self, group_id: int, attribute_name: str, value):
query_handler = CardAttributesQueryHandler(self.session)
card_ids: list[int] = await query_handler.get_card_ids_by_group_id(group_id)
for card_id in card_ids:
await self._set_card_attribute(card_id, attribute_name, value)
async def set_attributes(self, card: Card, attributes: Optional[dict] = None):
query_handler = CardAttributesQueryHandler(self.session)
project_attrs: list[Attribute] = await query_handler.get_attributes_for_project(card.board_id)
try:
for attr_name, attr_value in attributes.items():
attr = next(attr for attr in project_attrs if attr.name == attr_name)
if attr:
if attr.is_applicable_to_group and card.group:
await self.set_attr_for_each_in_group(card.group.id, attr_name, attr_value)
else:
await self._set_card_attribute(card.id, attr_name, attr_value)
except CardAttributeException:
raise

View File

@@ -0,0 +1,37 @@
from typing import Optional
from sqlalchemy import select
from sqlalchemy.orm import joinedload, selectinload
from card_attributes.handlers.base_handler import BaseHandler
from models import Attribute, project_attribute, card_relations
class CardAttributesQueryHandler(BaseHandler):
async def get_attributes_for_project(self, project_id: int) -> list[Attribute]:
stmt = (
select(Attribute)
.join(project_attribute, project_attribute.c.attribute_id == Attribute.id)
.where(project_attribute.c.project_id == project_id)
)
attributes = (await self.session.scalars(stmt)).all()
return list(attributes)
async def get_attr_by_name(self, attr_name: str) -> Optional[Attribute]:
stmt = (
select(Attribute)
.options(
selectinload(Attribute.projects),
)
.where(Attribute.name == attr_name)
)
attribute = (await self.session.scalars(stmt)).first()
return attribute
async def get_card_ids_by_group_id(self, group_id: int) -> list[int]:
stmt = (
select(card_relations.c.card_id)
.where(card_relations.c.group_id == group_id)
)
ids = await self.session.scalars(stmt)
return list(ids)

View File

@@ -1,24 +1,24 @@
from typing import TypedDict, List, Dict, Tuple, Optional from typing import TypedDict, List, Dict, Tuple, Optional
from models import DealProduct, Deal, DealStatusHistory from models import CardProduct, Card, CardStatusHistory
class DealTechSpecProductData(TypedDict): class DealTechSpecProductData(TypedDict):
deal: Deal deal: Card
last_status: DealStatusHistory last_status: CardStatusHistory
total_one_product: int total_one_product: int
quantity: int quantity: int
additional_info: Optional[str] additional_info: Optional[str]
# Поле для группировки товаров с одним артикулом и вывода таблицы [Штрихкод, Размер, Кол-во, Короба] # Поле для группировки товаров с одним артикулом и вывода таблицы [Штрихкод, Размер, Кол-во, Короба]
deal_products: List[DealProduct] deal_products: List[CardProduct]
# Поле для группировки товаров из нескольких сделок и вывода таблицы [Склад отгрузки, Кол-во] # Поле для группировки товаров из нескольких сделок и вывода таблицы [Склад отгрузки, Кол-во]
warehouses_and_quantities: List[Tuple[str, int]] warehouses_and_quantities: List[Tuple[str, int]]
class DealTechSpecData(TypedDict): class DealTechSpecData(TypedDict):
deals: List[Deal] deals: List[Card]
products: Dict[str, DealTechSpecProductData] products: Dict[str, DealTechSpecProductData]
product_images: Tuple product_images: Tuple
deal_ids_header: str deal_ids_header: str

View File

@@ -8,13 +8,13 @@ from weasyprint import HTML, CSS
from constants import DEAL_STATUS_STR, ENV, APP_PATH from constants import DEAL_STATUS_STR, ENV, APP_PATH
from generators.deal_pdf_generator.deal_data import DealTechSpecProductData, DealTechSpecData from generators.deal_pdf_generator.deal_data import DealTechSpecProductData, DealTechSpecData
from models import Deal, DealProduct, DealService as DealServiceModel, Product, DealGroup from models import Card, CardProduct, CardService as DealServiceModel, Product, CardGroup
from services.deal_group import DealGroupService from services.card_group import CardGroupService
from utils.images_fetcher import fetch_images from utils.images_fetcher import fetch_images
# Генерация ключа для группировки deal_product по артикулу и услугам # Генерация ключа для группировки deal_product по артикулу и услугам
def _gen_key_for_product(deal_product: DealProduct) -> str: def _gen_key_for_product(deal_product: CardProduct) -> str:
return f"{deal_product.product.article} - " + ",".join( return f"{deal_product.product.article} - " + ",".join(
str(service.service_id) for service in deal_product.services str(service.service_id) for service in deal_product.services
) )
@@ -48,10 +48,10 @@ class DealTechSpecPdfGenerator:
"deal_ids_header": "", "deal_ids_header": "",
"deal_status_str": DEAL_STATUS_STR, "deal_status_str": DEAL_STATUS_STR,
} }
self.deal: Deal self.deal: Card
@staticmethod @staticmethod
async def _group_deal_products_by_products(deal_products: List[DealProduct]) -> Dict[str, DealTechSpecProductData]: async def _group_deal_products_by_products(deal_products: List[CardProduct]) -> Dict[str, DealTechSpecProductData]:
products: Dict[str, DealTechSpecProductData] = {} products: Dict[str, DealTechSpecProductData] = {}
additional_info: Optional[str] additional_info: Optional[str]
@@ -61,7 +61,7 @@ class DealTechSpecPdfGenerator:
if key not in products: if key not in products:
products[key] = { products[key] = {
"deal": deal_product.deal, "deal": deal_product.card,
"deal_products": [deal_product], "deal_products": [deal_product],
"quantity": deal_product.quantity, "quantity": deal_product.quantity,
"additional_info": deal_product.product.additional_info, "additional_info": deal_product.product.additional_info,
@@ -75,18 +75,18 @@ class DealTechSpecPdfGenerator:
return products return products
async def _get_deal_by_id(self, deal_id: int) -> Optional[Deal]: async def _get_deal_by_id(self, deal_id: int) -> Optional[Card]:
deal: Deal | None = await self._session.scalar( deal: Card | None = await self._session.scalar(
select(Deal) select(Card)
.where(Deal.id == deal_id) .where(Card.id == deal_id)
.options( .options(
selectinload(Deal.products).selectinload(DealProduct.services), selectinload(Card.products).selectinload(CardProduct.services),
selectinload(Deal.products).selectinload(DealProduct.product).selectinload(Product.barcodes), selectinload(Card.products).selectinload(CardProduct.product).selectinload(Product.barcodes),
selectinload(Deal.services).selectinload(DealServiceModel.service), selectinload(Card.services).selectinload(DealServiceModel.service),
selectinload(Deal.status_history), selectinload(Card.status_history),
selectinload(Deal.group).selectinload(DealGroup.deals), selectinload(Card.group).selectinload(CardGroup.cards),
joinedload(Deal.client), joinedload(Card.client),
joinedload(Deal.shipping_warehouse), joinedload(Card.shipping_warehouse),
) )
) )
return deal return deal
@@ -94,7 +94,7 @@ class DealTechSpecPdfGenerator:
def _set_deals_ids_header(self): def _set_deals_ids_header(self):
self.deal_doc["deal_ids_header"] = f"ID: {self.deal.id}" self.deal_doc["deal_ids_header"] = f"ID: {self.deal.id}"
if self.deal.group: if self.deal.group:
self.deal_doc["deal_ids_header"] = "ID: " + ", ".join(str(d.id) for d in self.deal.group.deals) self.deal_doc["deal_ids_header"] = "ID: " + ", ".join(str(d.id) for d in self.deal.group.cards)
async def _create_deal_tech_spec_document_html(self, deal_id: int): async def _create_deal_tech_spec_document_html(self, deal_id: int):
deal = await self._get_deal_by_id(deal_id) deal = await self._get_deal_by_id(deal_id)
@@ -105,7 +105,7 @@ class DealTechSpecPdfGenerator:
self._set_deals_ids_header() self._set_deals_ids_header()
if deal.group: if deal.group:
deals = await DealGroupService(self._session).get_deals_by_group_id(deal.group.id) deals = await CardGroupService(self._session).get_cards_by_group_id(deal.group.id)
for d in deals: for d in deals:
self.deal_doc["deals"].append(d) self.deal_doc["deals"].append(d)
grouped_products = await self._group_deal_products_by_products(d.products) grouped_products = await self._group_deal_products_by_products(d.products)

View File

@@ -42,7 +42,7 @@ class PriceListPdfGenerator:
# Определяем функцию сортировки категорий по рангу # Определяем функцию сортировки категорий по рангу
def category_sort_key(category): def category_sort_key(category):
if service_type == ServiceType.DEAL_SERVICE: if service_type == ServiceType.DEAL_SERVICE:
return category.deal_service_rank return category.card_service_rank
else: else:
return category.product_service_rank return category.product_service_rank

View File

@@ -63,7 +63,7 @@ class ServiceExcelExporter:
categories = {service.category for services in categories_dict.values() for service in services} categories = {service.category for services in categories_dict.values() for service in services}
def category_sort_key(category): def category_sort_key(category):
return category.deal_service_rank if service_type == ServiceType.DEAL_SERVICE else category.product_service_rank return category.card_service_rank if service_type == ServiceType.DEAL_SERVICE else category.product_service_rank
sorted_categories = sorted(categories, key=category_sort_key) sorted_categories = sorted(categories, key=category_sort_key)
sorted_categories_dict = {category.id: categories_dict[category.id] for category in sorted_categories} sorted_categories_dict = {category.id: categories_dict[category.id] for category in sorted_categories}

View File

@@ -11,18 +11,18 @@ from sqlalchemy.orm import joinedload, selectinload
from constants import DOMAIN_NAME from constants import DOMAIN_NAME
from generators.base_pdf_card_generator.base_pdf_card_generator import BasePdfCardGenerator from generators.base_pdf_card_generator.base_pdf_card_generator import BasePdfCardGenerator
from models import Deal, ShippingWarehouse, Pallet from models import Card, ShippingWarehouse, Pallet
from models.shipping import Box from models.shipping import Box
class ShippingQRCodeGenerator(BasePdfCardGenerator): class ShippingQRCodeGenerator(BasePdfCardGenerator):
async def _get_deal_by_id(self, deal_id: int) -> Optional[Deal]: async def _get_deal_by_id(self, deal_id: int) -> Optional[Card]:
stmt = ( stmt = (
select(Deal) select(Card)
.where(Deal.id == deal_id) .where(Card.id == deal_id)
.options( .options(
joinedload(Deal.shipping_warehouse), joinedload(Card.shipping_warehouse),
selectinload(Deal.pallets), selectinload(Card.pallets),
) )
) )
deal = (await self._session.execute(stmt)).one_or_none() deal = (await self._session.execute(stmt)).one_or_none()
@@ -93,7 +93,7 @@ class ShippingQRCodeGenerator(BasePdfCardGenerator):
func.count(Box.id).label("box_count"), func.count(Box.id).label("box_count"),
) )
.join(Box, isouter=True) .join(Box, isouter=True)
.where(Pallet.deal_id == deal_id) .where(Pallet.card_id == deal_id)
.group_by(Pallet.id) .group_by(Pallet.id)
) )
pallets = (await self._session.execute(stmt_boxes_on_pallets)).all() pallets = (await self._session.execute(stmt_boxes_on_pallets)).all()

View File

@@ -31,8 +31,8 @@ app.add_middleware(
routers_list = [ routers_list = [
routers.auth_router, routers.auth_router,
routers.deal_router, routers.card_router,
routers.deal_group_router, routers.card_group_router,
routers.client_router, routers.client_router,
routers.service_router, routers.service_router,
routers.product_router, routers.product_router,

View File

@@ -1,10 +1,13 @@
from sqlalchemy.orm import configure_mappers from sqlalchemy.orm import configure_mappers
from .auth import *
from .project import * from .project import *
from .module import *
from .board import * from .board import *
from .status import * from .status import *
from .deal import * from .attribute import *
from .card import *
from .auth import *
from .card import *
from .client import * from .client import *
from .service import * from .service import *
from .product import * from .product import *
@@ -15,7 +18,7 @@ from .marketplace import *
from .payroll import * from .payroll import *
from .billing import * from .billing import *
from .marketplace_products import * from .marketplace_products import *
from .deal_group import * from .card_group import *
from .transaction import * from .transaction import *
from .residues import * from .residues import *
from .shipping import * from .shipping import *

100
models/attribute.py Normal file
View File

@@ -0,0 +1,100 @@
import pickle
from typing import TYPE_CHECKING
from sqlalchemy import ForeignKey, Table, Column, UniqueConstraint, Index
from sqlalchemy.orm import Mapped, mapped_column, relationship
from models.base import BaseModel
if TYPE_CHECKING:
from models import Project, BaseModel, Card
project_attribute = Table(
'project_attribute',
BaseModel.metadata,
Column('project_id', ForeignKey('projects.id')),
Column('attribute_id', ForeignKey('attributes.id')),
)
class AttributeType(BaseModel):
__tablename__ = 'attribute_types'
id: Mapped[int] = mapped_column(primary_key=True)
type: Mapped[str] = mapped_column(nullable=False, unique=True)
name: Mapped[str] = mapped_column(nullable=False, unique=True)
is_deleted: Mapped[bool] = mapped_column(default=False)
attributes: Mapped['Attribute'] = relationship(
'Attribute',
back_populates='type',
lazy='noload',
)
class Attribute(BaseModel):
__tablename__ = 'attributes'
id: Mapped[int] = mapped_column(primary_key=True)
label: Mapped[str] = mapped_column(nullable=False)
name: Mapped[str] = mapped_column(nullable=False, unique=True)
is_applicable_to_group: Mapped[bool] = mapped_column(
default=False,
comment='Применять ли изменения атрибута карточки ко всем карточкам в группе',
)
is_nullable: Mapped[bool] = mapped_column(default=False, nullable=False)
default_value: Mapped[bytes] = mapped_column(nullable=True)
projects: Mapped[list['Project']] = relationship(
'Project',
uselist=True,
secondary='project_attribute',
back_populates='attributes',
lazy='noload',
)
type_id: Mapped[int] = mapped_column(ForeignKey('attribute_types.id'), nullable=False)
type: Mapped[AttributeType] = relationship(
'AttributeType',
back_populates='attributes',
lazy='joined',
)
card_attributes: Mapped[list['CardAttribute']] = relationship(
'CardAttribute',
uselist=True,
lazy='noload',
back_populates='attribute',
)
class CardAttribute(BaseModel):
__tablename__ = 'card_attributes'
id: Mapped[int] = mapped_column(primary_key=True)
value: Mapped[bytes] = mapped_column(nullable=True)
card_id: Mapped[int] = mapped_column(ForeignKey('cards.id'), nullable=False)
card: Mapped['Card'] = relationship(
'Card',
back_populates='attributes',
lazy='noload',
)
attribute_id: Mapped[int] = mapped_column(ForeignKey('attributes.id'), nullable=False)
attribute: Mapped[Attribute] = relationship(
'Attribute',
back_populates='card_attributes',
lazy='joined',
)
__table_args__ = (
UniqueConstraint('card_id', 'attribute_id', name='uix_card_service'),
Index('idx_card_id_attribute_id', 'card_id', 'attribute_id', unique=True)
)
def set_value(self, value):
self.value = pickle.dumps(value)
def get_value(self):
return pickle.loads(self.value)

View File

@@ -9,7 +9,7 @@ from models.work_shifts import WorkShift
if TYPE_CHECKING: if TYPE_CHECKING:
from models.payroll import PayRate, PaymentRecord from models.payroll import PayRate, PaymentRecord
from models import Deal, DealEmployees from models import Card, CardEmployees
role_permissions = Table( role_permissions = Table(
'role_permissions', 'role_permissions',
@@ -115,7 +115,7 @@ class User(BaseModel):
foreign_keys="WorkShift.user_id" foreign_keys="WorkShift.user_id"
) )
managed_deals: Mapped[list["Deal"]] = relationship( managed_cards: Mapped[list["Card"]] = relationship(
back_populates="manager", back_populates="manager",
uselist=True, uselist=True,
) )
@@ -127,7 +127,7 @@ class User(BaseModel):
cascade="all, delete-orphan" cascade="all, delete-orphan"
) )
deals: Mapped[list['DealEmployees']] = relationship( cards: Mapped[list['CardEmployees']] = relationship(
back_populates='user', back_populates='user',
lazy='selectin' lazy='selectin'
) )

View File

@@ -35,14 +35,17 @@ class BarcodeTemplate(BaseModel):
__tablename__ = 'barcode_templates' __tablename__ = 'barcode_templates'
id = Column(Integer, autoincrement=True, primary_key=True, index=True) id = Column(Integer, autoincrement=True, primary_key=True, index=True)
name = Column(String, nullable=False, index=True, comment='Название шаблона') name = Column(String, nullable=False, index=True, comment='Название шаблона')
attributes = relationship('BarcodeTemplateAttribute', attributes = relationship(
'BarcodeTemplateAttribute',
secondary=barcode_template_attribute_link, secondary=barcode_template_attribute_link,
lazy='selectin' lazy='selectin',
) )
additional_attributes = relationship('BarcodeTemplateAdditionalField', additional_attributes = relationship(
'BarcodeTemplateAdditionalField',
lazy='selectin', lazy='selectin',
back_populates='barcode_template', back_populates='barcode_template',
cascade="all, delete") cascade="all, delete",
)
additional_field = Column(String, nullable=True, comment='Дополнительное поле') additional_field = Column(String, nullable=True, comment='Дополнительное поле')
is_default = Column(Boolean, nullable=False, default=False, comment='По умолчанию') is_default = Column(Boolean, nullable=False, default=False, comment='По умолчанию')

View File

@@ -7,17 +7,19 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
from models import BaseModel from models import BaseModel
if TYPE_CHECKING: if TYPE_CHECKING:
from models import Deal, DealGroup from models import Card, CardGroup
class DealBillRequest(BaseModel): class CardBillRequest(BaseModel):
__tablename__ = 'deal_bill_requests' __tablename__ = 'card_bill_requests'
deal_id: Mapped[int] = mapped_column(ForeignKey('deals.id'), card_id: Mapped[int] = mapped_column(
ForeignKey('cards.id'),
nullable=False, nullable=False,
primary_key=True, primary_key=True,
unique=True) unique=True,
deal: Mapped['Deal'] = relationship(back_populates='bill_request') )
card: Mapped['Card'] = relationship(back_populates='bill_request')
created_at: Mapped[datetime.datetime] = mapped_column(nullable=False) created_at: Mapped[datetime.datetime] = mapped_column(nullable=False)
paid: Mapped[bool] = mapped_column(nullable=False, default=False) paid: Mapped[bool] = mapped_column(nullable=False, default=False)
@@ -29,11 +31,13 @@ class DealBillRequest(BaseModel):
class GroupBillRequest(BaseModel): class GroupBillRequest(BaseModel):
__tablename__ = 'group_bill_requests' __tablename__ = 'group_bill_requests'
group_id: Mapped[int] = mapped_column(ForeignKey('deal_groups.id'), group_id: Mapped[int] = mapped_column(
ForeignKey('card_groups.id'),
nullable=False, nullable=False,
primary_key=True, primary_key=True,
unique=True) unique=True,
group: Mapped['DealGroup'] = relationship(back_populates='bill_request') )
group: Mapped['CardGroup'] = relationship(back_populates='bill_request')
created_at: Mapped[datetime.datetime] = mapped_column(nullable=False) created_at: Mapped[datetime.datetime] = mapped_column(nullable=False)
paid: Mapped[bool] = mapped_column(nullable=False, default=False) paid: Mapped[bool] = mapped_column(nullable=False, default=False)

View File

@@ -4,10 +4,10 @@ from typing import TYPE_CHECKING
from sqlalchemy import ForeignKey from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from models import BaseModel from models.base import BaseModel
if TYPE_CHECKING: if TYPE_CHECKING:
from models import Project, DealStatus, Deal from models import Project, CardStatus, Card
class Board(BaseModel): class Board(BaseModel):
@@ -26,6 +26,6 @@ class Board(BaseModel):
lazy="selectin", lazy="selectin",
) )
deal_statuses: Mapped[list["DealStatus"]] = relationship("DealStatus", back_populates="board", lazy="selectin", cascade="all,delete") statuses: Mapped[list["CardStatus"]] = relationship("CardStatus", back_populates="board", lazy="selectin", cascade="all,delete")
deals: Mapped[list["Deal"]] = relationship("Deal", uselist=True, back_populates="board", lazy="selectin") cards: Mapped[list["Card"]] = relationship("Card", uselist=True, back_populates="board", lazy="selectin")

118
models/card.py Normal file
View File

@@ -0,0 +1,118 @@
from datetime import datetime
from typing import Optional, TYPE_CHECKING
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, backref, Mapped, mapped_column
from models.base import BaseModel
from .marketplace import BaseMarketplace
from .shipping import Pallet, Box
from .shipping_warehouse import ShippingWarehouse
if TYPE_CHECKING:
from . import CardBillRequest, User, BaseModel, Board, CardStatus, CardGroup, CardAttribute, CardService as CardServiceModel, \
CardProduct, Client
class Card(BaseModel):
__tablename__ = 'cards'
# region Base card attributes
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(nullable=False, comment='Название карточки')
comment: Mapped[str] = mapped_column(nullable=False, server_default='', comment='Комментарий')
created_at: Mapped[datetime] = mapped_column(nullable=False, comment='Дата создания')
is_deleted: Mapped[bool] = mapped_column(nullable=False, server_default='0', default=False, comment='Удалена')
is_completed: Mapped[bool] = mapped_column(nullable=False, server_default='0', default=False, comment='Завершена')
lexorank: Mapped[str] = mapped_column(nullable=False, comment='Lexorank', index=True)
board_id: Mapped[int] = mapped_column(ForeignKey('boards.id'), nullable=True, server_default='1')
board: Mapped['Board'] = relationship(
'Board',
back_populates='cards',
)
current_status_id: Mapped[int] = mapped_column(
ForeignKey('card_statuses.id'),
nullable=False,
comment='Текущий статус',
)
status: Mapped['CardStatus'] = relationship(lazy='selectin')
status_history = relationship('CardStatusHistory', back_populates='card', cascade="all, delete-orphan")
attributes: Mapped[list['CardAttribute']] = relationship(
'CardAttribute',
uselist=True,
back_populates='card',
lazy='selectin',
)
group: Mapped[Optional["CardGroup"]] = relationship(
'CardGroup',
secondary='card_relations',
lazy='joined',
back_populates='cards'
)
# endregion
# region Attributes handled by modules
# module servicesAndProducts
is_locked: Mapped[bool] = mapped_column(default=False, server_default='0')
shipping_warehouse_id: Mapped[int] = mapped_column(ForeignKey('shipping_warehouses.id'), nullable=True)
shipping_warehouse: Mapped["ShippingWarehouse"] = relationship()
base_marketplace_key: Mapped[str] = mapped_column(ForeignKey("base_marketplaces.key"), nullable=True)
base_marketplace: Mapped["BaseMarketplace"] = relationship(lazy="joined")
services: Mapped[list['CardServiceModel']] = relationship(
'CardService',
back_populates='card',
cascade="all, delete-orphan",
order_by="desc(CardService.service_id)"
)
products: Mapped[list['CardProduct']] = relationship(
'CardProduct',
back_populates='card',
cascade="all, delete-orphan",
order_by="desc(CardProduct.product_id)"
)
bill_request: Mapped[Optional['CardBillRequest']] = relationship(back_populates='card', lazy='joined')
# module client
client_id: Mapped[int] = mapped_column(
ForeignKey('clients.id', ondelete='CASCADE'),
nullable=False,
comment='ID клиента',
)
client: Mapped['Client'] = relationship('Client', backref=backref('cards', cascade="all, delete-orphan"))
# module managers
manager_id: Mapped[int] = mapped_column(ForeignKey('users.id'), nullable=True)
manager: Mapped[Optional["User"]] = relationship(back_populates='managed_cards', lazy='joined')
# module shipment
pallets: Mapped[list[Pallet]] = relationship(back_populates='card', lazy='selectin')
boxes: Mapped[list[Box]] = relationship(back_populates='card', lazy='selectin')
# module employees
employees: Mapped[list['CardEmployees']] = relationship(back_populates='card', lazy='selectin')
# endregion
class CardEmployees(BaseModel):
__tablename__ = 'card_employees'
user_id: Mapped[int] = mapped_column(ForeignKey('users.id'), primary_key=True)
user: Mapped['User'] = relationship('User', back_populates='cards', lazy='selectin')
card_id: Mapped[int] = mapped_column(ForeignKey('cards.id'), primary_key=True)
card: Mapped[Card] = relationship('Card', back_populates='employees', lazy='selectin')
created_at: Mapped[datetime] = mapped_column()

View File

@@ -7,11 +7,11 @@ from models import BaseModel
from models import GroupBillRequest from models import GroupBillRequest
if TYPE_CHECKING: if TYPE_CHECKING:
from models import Deal from models import Card
class DealGroup(BaseModel): class CardGroup(BaseModel):
__tablename__ = 'deal_groups' __tablename__ = 'card_groups'
id: Mapped[int] = mapped_column( id: Mapped[int] = mapped_column(
primary_key=True primary_key=True
) )
@@ -21,9 +21,9 @@ class DealGroup(BaseModel):
lexorank: Mapped[str] = mapped_column( lexorank: Mapped[str] = mapped_column(
nullable=False nullable=False
) )
deals: Mapped[list["Deal"]] = relationship( cards: Mapped[list["Card"]] = relationship(
back_populates='group', back_populates='group',
secondary='deal_relations' secondary='card_relations'
) )
bill_request: Mapped[Optional['GroupBillRequest']] = relationship( bill_request: Mapped[Optional['GroupBillRequest']] = relationship(
back_populates='group', back_populates='group',
@@ -31,9 +31,9 @@ class DealGroup(BaseModel):
) )
deal_relations = Table( card_relations = Table(
'deal_relations', 'card_relations',
BaseModel.metadata, BaseModel.metadata,
Column('deal_id', ForeignKey('deals.id'), primary_key=True, unique=True), Column('card_id', ForeignKey('cards.id'), primary_key=True, unique=True),
Column('group_id', ForeignKey('deal_groups.id'), primary_key=True) Column('group_id', ForeignKey('card_groups.id'), primary_key=True)
) )

View File

@@ -1,70 +1,59 @@
from datetime import datetime
from typing import Optional, TYPE_CHECKING from typing import Optional, TYPE_CHECKING
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, Mapped, mapped_column from sqlalchemy.orm import relationship, Mapped, mapped_column
from models import BaseModel from models import BaseModel
if TYPE_CHECKING: if TYPE_CHECKING:
from models import ResidualPallet, ResidualBox from models import ResidualPallet, ResidualBox, Product, BarcodeTemplate, User
class Client(BaseModel): class Client(BaseModel):
__tablename__ = 'clients' __tablename__ = 'clients'
id = Column(Integer, autoincrement=True, primary_key=True, index=True) id: Mapped[int] = mapped_column(primary_key=True)
name = Column(String, nullable=False, unique=True, comment='Название клиента') name: Mapped[str] = mapped_column(nullable=False, unique=True, comment='Название клиента')
# TODO replace with additional model company_name: Mapped[str] = mapped_column(
company_name = Column(String,
nullable=False, nullable=False,
server_default='', server_default='',
comment='Название компании') comment='Название компании',
)
created_at = Column(DateTime, nullable=False, comment='Дата создания') created_at: Mapped[datetime] = mapped_column(nullable=False, comment='Дата создания')
products = relationship('Product', back_populates='client') products: Mapped[list['Product']] = relationship('Product', back_populates='client')
details = relationship('ClientDetails', uselist=False, back_populates='client', cascade='all, delete', details: Mapped['ClientDetails'] = relationship(
lazy='joined') uselist=False,
back_populates='client',
cascade='all, delete',
lazy='joined',
)
barcode_template_id = Column(Integer, ForeignKey('barcode_templates.id'), nullable=True) barcode_template_id: Mapped[int] = mapped_column(ForeignKey('barcode_templates.id'), nullable=True)
barcode_template = relationship('BarcodeTemplate', lazy='selectin') barcode_template: Mapped['BarcodeTemplate'] = relationship('BarcodeTemplate', lazy='selectin')
comment: Mapped[Optional[str]] = mapped_column(nullable=True, server_default=None, comment='Комментарий') comment: Mapped[Optional[str]] = mapped_column(nullable=True, server_default=None, comment='Комментарий')
pallets: Mapped[list["ResidualPallet"]] = relationship(back_populates='client', lazy='selectin') pallets: Mapped[list['ResidualPallet']] = relationship(back_populates='client', lazy='selectin')
boxes: Mapped[list["ResidualBox"]] = relationship(back_populates='client', lazy='selectin') boxes: Mapped[list['ResidualBox']] = relationship(back_populates='client', lazy='selectin')
class ClientDetails(BaseModel): class ClientDetails(BaseModel):
__tablename__ = 'client_details' __tablename__ = 'client_details'
id = Column(Integer, autoincrement=True, primary_key=True, index=True) id: Mapped[int] = mapped_column(primary_key=True)
client_id = Column(Integer, ForeignKey('clients.id'), unique=True, nullable=False, comment='ID клиента') client_id: Mapped[int] = mapped_column(ForeignKey('clients.id'), unique=True, nullable=False, comment='ID клиента')
client = relationship('Client', back_populates='details', cascade='all, delete', uselist=False) client: Mapped[Client] = relationship('Client', back_populates='details', cascade='all, delete', uselist=False)
telegram = Column(String) telegram: Mapped[Optional[str]] = mapped_column()
phone_number = Column(String) phone_number: Mapped[Optional[str]] = mapped_column()
inn = Column(String) inn: Mapped[Optional[str]] = mapped_column()
email = Column(String) email: Mapped[Optional[str]] = mapped_column()
last_modified_at = Column(DateTime, nullable=False) last_modified_at: Mapped[datetime] = mapped_column(nullable=False)
modified_by_user_id = Column(Integer, ForeignKey('users.id'), nullable=False) modified_by_user_id: Mapped[int] = mapped_column(ForeignKey('users.id'), nullable=False)
modified_by_user = relationship('User') modified_by_user: Mapped['User'] = relationship('User')
# class ClientContact(BaseModel):
# __tablename__ = 'client_contact'
# id: Mapped[int] = mapped_column(primary_key=True)
#
# client_id: Mapped[int] = mapped_column(ForeignKey('clients.id'))
# client: Mapped["Client"] = relationship('Client', back_populates='users')
#
# first_name: Mapped[str] = mapped_column()
# last_name: Mapped[str] = mapped_column()
#
# telegram: Mapped[str] = mapped_column()
# phone_number: Mapped[str] = mapped_column()
# email: Mapped[str] = mapped_column()
# inn: Mapped[str] = mapped_column()
#

View File

@@ -1,106 +0,0 @@
from datetime import datetime
from enum import IntEnum, unique
from typing import Optional, TYPE_CHECKING
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean
from sqlalchemy.orm import relationship, backref, Mapped, mapped_column
from models.base import BaseModel
from .marketplace import BaseMarketplace
from .board import Board
from .status import DealStatus
from .shipping import Pallet, Box
from .shipping_warehouse import ShippingWarehouse
if TYPE_CHECKING:
from . import (
DealBillRequest,
DealGroup,
User,
)
class Deal(BaseModel):
__tablename__ = 'deals'
id = Column(Integer, autoincrement=True, primary_key=True, index=True)
name = Column(String, nullable=False, comment='Название сделки')
created_at = Column(DateTime, nullable=False, comment='Дата создания')
client_id = Column(Integer, ForeignKey('clients.id', ondelete='CASCADE'), nullable=False, comment='ID клиента')
client = relationship('Client', backref=backref('deals', cascade="all, delete-orphan"))
status_history = relationship('DealStatusHistory', back_populates='deal', cascade="all, delete-orphan")
is_deleted = Column(Boolean, nullable=False, server_default='0', default=False, comment='Удалена')
is_completed = Column(Boolean, nullable=False, server_default='0', default=False, comment='Завершена')
is_locked: Mapped[bool] = mapped_column(default=False, server_default='0')
is_accounted: Mapped[bool] = mapped_column(default=True, server_default='1')
current_status_id: Mapped[int] = mapped_column(
ForeignKey("deal_statuses.id"),
nullable=False,
comment='Текущий статус',
)
status: Mapped["DealStatus"] = relationship(lazy="selectin")
shipping_warehouse_id: Mapped[int] = mapped_column(ForeignKey('shipping_warehouses.id'), nullable=True)
shipping_warehouse: Mapped["ShippingWarehouse"] = relationship()
base_marketplace_key: Mapped[str] = mapped_column(ForeignKey("base_marketplaces.key"), nullable=True)
base_marketplace: Mapped["BaseMarketplace"] = relationship(lazy="joined")
delivery_date: Mapped[Optional[datetime]] = mapped_column(nullable=True)
receiving_slot_date: Mapped[Optional[datetime]] = mapped_column(nullable=True)
services = relationship(
'DealService',
back_populates='deal',
cascade="all, delete-orphan",
order_by="desc(DealService.service_id)"
)
products = relationship(
'DealProduct',
back_populates='deal',
cascade="all, delete-orphan",
order_by="desc(DealProduct.product_id)"
)
board_id: Mapped[int] = mapped_column(ForeignKey('boards.id'), nullable=True, server_default='1')
board: Mapped[Board] = relationship(
"Board",
back_populates="deals",
)
# TODO remake with sequence
lexorank = Column(String, nullable=False, comment='Lexorank', index=True)
comment = Column(String, nullable=False, server_default='', comment='Коментарий к заданию')
bill_request: Mapped[Optional['DealBillRequest']] = relationship(back_populates='deal', lazy='joined')
group: Mapped[Optional["DealGroup"]] = relationship(
'DealGroup',
secondary='deal_relations',
lazy='joined',
back_populates='deals'
)
manager_id: Mapped[int] = mapped_column(ForeignKey('users.id'), nullable=True)
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')
employees: Mapped[list['DealEmployees']] = relationship(back_populates='deal', lazy='selectin')
class DealEmployees(BaseModel):
__tablename__ = 'deal_employees'
user_id: Mapped[int] = mapped_column(ForeignKey('users.id'), primary_key=True)
user: Mapped['User'] = relationship('User', back_populates='deals', lazy='selectin')
deal_id: Mapped[int] = mapped_column(ForeignKey('deals.id'), primary_key=True)
deal: Mapped[Deal] = relationship('Deal', back_populates='employees', lazy='selectin')
created_at: Mapped[datetime] = mapped_column()

33
models/module.py Normal file
View File

@@ -0,0 +1,33 @@
from typing import TYPE_CHECKING
from sqlalchemy import Table, Column, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from models.base import BaseModel
if TYPE_CHECKING:
from models import Project
project_module = Table(
'project_module',
BaseModel.metadata,
Column('project_id', ForeignKey('projects.id')),
Column('module_id', ForeignKey('modules.id')),
)
class Module(BaseModel):
__tablename__ = 'modules'
id: Mapped[int] = mapped_column(primary_key=True)
key: Mapped[str] = mapped_column(unique=True, nullable=False)
is_deleted: Mapped[bool] = mapped_column(default=False)
projects: Mapped[list['Project']] = relationship(
'Project',
uselist=True,
secondary='project_module',
back_populates='modules',
lazy='noload',
)

View File

@@ -1,74 +1,93 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Optional
from sqlalchemy import Column, Integer, String, ForeignKey, Sequence from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, Mapped from sqlalchemy.orm import relationship, Mapped, mapped_column
from sqlalchemy.testing.schema import mapped_column
from models import BaseModel from models.base import BaseModel
if TYPE_CHECKING: if TYPE_CHECKING:
from models import Marketplace from models import Client, BarcodeTemplate, WildberriesProduct, OzonProduct
class Product(BaseModel): class Product(BaseModel):
__tablename__ = 'products' __tablename__ = 'products'
id = Column(Integer, autoincrement=True, primary_key=True, index=True) id: Mapped[int] = mapped_column(primary_key=True)
name = Column(String, nullable=False, index=True) name: Mapped[str] = mapped_column(nullable=False, index=True)
article = Column(String, nullable=False, default='', server_default='', index=True) article: Mapped[str] = mapped_column(nullable=False, default='', server_default='', index=True)
factory_article = Column(String, nullable=False, default='', server_default='', index=True) factory_article: Mapped[str] = mapped_column(nullable=False, default='', server_default='', index=True)
client_id = Column(Integer, ForeignKey('clients.id'), nullable=False, comment='ID сделки') client_id: Mapped[int] = mapped_column(ForeignKey('clients.id'), nullable=False)
client = relationship('Client', back_populates='products') client: Mapped['Client'] = relationship('Client', back_populates='products')
barcodes = relationship('ProductBarcode', back_populates='product', cascade="all, delete-orphan") barcodes: Mapped[list['ProductBarcode']] = relationship(
'ProductBarcode',
back_populates='product',
cascade='all, delete-orphan',
)
barcode_template_id = Column(Integer, ForeignKey('barcode_templates.id'), nullable=True) barcode_template_id: Mapped[Optional[int]] = mapped_column(ForeignKey('barcode_templates.id'), nullable=True)
barcode_template = relationship('BarcodeTemplate', lazy='joined') barcode_template: Mapped['BarcodeTemplate'] = relationship('BarcodeTemplate', lazy='joined')
barcode_image = relationship('ProductBarcodeImage', back_populates='product', lazy='joined', uselist=False) barcode_image: Mapped['ProductBarcodeImage'] = relationship(
'ProductBarcodeImage',
back_populates='product',
lazy='joined',
uselist=False,
)
# Attributes # Attributes
# TODO move to another table # TODO move to another table
brand = Column(String, nullable=True, comment='Бренд') brand: Mapped[Optional[str]] = mapped_column(nullable=True, comment='Бренд')
color = Column(String, nullable=True, comment='Цвет') color: Mapped[Optional[str]] = mapped_column(nullable=True, comment='Цвет')
composition = Column(String, nullable=True, comment='Состав') composition: Mapped[Optional[str]] = mapped_column(nullable=True, comment='Состав')
size = Column(String, nullable=True, comment='Размер') size: Mapped[Optional[str]] = mapped_column(nullable=True, comment='Размер')
additional_info = Column(String, nullable=True, comment='Дополнительное поле') additional_info: Mapped[Optional[str]] = mapped_column(nullable=True, comment='Дополнительное поле')
images = relationship('ProductImage', images: Mapped[list['ProductImage']] = relationship(
'ProductImage',
back_populates='product', back_populates='product',
lazy='selectin', lazy='selectin',
cascade="all, delete-orphan") cascade='all, delete-orphan',
)
wildberries_products = relationship('WildberriesProduct', wildberries_products: Mapped[list['WildberriesProduct']] = relationship(
'WildberriesProduct',
back_populates='product', back_populates='product',
cascade="all, delete-orphan", cascade='all, delete-orphan',
uselist=True) uselist=True,
)
ozon_products = relationship('OzonProduct', ozon_products: Mapped[list['OzonProduct']] = relationship(
'OzonProduct',
back_populates='product', back_populates='product',
cascade="all, delete-orphan", cascade='all, delete-orphan',
uselist=True) uselist=True,
)
class ProductImage(BaseModel): class ProductImage(BaseModel):
__tablename__ = 'product_images' __tablename__ = 'product_images'
id = Column(Integer, autoincrement=True, primary_key=True, index=True) id: Mapped[int] = mapped_column(primary_key=True)
product_id = Column(Integer, ForeignKey('products.id'), nullable=False) product_id: Mapped[int] = mapped_column(ForeignKey('products.id'), nullable=False)
product: Mapped["Product"] = relationship(back_populates='images') product: Mapped['Product'] = relationship(back_populates='images')
image_url = Column(String, nullable=False) image_url: Mapped[str] = mapped_column(nullable=False)
class ProductBarcode(BaseModel): class ProductBarcode(BaseModel):
__tablename__ = 'product_barcodes' __tablename__ = 'product_barcodes'
product_id = Column(Integer, ForeignKey('products.id'), nullable=False, comment='ID товара', primary_key=True) product_id: Mapped[int] = mapped_column(
product: Mapped["Product"] = relationship(back_populates='barcodes') ForeignKey('products.id'),
nullable=False,
comment='ID товара',
primary_key=True,
)
product: Mapped['Product'] = relationship(back_populates='barcodes')
barcode = Column(String, nullable=False, index=True, comment='ШК товара', primary_key=True) barcode: Mapped[str] = mapped_column(nullable=False, index=True, comment='ШК товара', primary_key=True)
class ProductBarcodeImage(BaseModel): class ProductBarcodeImage(BaseModel):
__tablename__ = 'product_barcode_images' __tablename__ = 'product_barcode_images'
product_id = Column(Integer, ForeignKey('products.id'), primary_key=True, comment='ID товара') product_id: Mapped[int] = mapped_column(ForeignKey('products.id'), primary_key=True, comment='ID товара')
product: Mapped["Product"] = relationship(back_populates='barcode_image') product: Mapped['Product'] = relationship(back_populates='barcode_image')
filename = Column(String, nullable=False) filename: Mapped[str] = mapped_column(nullable=False)

View File

@@ -3,22 +3,41 @@ from typing import TYPE_CHECKING
from sqlalchemy.orm import mapped_column, Mapped, relationship from sqlalchemy.orm import mapped_column, Mapped, relationship
from models import BaseModel from models.base import BaseModel
if TYPE_CHECKING: if TYPE_CHECKING:
from board import Board from board import Board
from attribute import Attribute
from module import Module
class Project(BaseModel): class Project(BaseModel):
__tablename__ = "projects" __tablename__ = 'projects'
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(nullable=False) name: Mapped[str] = mapped_column(nullable=False)
created_at: Mapped[datetime] = mapped_column(nullable=False) created_at: Mapped[datetime] = mapped_column(nullable=False)
is_deleted: Mapped[bool] = mapped_column(default=False) is_deleted: Mapped[bool] = mapped_column(default=False)
boards: Mapped[list["Board"]] = relationship( boards: Mapped[list['Board']] = relationship(
"Board", 'Board',
back_populates="project", back_populates='project',
lazy="noload", lazy='noload',
) )
attributes: Mapped[list['Attribute']] = relationship(
'Attribute',
uselist=True,
secondary='project_attribute',
back_populates='projects',
lazy='selectin',
)
modules: Mapped[list['Module']] = relationship(
'Module',
uselist=True,
secondary='project_module',
back_populates='projects',
lazy='selectin',
)

View File

@@ -1,114 +1,137 @@
from sqlalchemy import Table, Column, Integer, ForeignKey, ForeignKeyConstraint, UniqueConstraint, String from typing import TYPE_CHECKING
from sqlalchemy import Table, Column, ForeignKey, ForeignKeyConstraint, UniqueConstraint
from sqlalchemy.orm import relationship, mapped_column, Mapped from sqlalchemy.orm import relationship, mapped_column, Mapped
from models import Product
from models.base import BaseModel from models.base import BaseModel
deal_product_service_employees = Table( if TYPE_CHECKING:
'deal_product_service_employees', from models import Card, Service, User
card_product_service_employees = Table(
'card_product_service_employees',
BaseModel.metadata, BaseModel.metadata,
Column('deal_id', primary_key=True), Column('card_id', primary_key=True),
Column('service_id', primary_key=True), Column('service_id', primary_key=True),
Column('product_id', primary_key=True), Column('product_id', primary_key=True),
Column('user_id', ForeignKey('users.id'), primary_key=True), Column('user_id', ForeignKey('users.id'), primary_key=True),
ForeignKeyConstraint( ForeignKeyConstraint(
['deal_id', 'product_id', 'service_id'], ['card_id', 'product_id', 'service_id'],
['deal_product_services.deal_id', 'deal_product_services.product_id', 'deal_product_services.service_id'] ['card_product_services.card_id', 'card_product_services.product_id', 'card_product_services.service_id']
) )
) )
deal_service_employees = Table( card_service_employees = Table(
'deal_service_employees', 'card_service_employees',
BaseModel.metadata, BaseModel.metadata,
Column('deal_id', primary_key=True), Column('card_id', primary_key=True),
Column('service_id', primary_key=True), Column('service_id', primary_key=True),
Column('user_id', ForeignKey('users.id'), primary_key=True), Column('user_id', ForeignKey('users.id'), primary_key=True),
ForeignKeyConstraint( ForeignKeyConstraint(
['deal_id', 'service_id'], ['card_id', 'service_id'],
['deal_services.deal_id', 'deal_services.service_id'] ['card_services.card_id', 'card_services.service_id']
) )
) )
class DealService(BaseModel): class CardService(BaseModel):
__tablename__ = 'deal_services' __tablename__ = 'card_services'
deal_id = Column(Integer, ForeignKey('deals.id'), card_id: Mapped[int] = mapped_column(
nullable=False, ForeignKey('cards.id'),
comment='ID Сделки', comment='ID Сделки',
primary_key=True) primary_key=True,
deal = relationship('Deal', back_populates='services') )
card: Mapped['Card'] = relationship('Card', back_populates='services')
service_id = Column(Integer, ForeignKey('services.id'), nullable=False, comment='ID Услуги', primary_key=True) service_id: Mapped[int] = mapped_column(ForeignKey('services.id'), nullable=False, comment='ID Услуги', primary_key=True)
service = relationship('Service') service: Mapped['Service'] = relationship('Service')
quantity = Column(Integer, nullable=False, comment='Кол-во услуги') quantity: Mapped[int] = mapped_column(nullable=False, comment='Кол-во услуги')
price = Column(Integer, nullable=False, server_default='0', comment='Цена услуги') price: Mapped[int] = mapped_column(nullable=False, server_default='0', comment='Цена услуги')
is_fixed_price: Mapped[bool] = mapped_column(default=False, server_default='0', comment='Фиксированная цена') is_fixed_price: Mapped[bool] = mapped_column(default=False, server_default='0', comment='Фиксированная цена')
employees = relationship('User', secondary=deal_service_employees) employees: Mapped[list['User']] = relationship('User', secondary=card_service_employees)
__table_args__ = ( __table_args__ = (
UniqueConstraint('deal_id', 'service_id', name='uix_deal_service'), UniqueConstraint('card_id', 'service_id', name='uix_card_service'),
) )
class DealProductService(BaseModel): class CardProductService(BaseModel):
__tablename__ = 'deal_product_services' __tablename__ = 'card_product_services'
deal_id = Column(Integer, primary_key=True, nullable=False, comment='ID Сделки') card_id: Mapped[int] = mapped_column(primary_key=True, nullable=False, comment='ID Сделки')
product_id = Column(Integer, primary_key=True, nullable=False, comment='ID Продукта') product_id: Mapped[int] = mapped_column(primary_key=True, nullable=False, comment='ID Продукта')
service_id = Column(Integer, ForeignKey('services.id'), primary_key=True, nullable=False, comment='ID Услуги') service_id: Mapped[int] = mapped_column(
ForeignKey('services.id'),
primary_key=True,
nullable=False,
comment='ID Услуги',
)
price = Column(Integer, nullable=False, comment='Цена услуги') price: Mapped[int] = mapped_column(nullable=False, comment='Цена услуги')
is_fixed_price: Mapped[bool] = mapped_column(default=False, server_default='0', comment='Фиксированная цена') is_fixed_price: Mapped[bool] = mapped_column(default=False, server_default='0', comment='Фиксированная цена')
deal_product = relationship('DealProduct', card_product: Mapped['CardProduct'] = relationship(
'CardProduct',
back_populates='services', back_populates='services',
primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, " primaryjoin="and_(CardProductService.card_id == CardProduct.card_id, "
"DealProductService.product_id == DealProduct.product_id)", "CardProductService.product_id == CardProduct.product_id)",
foreign_keys=[deal_id, product_id]) foreign_keys=[card_id, product_id],
service = relationship('Service',
foreign_keys=[service_id],
lazy='joined'
) )
employees = relationship('User',
secondary=deal_product_service_employees, service: Mapped['Service'] = relationship(
'Service',
foreign_keys=[service_id],
lazy='joined',
)
employees: Mapped[list['User']] = relationship(
'User',
secondary=card_product_service_employees,
) )
__table_args__ = ( __table_args__ = (
ForeignKeyConstraint( ForeignKeyConstraint(
['deal_id', 'product_id'], ['card_id', 'product_id'],
['deal_products.deal_id', 'deal_products.product_id'] ['card_products.card_id', 'card_products.product_id']
), ),
) )
class DealProduct(BaseModel): class CardProduct(BaseModel):
__tablename__ = 'deal_products' __tablename__ = 'card_products'
deal_id = Column(Integer, ForeignKey('deals.id'), primary_key=True, nullable=False, comment='ID Сделки') card_id: Mapped[int] = mapped_column(ForeignKey('cards.id'), primary_key=True, nullable=False, comment='ID карточки')
product_id = Column(Integer, ForeignKey('products.id'), primary_key=True, nullable=False, comment='ID Продукта') product_id: Mapped[int] = mapped_column(
quantity = Column(Integer, nullable=False, comment='Кол-во продукта') ForeignKey('products.id'),
comment = Column(String, nullable=False, server_default='', comment='Комментарий к товару') primary_key=True,
nullable=False,
comment='ID Продукта',
)
quantity: Mapped[int] = mapped_column(nullable=False, comment='Кол-во продукта')
comment: Mapped[str] = mapped_column(nullable=False, server_default='', comment='Комментарий к товару')
deal = relationship('Deal', card: Mapped['Card'] = relationship(
'Card',
back_populates='products', back_populates='products',
foreign_keys=[deal_id]) foreign_keys=[card_id],
product = relationship( )
product: Mapped['Product'] = relationship(
'Product', 'Product',
lazy='joined', lazy='joined',
foreign_keys=[product_id], foreign_keys=[product_id],
) )
services = relationship('DealProductService', services: Mapped[list['CardProductService']] = relationship(
back_populates='deal_product', 'CardProductService',
back_populates='card_product',
cascade="all, delete-orphan", cascade="all, delete-orphan",
primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, " primaryjoin="and_(CardProductService.card_id == CardProduct.card_id, "
"DealProductService.product_id == DealProduct.product_id)", "CardProductService.product_id == CardProduct.product_id)",
foreign_keys=[DealProductService.deal_id, DealProductService.product_id], foreign_keys=[CardProductService.card_id, CardProductService.product_id],
lazy='selectin', lazy='selectin',
order_by="desc(DealProductService.service_id)" order_by="desc(CardProductService.service_id)"
) )

View File

@@ -1,4 +0,0 @@
from sqlalchemy import Sequence
from models import BaseModel

View File

@@ -1,4 +1,4 @@
from sqlalchemy import Column, Integer, String, ForeignKey, Double, asc, Table from sqlalchemy import Column, Integer, String, ForeignKey, Double, Table
from sqlalchemy.orm import relationship, mapped_column, Mapped from sqlalchemy.orm import relationship, mapped_column, Mapped
import enums.service import enums.service
@@ -14,11 +14,15 @@ services_kit_services = Table(
class Service(BaseModel): class Service(BaseModel):
__tablename__ = 'services' __tablename__ = 'services'
id: Mapped[int] = mapped_column(Integer, autoincrement=True, primary_key=True, index=True) id: Mapped[int] = mapped_column(primary_key=True)
name = Column(String, nullable=False, comment='Название услуги') name: Mapped[str] = mapped_column(nullable=False, comment='Название услуги')
category_id = Column(Integer, ForeignKey('service_categories.id'), nullable=False, comment='ID категории услуги') category_id: Mapped[int] = mapped_column(
category = relationship('ServiceCategory', lazy='joined') ForeignKey('service_categories.id'),
nullable=False,
comment='ID категории услуги',
)
category: Mapped['ServiceCategory'] = relationship('ServiceCategory', lazy='joined')
is_deleted: Mapped[bool] = mapped_column( is_deleted: Mapped[bool] = mapped_column(
nullable=False, nullable=False,
server_default='0', server_default='0',
@@ -37,15 +41,18 @@ class Service(BaseModel):
comment='Себестоимость услуги' comment='Себестоимость услуги'
) )
service_type = Column(Integer, service_type: Mapped[int] = mapped_column(
server_default=f'{enums.service.ServiceType.DEAL_SERVICE}', server_default=f'{enums.service.ServiceType.DEAL_SERVICE}',
nullable=False, nullable=False,
comment='Тип услуги') comment='Тип услуги',
price_ranges = relationship('ServicePriceRange', )
price_ranges: Mapped[list['ServicePriceRange']] = relationship(
'ServicePriceRange',
back_populates='service', back_populates='service',
lazy='selectin', lazy='selectin',
order_by="asc(ServicePriceRange.from_quantity)", order_by="asc(ServicePriceRange.from_quantity)",
cascade="all, delete-orphan") cascade="all, delete-orphan",
)
rank: Mapped[str] = mapped_column( rank: Mapped[str] = mapped_column(
nullable=False, nullable=False,
server_default='', server_default='',
@@ -55,25 +62,25 @@ class Service(BaseModel):
class ServicePriceRange(BaseModel): class ServicePriceRange(BaseModel):
__tablename__ = 'service_price_ranges' __tablename__ = 'service_price_ranges'
id = Column(Integer, autoincrement=True, primary_key=True, index=True) id: Mapped[int] = mapped_column(primary_key=True)
service_id = Column(Integer, ForeignKey('services.id'), nullable=False, comment='ID услуги') service_id: Mapped[int] = mapped_column(ForeignKey('services.id'), nullable=False, comment='ID услуги')
service = relationship('Service', back_populates='price_ranges') service: Mapped['Service'] = relationship('Service', back_populates='price_ranges')
from_quantity = Column(Integer, nullable=False, comment='От количества') from_quantity: Mapped[int] = mapped_column(nullable=False, comment='От количества')
to_quantity = Column(Integer, nullable=False, comment='До количества') to_quantity: Mapped[int] = mapped_column(nullable=False, comment='До количества')
price = Column(Double, nullable=False, comment='Цена') price: Mapped[float] = mapped_column(Double, nullable=False, comment='Цена')
class ServiceCategory(BaseModel): class ServiceCategory(BaseModel):
__tablename__ = 'service_categories' __tablename__ = 'service_categories'
id = Column(Integer, autoincrement=True, primary_key=True, index=True) id: Mapped[int] = mapped_column(primary_key=True)
name = Column(String, nullable=False) name: Mapped[str] = mapped_column(nullable=False)
is_deleted: Mapped[bool] = mapped_column( is_deleted: Mapped[bool] = mapped_column(
nullable=False, nullable=False,
server_default='0', server_default='0',
comment='Удалена ли категория' comment='Удалена ли категория'
) )
deal_service_rank: Mapped[str] = mapped_column( card_service_rank: Mapped[str] = mapped_column(
nullable=False, nullable=False,
server_default='', server_default='',
comment='Ранг услуги для сделки' comment='Ранг услуги для сделки'

View File

@@ -5,15 +5,15 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
from models import BaseModel from models import BaseModel
if TYPE_CHECKING: if TYPE_CHECKING:
from models import Deal, Product, Client from models import Card, Product, Client
class Pallet(BaseModel): class Pallet(BaseModel):
__tablename__ = 'pallets' __tablename__ = 'pallets'
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
deal_id: Mapped[int] = mapped_column(ForeignKey('deals.id')) card_id: Mapped[int] = mapped_column(ForeignKey('cards.id'))
deal: Mapped['Deal'] = relationship(back_populates='pallets') card: Mapped['Card'] = relationship(back_populates='pallets')
boxes: Mapped[list['Box']] = relationship( boxes: Mapped[list['Box']] = relationship(
back_populates='pallet', back_populates='pallet',
@@ -54,5 +54,5 @@ class Box(BaseModel):
pallet_id: Mapped[Optional[int]] = mapped_column(ForeignKey('pallets.id')) pallet_id: Mapped[Optional[int]] = mapped_column(ForeignKey('pallets.id'))
pallet: Mapped[Pallet] = relationship(back_populates='boxes') pallet: Mapped[Pallet] = relationship(back_populates='boxes')
deal_id: Mapped[Optional[int]] = mapped_column(ForeignKey('deals.id')) card_id: Mapped[Optional[int]] = mapped_column(ForeignKey('cards.id'))
deal: Mapped['Deal'] = relationship(back_populates='boxes') card: Mapped['Card'] = relationship(back_populates='boxes')

View File

@@ -1,16 +1,17 @@
from datetime import datetime
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import ForeignKey, Column, Integer, DateTime, String from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from models import BaseModel from models import BaseModel
if TYPE_CHECKING: if TYPE_CHECKING:
from models import Board from models import Board, Card, User
class DealStatus(BaseModel): class CardStatus(BaseModel):
__tablename__ = "deal_statuses" __tablename__ = 'card_statuses'
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(nullable=False) name: Mapped[str] = mapped_column(nullable=False)
@@ -19,43 +20,44 @@ class DealStatus(BaseModel):
is_deleted: Mapped[bool] = mapped_column(default=False, nullable=False) is_deleted: Mapped[bool] = mapped_column(default=False, nullable=False)
board_id: Mapped[int] = mapped_column(ForeignKey('boards.id'), nullable=False) board_id: Mapped[int] = mapped_column(ForeignKey('boards.id'), nullable=False)
board: Mapped["Board"] = relationship("Board", back_populates="deal_statuses") board: Mapped['Board'] = relationship('Board', back_populates='statuses')
class DealStatusHistory(BaseModel): class CardStatusHistory(BaseModel):
__tablename__ = 'deals_status_history' __tablename__ = 'cards_status_history'
id = Column(Integer, autoincrement=True, primary_key=True, index=True) id: Mapped[int] = mapped_column(primary_key=True)
deal_id = Column(Integer, ForeignKey('deals.id'), nullable=False, comment='ID сделки') card_id: Mapped[int] = mapped_column(ForeignKey('cards.id'), nullable=False, comment='ID карточки')
deal = relationship('Deal', back_populates='status_history') card: Mapped['Card'] = relationship('Card', back_populates='status_history')
user_id = Column(Integer, ForeignKey('users.id'), nullable=False) user_id: Mapped[int] = mapped_column(ForeignKey('users.id'), nullable=False)
user = relationship('User') user: Mapped['User'] = relationship('User')
changed_at = Column(DateTime, nullable=False, comment='Дата и время когда произошла смена статуса') changed_at: Mapped[datetime] = mapped_column(nullable=False, comment='Дата и время когда произошла смена статуса')
from_status_id: Mapped[int] = mapped_column( from_status_id: Mapped[int] = mapped_column(
ForeignKey('deal_statuses.id'), ForeignKey('card_statuses.id'),
nullable=False, nullable=False,
comment='Предыдущий статус', comment='Предыдущий статус',
) )
from_status: Mapped[DealStatus] = relationship( from_status: Mapped[CardStatus] = relationship(
'DealStatus', 'CardStatus',
foreign_keys=[from_status_id], foreign_keys=[from_status_id],
lazy='joined', lazy='joined',
) )
to_status_id: Mapped[int] = mapped_column( to_status_id: Mapped[int] = mapped_column(
ForeignKey('deal_statuses.id'), ForeignKey('card_statuses.id'),
nullable=False, nullable=False,
comment='Новый статус', comment='Новый статус',
) )
to_status: Mapped[DealStatus] = relationship( to_status: Mapped[CardStatus] = relationship(
'DealStatus', 'CardStatus',
foreign_keys=[to_status_id], foreign_keys=[to_status_id],
lazy='joined', lazy='joined',
) )
next_status_deadline = Column(DateTime, next_status_deadline: Mapped[datetime] = mapped_column(
comment='Дедлайн до которого сделку нужно перевести на следующий этап') comment='Дедлайн до которого сделку нужно перевести на следующий этап',
comment = Column(String, nullable=False, comment='Коментарий', server_default='') )
comment: Mapped[str] = mapped_column(nullable=False, comment='Комментарий', server_default='')

View File

@@ -9,7 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from models import ProductBarcode, Product, ShippingWarehouse, BaseMarketplace from models import ProductBarcode, Product, ShippingWarehouse, BaseMarketplace
from schemas.deal import ParsedProductRowSchema, ParseDealsExcelResponse, ParsedCityBreakdownSchema, \ from schemas.card import ParsedProductRowSchema, ParseCardsExcelResponse, ParsedCityBreakdownSchema, \
OptionalShippingWarehouseSchema OptionalShippingWarehouseSchema
from schemas.marketplace import MarketplaceSchema, BaseMarketplaceSchema from schemas.marketplace import MarketplaceSchema, BaseMarketplaceSchema
from schemas.product import ProductSchema from schemas.product import ProductSchema
@@ -133,7 +133,7 @@ class DealParser:
return rows return rows
async def parse(self, file_bytes: bytes) -> ParseDealsExcelResponse: async def parse(self, file_bytes: bytes) -> ParseCardsExcelResponse:
p = mock.patch('openpyxl.styles.fonts.Font.family.max', new=100) p = mock.patch('openpyxl.styles.fonts.Font.family.max', new=100)
p.start() p.start()
@@ -144,4 +144,4 @@ class DealParser:
rows = await self._parse_barcodes(ws) rows = await self._parse_barcodes(ws)
return ParseDealsExcelResponse(rows=rows, errors=self._errors) return ParseCardsExcelResponse(rows=rows, errors=self._errors)

View File

@@ -1,6 +1,6 @@
from .auth import auth_router from .auth import auth_router
from .deal import deal_router from .card import card_router
from .group import deal_group_router from .group import card_group_router
from .client import client_router from .client import client_router
from .service import service_router from .service import service_router
from .product import product_router from .product import product_router

View File

@@ -29,34 +29,34 @@ async def webhook(
@billing_router.post( @billing_router.post(
'/create-deal-bill', '/create-deal-bill',
operation_id='create_deal_bill', operation_id='create_deal_bill',
response_model=CreateDealBillResponse response_model=CreateCardBillResponse
) )
async def create_deal_bill( async def create_deal_bill(
session: SessionDependency, session: SessionDependency,
request: CreateDealBillRequest, request: CreateCardBillRequest,
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await BillingService(session).create_deal_billing(user, request) return await BillingService(session).create_card_billing(user, request)
@billing_router.post( @billing_router.post(
'/cancel-deal-bill', '/cancel-deal-bill',
operation_id='cancel_deal_bill', operation_id='cancel_deal_bill',
response_model=CancelDealBillResponse response_model=CancelCardBillResponse
) )
async def cancel_deal_billing( async def cancel_deal_billing(
session: SessionDependency, session: SessionDependency,
request: CancelDealBillRequest, request: CancelCardBillRequest,
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await BillingService(session).cancel_deal_billing(user, request) return await BillingService(session).cancel_card_billing(user, request)
@billing_router.get( @billing_router.get(
'/deal-bill-request/{deal_id}', '/deal-bill-request/{deal_id}',
response_model=GetDealBillById, response_model=GetCardBillById,
operation_id='get_deal_bill_by_id' operation_id='get_deal_bill_by_id'
) )
async def get_deal_bill_by_id( async def get_deal_bill_by_id(
deal_id: int, deal_id: int,
session: SessionDependency session: SessionDependency
): ):
return await BillingService(session).get_deal_bill_by_id(deal_id) return await BillingService(session).get_card_bill_by_id(deal_id)

View File

@@ -10,170 +10,169 @@ from backend.session import get_session
from generators.deal_pdf_generator.generator import DealTechSpecPdfGenerator from generators.deal_pdf_generator.generator import DealTechSpecPdfGenerator
from models import User from models import User
from parsers import DealParser from parsers import DealParser
from schemas.barcode import GetDealProductsBarcodesPdfRequest, GetDealProductsBarcodesPdfResponse from schemas.barcode import GetCardProductsBarcodesPdfRequest, GetCardProductsBarcodesPdfResponse
from schemas.deal import * from schemas.card import *
from services.auth import get_current_user, authorized_user, guest_user from services.auth import get_current_user, authorized_user, guest_user
from services.barcode import BarcodeService from services.barcode import BarcodeService
from services.billing import BillingService from services.billing import BillingService
from services.deal import DealService from services.card import CardsService
deal_router = APIRouter( card_router = APIRouter(
prefix='/deal', prefix='/card',
tags=['deal'], tags=['card'],
) )
# region Deal # region Card
@deal_router.post( @card_router.post(
'/delete', '/delete',
response_model=DealDeleteResponse, response_model=CardDeleteResponse,
operation_id='deleteDeal', operation_id='deleteCard',
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def delete( async def delete(
request: DealDeleteRequest, request: CardDeleteRequest,
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)]
): ):
return await DealService(session).delete(request) return await CardsService(session).delete(request)
@deal_router.post( @card_router.post(
'/complete', '/complete',
response_model=DealCompleteResponse, response_model=CardCompleteResponse,
operation_id='completeDeal', operation_id='completeCard',
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def complete( async def complete(
request: DealCompleteRequest, request: CardCompleteRequest,
session: SessionDependency, session: SessionDependency,
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).complete(user, request) return await CardsService(session).complete(user, request)
@deal_router.post( @card_router.post(
'/quickCreate', '/quickCreate',
response_model=DealQuickCreateResponse, response_model=CardQuickCreateResponse,
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def quick_create( async def quick_create(
request: DealQuickCreateRequest, request: CardQuickCreateRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: Annotated[User, Depends(get_current_user)] user: Annotated[User, Depends(get_current_user)]
): ):
return await DealService(session).quick_create(request, user) return await CardsService(session).quick_create(request, user)
@deal_router.post( @card_router.post(
'/changeStatus', '/changeStatus',
response_model=DealChangeStatusResponse, response_model=CardChangeStatusResponse,
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def change_status( async def change_status(
request: DealChangeStatusRequest, request: CardChangeStatusRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: Annotated[User, Depends(get_current_user)] user: Annotated[User, Depends(get_current_user)]
): ):
return await DealService(session).change_status_manual(request, user) return await CardsService(session).change_status_manual(request, user)
@deal_router.get( @card_router.get(
'/summaries', '/summaries',
response_model=DealSummaryResponse, response_model=CardSummaryResponse,
operation_id='getDealSummaries', operation_id='getCardSummaries',
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def get_summary( async def get_summary(
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
full: Optional[bool] full: Optional[bool]
): ):
return await DealService(session).get_summary(full) return await CardsService(session).get_summary(full)
@deal_router.post( @card_router.post(
'/summaries/reorder', '/summaries/reorder',
response_model=DealSummaryResponse, response_model=CardSummaryResponse,
operation_id='reorderDealSummaries', operation_id='reorderCardSummaries',
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def reorder( async def reorder(
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
request: DealSummaryReorderRequest, request: CardSummaryReorderRequest,
user: Annotated[User, Depends(get_current_user)] user: Annotated[User, Depends(get_current_user)]
): ):
return await DealService(session).reorder(request, user) return await CardsService(session).reorder(request, user)
@deal_router.get( @card_router.get(
'/get-all', '/get-all',
response_model=DealGetAllResponse, response_model=CardGetAllResponse,
operation_id='getAllDeals', operation_id='getAllCards',
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def get_all( async def get_all(
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)]
): ):
return await DealService(session).get_all() return await CardsService(session).get_all()
# endpoint to get deal by id # endpoint to get card by id
@deal_router.get( @card_router.get(
'/get/{deal_id}', '/get/{card_id}',
response_model=DealSchema, response_model=CardSchema,
operation_id='getDealById', operation_id='getCardById',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def get_deal_by_id( async def get_card_by_id(
deal_id: int, card_id: int,
user: CurrentUserDependency, user: CurrentUserDependency,
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)]
): ):
return await DealService(session).get_by_id(user, deal_id) return await CardsService(session).get_by_id(user, card_id)
@deal_router.post( @card_router.post(
'/update-general-info', '/update-general-info',
response_model=DealUpdateGeneralInfoResponse, response_model=CardUpdateGeneralInfoResponse,
operation_id='updateDealGeneralInfo', operation_id='updateCardGeneralInfo',
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def update_general_info( async def update_general_info(
request: DealUpdateGeneralInfoRequest, request: CardUpdateGeneralInfoRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency, user: CurrentUserDependency,
): ):
return await DealService(session).update_general_info(request, user) return await CardsService(session).update_general_info(request, user)
@deal_router.post( @card_router.post(
'/add-kit', '/add-kit',
response_model=DealAddKitResponse, response_model=CardAddKitResponse,
operation_id='add_kit_to_deal' operation_id='add_kit_to_card'
) )
async def add_kit_to_deal( async def add_kit_to_card(
session: SessionDependency, session: SessionDependency,
request: DealAddKitRequest request: CardAddKitRequest
): ):
return await DealService(session).add_kit_to_deal(request) return await CardsService(session).add_kit_to_card(request)
@deal_router.post( @card_router.post(
'/create-guest-url', '/create-guest-url',
response_model=DealCreateGuestUrlResponse, response_model=CardCreateGuestUrlResponse,
operation_id='create_deal_guest_url', operation_id='create_deal_guest_url',
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def create_guest_url( async def create_guest_url(
session: SessionDependency, session: SessionDependency,
request: DealCreateGuestUrlRequest, request: CardCreateGuestUrlRequest,
user: CurrentUserDependency user: CurrentUserDependency
): ):
return DealService(session).create_guest_url(user, request) return CardsService(session).create_guest_url(user, request)
@deal_router.get( @card_router.get(
'/billing-document/{deal_id}', '/billing-document/{deal_id}',
operation_id='get_billing_document', operation_id='get_billing_document',
# dependencies=[Depends(authorized_user)], # dependencies=[Depends(authorized_user)],
@@ -186,7 +185,7 @@ async def get_billing_document(
return Response(pdf_file.getvalue(), media_type='application/pdf') return Response(pdf_file.getvalue(), media_type='application/pdf')
@deal_router.get( @card_router.get(
'/tech-spec/{deal_id}', '/tech-spec/{deal_id}',
operation_id='get_deal_tech_spec', operation_id='get_deal_tech_spec',
# dependencies=[Depends(authorized_user)], # dependencies=[Depends(authorized_user)],
@@ -199,33 +198,33 @@ async def get_deal_tech_spec(
return Response(pdf_file.getvalue(), media_type='application/pdf') return Response(pdf_file.getvalue(), media_type='application/pdf')
@deal_router.post( @card_router.post(
'/prefill', '/prefill',
response_model=DealPrefillResponse, response_model=CardPrefillResponse,
operation_id='prefill_deal', operation_id='prefill_card',
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def post_prefill_deal( async def post_prefill_card(
session: SessionDependency, session: SessionDependency,
request: DealPrefillRequest, request: CardPrefillRequest,
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).prefill_deal(user, request) return await CardsService(session).prefill_card_products_and_services(user, request)
@deal_router.post( @card_router.post(
'/recalculate-price', '/recalculate-price',
response_model=DealRecalculatePriceResponse, response_model=CardRecalculatePriceResponse,
operation_id='recalculate_deal_price', operation_id='recalculate_card_price',
) )
async def recalculate_deal_price( async def recalculate_card_price(
session: SessionDependency, session: SessionDependency,
request: DealRecalculatePriceRequest, request: CardRecalculatePriceRequest,
): ):
return await DealService(session).recalculate_price(request) return await CardsService(session).recalculate_price(request)
@deal_router.post( @card_router.post(
'/employee', '/employee',
response_model=ManageEmployeeResponse, response_model=ManageEmployeeResponse,
operation_id='manage_employee', operation_id='manage_employee',
@@ -234,24 +233,24 @@ async def manage_employee(
session: SessionDependency, session: SessionDependency,
request: ManageEmployeeRequest, request: ManageEmployeeRequest,
): ):
return await DealService(session).manage_employee(request) return await CardsService(session).manage_employee(request)
@deal_router.get( @card_router.get(
'/employee/available/{deal_id}', '/employee/available/{card_id}',
response_model=GetAvailableEmployeesToAssignResponse, response_model=GetAvailableEmployeesToAssignResponse,
operation_id='get_available_employees_to_assign', operation_id='get_available_employees_to_assign',
) )
async def get_available_employees_to_assign( async def get_available_employees_to_assign(
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
deal_id: int, card_id: int,
): ):
return await DealService(session).get_available_employees_to_assign(deal_id) return await CardsService(session).get_available_employees_to_assign(card_id)
@deal_router.post( @card_router.post(
'/prefill/excel/parse', '/prefill/excel/parse',
response_model=ParseDealsExcelResponse, response_model=ParseCardsExcelResponse,
operation_id='parse_deals_excel', operation_id='parse_deals_excel',
) )
async def parse_deals_excel( async def parse_deals_excel(
@@ -262,221 +261,220 @@ async def parse_deals_excel(
return await DealParser(session).parse(file_bytes) return await DealParser(session).parse(file_bytes)
@deal_router.post( @card_router.post(
'/prefill/excel/create', '/prefill/excel/create',
response_model=CreateDealsFromExcelResponse, response_model=CreateCardsFromExcelResponse,
operation_id='create_deals_excel', operation_id='create_deals_excel',
) )
async def create_deals_from_excel( async def create_deals_from_excel(
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
request: CreateDealsFromExcelRequest, request: CreateCardsFromExcelRequest,
user: CurrentUserDependency, user: CurrentUserDependency,
): ):
return await DealService(session).create_deals_from_excel(request, user) return await CardsService(session).create_cards_from_excel(request, user)
# endregion # endregion
# region Deal services # region Card services
@deal_router.post( @card_router.post(
'/services/add/multiple', '/services/add/multiple',
response_model=DealAddServicesResponse, response_model=CardAddServicesResponse,
operation_id='addMultipleDealServices', operation_id='add_multiple_card_services',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def services_add( async def services_add(
request: DealAddServicesRequest, request: CardAddServicesRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
): ):
return await DealService(session).add_services(request) return await CardsService(session).add_services(request)
@deal_router.post( @card_router.post(
'/services/add', '/services/add',
response_model=DealAddServiceResponse, response_model=CardAddServiceResponse,
operation_id='addDealService', operation_id='add_card_service',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def services_add( async def services_add(
request: DealAddServiceRequest, request: CardAddServiceRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).add_service(user, request) return await CardsService(session).add_service(user, request)
@deal_router.post( @card_router.post(
'/services/update-quantity', '/services/update-quantity',
response_model=DealUpdateServiceQuantityResponse, response_model=CardUpdateServiceQuantityResponse,
operation_id='updateDealServiceQuantity', operation_id='update_card_service_quantity',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def services_update_quantity( async def services_update_quantity(
request: DealUpdateServiceQuantityRequest, request: CardUpdateServiceQuantityRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).update_service_quantity(user, request) return await CardsService(session).update_service_quantity(user, request)
@deal_router.post( @card_router.post(
'/services/update', '/services/update',
response_model=DealUpdateServiceResponse, response_model=CardUpdateServiceResponse,
operation_id='updateDealService', operation_id='update_card_service',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def services_update( async def services_update(
request: DealUpdateServiceRequest, request: CardUpdateServiceRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).update_service(user, request) return await CardsService(session).update_service(user, request)
@deal_router.post( @card_router.post(
'/services/delete', '/services/delete',
response_model=DealDeleteServiceResponse, response_model=CardDeleteServiceResponse,
operation_id='deleteDealService', operation_id='delete_card_service',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def services_delete( async def services_delete(
request: DealDeleteServiceRequest, request: CardDeleteServiceRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).delete_service(user, request) return await CardsService(session).delete_service(user, request)
@deal_router.post( @card_router.post(
'/services/delete/multiple', '/services/delete/multiple',
response_model=DealDeleteServicesResponse, response_model=CardDeleteServicesResponse,
operation_id='deleteMultipleDealServices', operation_id='delete_multiple_card_services',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def services_delete( async def services_delete(
request: DealDeleteServicesRequest, request: CardDeleteServicesRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).delete_services(user, request) return await CardsService(session).delete_services(user, request)
@deal_router.post( @card_router.post(
'/services/copy', '/services/copy',
response_model=DealServicesCopyResponse, response_model=CardServicesCopyResponse,
operation_id='copy_product_services', operation_id='copy_product_services',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def services_copy( async def services_copy(
session: SessionDependency, session: SessionDependency,
request: DealServicesCopyRequest, request: CardServicesCopyRequest,
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).copy_services(user, request) return await CardsService(session).copy_services(user, request)
# endregion # endregion
# region Deal products # region Card products
@deal_router.post( @card_router.post(
'/products/update-quantity', '/products/update-quantity',
response_model=DealUpdateProductQuantityResponse, response_model=CardUpdateProductQuantityResponse,
operation_id='updateDealProductQuantity', operation_id='update_card_product_quantity',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def products_update( async def products_update(
request: DealUpdateProductQuantityRequest, request: CardUpdateProductQuantityRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).update_product_quantity(user, request) return await CardsService(session).update_product_quantity(user, request)
@deal_router.post( @card_router.post(
'/products/add', '/products/add',
response_model=DealAddProductResponse, response_model=CardAddProductResponse,
operation_id='addDealProduct', operation_id='add_card_product',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def products_add( async def products_add(
request: DealAddProductRequest, request: CardAddProductRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).add_product(user, request) return await CardsService(session).add_product(user, request)
@deal_router.post( @card_router.post(
'/products/delete', '/products/delete',
response_model=DealDeleteProductResponse, response_model=CardDeleteProductResponse,
operation_id='deleteDealProduct', operation_id='delete_card_product',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def products_delete( async def products_delete(
request: DealDeleteProductRequest, request: CardDeleteProductRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).delete_product(user, request) return await CardsService(session).delete_product(user, request)
@deal_router.post( @card_router.post(
'/products/delete/multiple', '/products/delete/multiple',
response_model=DealDeleteProductsResponse, response_model=CardDeleteProductsResponse,
operation_id='deleteMultipleDealProducts', operation_id='delete_multiple_card_products',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def products_delete( async def products_delete(
request: DealDeleteProductsRequest, request: CardDeleteProductsRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).delete_products(user, request) return await CardsService(session).delete_products(user, request)
@deal_router.post( @card_router.post(
'/product/update', '/product/update',
response_model=DealUpdateProductResponse, response_model=CardUpdateProductResponse,
operation_id='updateDealProduct', operation_id='update_card_product',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def products_update( async def products_update(
request: DealUpdateProductRequest, request: CardUpdateProductRequest,
session: Annotated[AsyncSession, Depends(get_session)], session: Annotated[AsyncSession, Depends(get_session)],
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).update_product(user, request) return await CardsService(session).update_product(user, request)
@deal_router.post( @card_router.post(
'/product/add-kit', '/product/add-kit',
response_model=DealProductAddKitResponse, response_model=CardProductAddKitResponse,
operation_id='add_kit_to_deal_product', operation_id='add_kit_to_card_product',
dependencies=[Depends(guest_user)] dependencies=[Depends(guest_user)]
) )
async def add_kit_to_deal_product( async def add_kit_to_card_product(
session: SessionDependency, session: SessionDependency,
request: DealProductAddKitRequest, request: CardProductAddKitRequest,
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealService(session).add_kit_to_deal_product(user, request) return await CardsService(session).add_kit_to_card_product(user, request)
@deal_router.post( @card_router.post(
'/barcodes/get-pdf', '/barcodes/get-pdf',
operation_id='get_deal_products_barcodes_pdf', operation_id='get_card_products_barcodes_pdf',
response_model=GetDealProductsBarcodesPdfResponse response_model=GetCardProductsBarcodesPdfResponse
) )
async def get_deal_products_barcodes_pdf( async def get_card_products_barcodes_pdf(
request: GetDealProductsBarcodesPdfRequest, request: GetCardProductsBarcodesPdfRequest,
session: Annotated[AsyncSession, Depends(get_session)] session: Annotated[AsyncSession, Depends(get_session)]
): ):
filename, pdf_buffer = await BarcodeService(session).get_deal_barcodes_pdf(request) filename, pdf_buffer = await BarcodeService(session).get_card_barcodes_pdf(request)
pdf_buffer: BytesIO pdf_buffer: BytesIO
base64_string = base64.b64encode(pdf_buffer.read()).decode('utf-8') base64_string = base64.b64encode(pdf_buffer.read()).decode('utf-8')
return GetDealProductsBarcodesPdfResponse( return GetCardProductsBarcodesPdfResponse(
base64_string=base64_string, base64_string=base64_string,
filename=filename, filename=filename,
mime_type='application/pdf' mime_type='application/pdf'

View File

@@ -3,77 +3,77 @@ from fastapi import APIRouter, Depends
from backend.dependecies import SessionDependency, CurrentUserDependency from backend.dependecies import SessionDependency, CurrentUserDependency
from schemas.group import * from schemas.group import *
from services.auth import authorized_user from services.auth import authorized_user
from services.deal_group import DealGroupService from services.card_group import CardGroupService
deal_group_router = APIRouter( card_group_router = APIRouter(
prefix='/deal-group', prefix='/card-group',
tags=['deal-group'], tags=['card-group'],
) )
@deal_group_router.patch( @card_group_router.patch(
'/', '/',
response_model=DealGroupUpdateResponse, response_model=CardGroupUpdateResponse,
operation_id='update_deal_group', operation_id='update_card_group',
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def update_group( async def update_group(
request: DealGroupUpdateRequest, request: CardGroupUpdateRequest,
session: SessionDependency, session: SessionDependency,
): ):
return await DealGroupService(session).update_group(request) return await CardGroupService(session).update_group(request)
@deal_group_router.post( @card_group_router.post(
'/', '/',
response_model=DealCreateGroupResponse, response_model=CardCreateGroupResponse,
operation_id='create_deal_group', operation_id='create_card_group',
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def create_group( async def create_group(
request: DealCreateGroupRequest, request: CreateCardGroupRequest,
session: SessionDependency, session: SessionDependency,
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealGroupService(session).create_group(user, request) return await CardGroupService(session).create_group(user, request)
@deal_group_router.patch( @card_group_router.patch(
'/change-status', '/change-status',
response_model=DealGroupChangeStatusResponse, response_model=CardGroupChangeStatusResponse,
operation_id='change_status', operation_id='change_status',
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def change_status( async def change_status(
request: DealGroupChangeStatusRequest, request: CardGroupChangeStatusRequest,
session: SessionDependency, session: SessionDependency,
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealGroupService(session).change_group_status(user,request) return await CardGroupService(session).change_group_status(user,request)
@deal_group_router.post( @card_group_router.post(
'/deal', '/card',
response_model=DealAddToGroupResponse, response_model=CardAddToGroupResponse,
operation_id='add_deal', operation_id='add_card',
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def add_deal( async def add_card(
request: DealAddToGroupRequest, request: CardAddToGroupRequest,
session: SessionDependency, session: SessionDependency,
user: CurrentUserDependency user: CurrentUserDependency
): ):
return await DealGroupService(session).add_deal(user, request) return await CardGroupService(session).add_card(user, request)
@deal_group_router.delete( @card_group_router.delete(
'/deal', '/card',
response_model=DealRemoveFromGroupResponse, response_model=CardRemoveFromGroupResponse,
operation_id='remove_deal', operation_id='remove_card',
dependencies=[Depends(authorized_user)] dependencies=[Depends(authorized_user)]
) )
async def remove_deal( async def remove_card(
request: DealRemoveFromGroupRequest, request: CardRemoveFromGroupRequest,
session: SessionDependency, session: SessionDependency,
): ):
return await DealGroupService(session).remove_deal(request) return await CardGroupService(session).remove_card(request)

View File

@@ -15,16 +15,16 @@ shipping_router = APIRouter(
@shipping_router.post( @shipping_router.post(
'/pallet/{deal_id}', '/pallet/{card_id}',
response_model=CreatePalletResponse, response_model=CreatePalletResponse,
operation_id='create_pallet', operation_id='create_pallet',
dependencies=[Depends(authorized_user)], dependencies=[Depends(authorized_user)],
) )
async def create_pallet( async def create_pallet(
session: SessionDependency, session: SessionDependency,
deal_id: int, card_id: int,
): ):
return await ShippingService(session).create_pallet(deal_id) return await ShippingService(session).create_pallet(card_id)
@shipping_router.delete( @shipping_router.delete(

43
schemas/attribute.py Normal file
View File

@@ -0,0 +1,43 @@
import pickle
from datetime import datetime, date
from typing import Optional
from pydantic import field_validator
from schemas.base import BaseSchema
# region Entities
class AttributeTypeSchema(BaseSchema):
id: int
type: str
name: str
is_deleted: bool
class AttributeSchema(BaseSchema):
id: int
label: str
name: str
is_applicable_to_group: bool
is_nullable: bool
default_value: Optional[bool | int | float | str | date | datetime]
type: AttributeTypeSchema
@field_validator("default_value", mode="before")
def validate_default_value(cls, value: Optional[bytes]):
return pickle.loads(value) if value else None
class CardAttributeSchema(BaseSchema):
value: Optional[bool | int | float | str | date | datetime]
card_id: int
attribute: AttributeSchema
@field_validator("value", mode="before")
def validate_value(cls, value: Optional[bytes]):
return pickle.loads(value) if value else None
# endregion

View File

@@ -81,8 +81,8 @@ class GetProductBarcodePdfRequest(GetProductBarcodeRequest):
quantity: int quantity: int
class GetDealProductsBarcodesPdfRequest(BaseSchema): class GetCardProductsBarcodesPdfRequest(BaseSchema):
deal_id: int card_id: int
# endregion # endregion
@@ -131,7 +131,7 @@ class GetProductBarcodePdfResponse(BaseSchema):
mime_type: str mime_type: str
class GetDealProductsBarcodesPdfResponse(BaseSchema): class GetCardProductsBarcodesPdfResponse(BaseSchema):
base64_string: str base64_string: str
filename: str filename: str
mime_type: str mime_type: str

View File

@@ -3,14 +3,16 @@ from typing import Optional
from schemas.base import BaseSchema, OkMessageSchema from schemas.base import BaseSchema, OkMessageSchema
# region Entities # region Entities
class DealBillRequestSchema(BaseSchema): class CardBillRequestSchema(BaseSchema):
deal_id: int card_id: int
created_at: datetime.datetime created_at: datetime.datetime
paid: bool paid: bool
pdf_url: Optional[str] pdf_url: Optional[str]
invoice_number: Optional[str] invoice_number: Optional[str]
class GroupBillRequestSchema(BaseSchema): class GroupBillRequestSchema(BaseSchema):
group_id: int group_id: int
created_at: datetime.datetime created_at: datetime.datetime
@@ -18,28 +20,30 @@ class GroupBillRequestSchema(BaseSchema):
pdf_url: Optional[str] pdf_url: Optional[str]
invoice_number: Optional[str] invoice_number: Optional[str]
# endregion # endregion
# region Requests # region Requests
class CreateDealBillRequest(BaseSchema): class CreateCardBillRequest(BaseSchema):
deal_id: int card_id: int
class CancelDealBillRequest(BaseSchema): class CancelCardBillRequest(BaseSchema):
deal_id: int card_id: int
# endregion # endregion
# region Responses # region Responses
class CreateDealBillResponse(OkMessageSchema): class CreateCardBillResponse(OkMessageSchema):
pass pass
class CancelDealBillResponse(OkMessageSchema): class CancelCardBillResponse(OkMessageSchema):
pass pass
class GetDealBillById(BaseSchema): class GetCardBillById(BaseSchema):
deal_bill: DealBillRequestSchema card_bill: CardBillRequestSchema
# endregion # endregion

View File

@@ -13,7 +13,7 @@ class BaseBoardSchema(BaseSchema):
class BoardSchema(BaseBoardSchema): class BoardSchema(BaseBoardSchema):
id: int id: int
ordinal_number: int ordinal_number: int
deal_statuses: list[StatusSchema] statuses: list[StatusSchema]
project: ProjectSchema project: ProjectSchema

421
schemas/card.py Normal file
View File

@@ -0,0 +1,421 @@
from datetime import datetime, date
from typing import List, Optional, Union
from pydantic import constr
from schemas.attribute import CardAttributeSchema
from schemas.base import BaseSchema, OkMessageSchema
from schemas.billing import CardBillRequestSchema
from schemas.board import BoardSchema
from schemas.client import ClientSchema
from schemas.group import CardGroupSchema
from schemas.marketplace import BaseMarketplaceSchema
from schemas.product import ProductSchema
from schemas.service import ServiceSchema
from schemas.shipping import PalletSchema, BoxSchema
from schemas.shipping_warehouse import ShippingWarehouseSchema, BaseShippingWarehouseSchema
from schemas.status import StatusSchema, CardStatusHistorySchema
from schemas.user import UserSchema
# region Entities
class BaseSchemaWithAttributes(BaseSchema):
attributes: dict[str, Union[bool, str, int, float, date, datetime, None]] = None
def validate_attributes(cls, value):
for attr_name, attr_value in value.items():
if not isinstance(attr_value, (str, int, float, date, datetime, None)):
raise ValueError(f"Invalid type for attribute '{attr_name}': {type(attr_value)}")
return value
class CardSummary(BaseSchema):
id: int
name: str
client_name: str
created_at: datetime
status: StatusSchema
board: BoardSchema
total_price: int
rank: int
base_marketplace: Optional[BaseMarketplaceSchema] = None
total_products: int
shipment_warehouse_id: Optional[int]
shipment_warehouse_name: Optional[str]
bill_request: Optional[CardBillRequestSchema] = None
group: Optional[CardGroupSchema] = None
class CardServiceSchema(BaseSchema):
service: ServiceSchema
quantity: int
price: int
employees: List[UserSchema]
is_fixed_price: bool
class CardProductServiceSchema(BaseSchema):
service: ServiceSchema
price: int
employees: List[UserSchema]
is_fixed_price: bool
class CardProductSchema(BaseSchema):
product: ProductSchema
services: List[CardProductServiceSchema]
quantity: int
comment: str = ""
class CardEmployeesSchema(BaseSchema):
user: UserSchema
created_at: datetime
class BaseCardSchema(BaseSchema):
id: int
name: str
comment: str
created_at: datetime
status: StatusSchema
board: BoardSchema
status_history: List[CardStatusHistorySchema]
is_deleted: bool
is_completed: bool
is_locked: bool
services: List[CardServiceSchema]
products: List[CardProductSchema]
client_id: int
client: ClientSchema
shipping_warehouse: Optional[Union[ShippingWarehouseSchema, str]] = None
bill_request: Optional[CardBillRequestSchema] = None
group: Optional[CardGroupSchema] = None
manager: Optional[UserSchema] = None
pallets: List[PalletSchema] = []
boxes: List[BoxSchema] = []
employees: List[CardEmployeesSchema] = []
class CardSchema(BaseCardSchema):
attributes: list[CardAttributeSchema]
class CardGeneralInfoSchema(BaseSchemaWithAttributes):
name: str
is_deleted: bool
is_completed: bool
comment: str
shipping_warehouse: Optional[str] = None
manager: Optional[UserSchema] = None
board_id: int
status_id: int
class OptionalShippingWarehouseSchema(BaseShippingWarehouseSchema):
id: Optional[int] = None
class ParsedCityBreakdownSchema(BaseSchema):
base_marketplace: BaseMarketplaceSchema
shipping_warehouse: OptionalShippingWarehouseSchema
quantity: int
class ParsedProductRowSchema(BaseSchema):
barcode: str
products: list[ProductSchema]
breakdowns: list[ParsedCityBreakdownSchema]
class CityBreakdownFromExcelSchema(BaseSchema):
base_marketplace: BaseMarketplaceSchema
shipping_warehouse: OptionalShippingWarehouseSchema
quantity: int
class ProductFromExcelSchema(BaseSchema):
product_id: int
cities_breakdown: list[CityBreakdownFromExcelSchema]
# endregion Entities
# region Requests
class CardChangeStatusRequest(BaseSchema):
card_id: int
new_status: int
class CardCreateRequest(BaseSchema):
name: str
status_id: int
class CardQuickCreateRequest(BaseSchema):
name: constr(strip_whitespace=True)
client_name: constr(strip_whitespace=True)
comment: str
acceptance_date: datetime
shipping_warehouse: constr(strip_whitespace=True)
base_marketplace: BaseMarketplaceSchema
status_id: int
class CardSummaryRequest(BaseSchema):
pass
class CardAddServicesRequest(BaseSchema):
card_id: int
services: list[CardServiceSchema]
class CardUpdateServiceQuantityRequest(BaseSchema):
card_id: int
service_id: int
quantity: int
class CardUpdateServiceRequest(BaseSchema):
card_id: int
service: CardServiceSchema
class CardAddServiceRequest(BaseSchema):
card_id: int
service_id: int
quantity: int
price: int
class CardDeleteServiceRequest(BaseSchema):
card_id: int
service_id: int
class CardDeleteServicesRequest(BaseSchema):
card_id: int
service_ids: List[int]
class CardUpdateProductQuantityRequest(BaseSchema):
card_id: int
product_id: int
quantity: int
class CardAddProductRequest(BaseSchema):
card_id: int
product: CardProductSchema
class CardDeleteProductRequest(BaseSchema):
card_id: int
product_id: int
class CardDeleteProductsRequest(BaseSchema):
card_id: int
product_ids: List[int]
class CardUpdateGeneralInfoRequest(BaseSchema):
card_id: int
data: CardGeneralInfoSchema
class CardSummaryReorderRequest(BaseSchema):
card_id: int
status_id: int
index: int
deadline: datetime | None = None
comment: str | None = None
class CardDeleteRequest(BaseSchema):
card_id: int
class CardUpdateProductRequest(BaseSchema):
card_id: int
product: CardProductSchema
class CardServicesCopyRequest(BaseSchema):
card_id: int
source_product_id: int
destination_product_ids: List[int]
class CardProductAddKitRequest(BaseSchema):
card_id: int
product_id: int
kit_id: int
class CardAddKitRequest(BaseSchema):
card_id: int
kit_id: int
class CardCreateGuestUrlRequest(BaseSchema):
card_id: int
class CardCompleteRequest(BaseSchema):
card_id: int
class CardPrefillRequest(BaseSchema):
old_card_id: int
new_card_id: int
class CardRecalculatePriceRequest(BaseSchema):
card_id: int
class ManageEmployeeRequest(BaseSchema):
card_id: int
user_id: int
is_assign: bool
class CreateCardsFromExcelRequest(BaseSchema):
client_id: int
status_id: int
products: list[ProductFromExcelSchema]
# endregion Requests
# region Responses
class CardUpdateProductQuantityResponse(OkMessageSchema):
pass
class CardDeleteServicesResponse(OkMessageSchema):
pass
class CardGetAllResponse(BaseSchema):
cards: List[CardSchema]
class CardChangeStatusResponse(BaseSchema):
ok: bool
class CardCreateResponse(BaseSchema):
ok: bool
class CardQuickCreateResponse(BaseSchema):
card_id: int
class CardSummaryResponse(BaseSchema):
summaries: List[CardSummary]
class CardAddServicesResponse(BaseSchema):
ok: bool
message: str
class CardUpdateServiceQuantityResponse(BaseSchema):
ok: bool
message: str
class CardUpdateServiceResponse(OkMessageSchema):
pass
class CardAddServiceResponse(OkMessageSchema):
pass
class CardDeleteServiceResponse(OkMessageSchema):
pass
class CardDeleteProductResponse(OkMessageSchema):
pass
class CardDeleteProductsResponse(OkMessageSchema):
pass
class CardAddProductResponse(OkMessageSchema):
pass
class CardUpdateGeneralInfoResponse(OkMessageSchema):
pass
class CardSummaryReorderResponse(OkMessageSchema):
pass
class CardDeleteResponse(OkMessageSchema):
pass
class CardUpdateProductResponse(OkMessageSchema):
pass
class CardServicesCopyResponse(OkMessageSchema):
pass
class CardProductAddKitResponse(OkMessageSchema):
pass
class CardAddKitResponse(OkMessageSchema):
pass
class CardCreateGuestUrlResponse(OkMessageSchema):
url: str
class CardCompleteResponse(OkMessageSchema):
pass
class CardPrefillResponse(OkMessageSchema):
pass
class CardRecalculatePriceResponse(OkMessageSchema):
pass
class ManageEmployeeResponse(OkMessageSchema):
pass
class GetAvailableEmployeesToAssignResponse(BaseSchema):
employees: list[UserSchema]
class ParseCardsExcelResponse(BaseSchema):
rows: list[ParsedProductRowSchema]
errors: list[str]
class CreateCardsFromExcelResponse(OkMessageSchema):
pass
# endregion Responses

View File

@@ -1,420 +0,0 @@
import datetime
from typing import List, Optional, Union
from pydantic import constr
from schemas.base import BaseSchema, OkMessageSchema
from schemas.billing import DealBillRequestSchema
from schemas.board import BoardSchema
from schemas.client import ClientSchema
from schemas.group import DealGroupSchema
from schemas.marketplace import BaseMarketplaceSchema
from schemas.product import ProductSchema
from schemas.service import ServiceSchema
from schemas.shipping import PalletSchema, BoxSchema
from schemas.shipping_warehouse import ShippingWarehouseSchema, BaseShippingWarehouseSchema
from schemas.status import StatusSchema, DealStatusHistorySchema
from schemas.user import UserSchema
# region Entities
class FastDeal(BaseSchema):
name: str
client: ClientSchema
comment: str
acceptance_date: datetime.datetime
class DealSummary(BaseSchema):
id: int
name: str
client_name: str
created_at: datetime.datetime
status: StatusSchema
board: BoardSchema
total_price: int
rank: int
base_marketplace: Optional[BaseMarketplaceSchema] = None
total_products: int
shipment_warehouse_id: Optional[int]
shipment_warehouse_name: Optional[str]
delivery_date: Optional[datetime.datetime] = None
receiving_slot_date: Optional[datetime.datetime] = None
bill_request: Optional[DealBillRequestSchema] = None
group: Optional[DealGroupSchema] = None
class DealServiceSchema(BaseSchema):
service: ServiceSchema
quantity: int
price: int
employees: List[UserSchema]
is_fixed_price: bool
class DealProductServiceSchema(BaseSchema):
service: ServiceSchema
price: int
employees: List[UserSchema]
is_fixed_price: bool
class DealProductSchema(BaseSchema):
product: ProductSchema
services: List[DealProductServiceSchema]
quantity: int
comment: str = ""
class DealEmployeesSchema(BaseSchema):
user: UserSchema
created_at: datetime.datetime
class DealSchema(BaseSchema):
id: int
name: str
client_id: int
created_at: datetime.datetime
status: StatusSchema
board: BoardSchema
services: List[DealServiceSchema]
products: List[DealProductSchema]
status_history: List[DealStatusHistorySchema]
is_deleted: bool
is_completed: bool
is_locked: bool
is_accounted: bool
client: ClientSchema
comment: str
shipping_warehouse: Optional[Union[ShippingWarehouseSchema, str]] = None
bill_request: Optional[DealBillRequestSchema] = None
group: Optional[DealGroupSchema] = None
manager: Optional[UserSchema] = None
pallets: List[PalletSchema] = []
boxes: List[BoxSchema] = []
employees: List[DealEmployeesSchema] = []
delivery_date: Optional[datetime.datetime] = None
receiving_slot_date: Optional[datetime.datetime] = None
class DealGeneralInfoSchema(BaseSchema):
name: str
is_deleted: bool
is_completed: bool
is_accounted: bool
comment: str
shipping_warehouse: Optional[str] = None
delivery_date: Optional[datetime.datetime] = None
receiving_slot_date: Optional[datetime.datetime] = None
manager: Optional[UserSchema] = None
board: BoardSchema
status: StatusSchema
class OptionalShippingWarehouseSchema(BaseShippingWarehouseSchema):
id: Optional[int] = None
class ParsedCityBreakdownSchema(BaseSchema):
base_marketplace: BaseMarketplaceSchema
shipping_warehouse: OptionalShippingWarehouseSchema
quantity: int
class ParsedProductRowSchema(BaseSchema):
barcode: str
products: list[ProductSchema]
breakdowns: list[ParsedCityBreakdownSchema]
class CityBreakdownFromExcelSchema(BaseSchema):
base_marketplace: BaseMarketplaceSchema
shipping_warehouse: OptionalShippingWarehouseSchema
quantity: int
class ProductFromExcelSchema(BaseSchema):
product_id: int
cities_breakdown: list[CityBreakdownFromExcelSchema]
# endregion Entities
# region Requests
class DealChangeStatusRequest(BaseSchema):
deal_id: int
new_status: int
class DealCreateRequest(BaseSchema):
name: str
status_id: int
class DealQuickCreateRequest(BaseSchema):
name: constr(strip_whitespace=True)
client_name: constr(strip_whitespace=True)
comment: str
acceptance_date: datetime.datetime
shipping_warehouse: constr(strip_whitespace=True)
base_marketplace: BaseMarketplaceSchema
status_id: int
class DealSummaryRequest(BaseSchema):
pass
class DealAddServicesRequest(BaseSchema):
deal_id: int
services: list[DealServiceSchema]
class DealUpdateServiceQuantityRequest(BaseSchema):
deal_id: int
service_id: int
quantity: int
class DealUpdateServiceRequest(BaseSchema):
deal_id: int
service: DealServiceSchema
class DealAddServiceRequest(BaseSchema):
deal_id: int
service_id: int
quantity: int
price: int
class DealDeleteServiceRequest(BaseSchema):
deal_id: int
service_id: int
class DealDeleteServicesRequest(BaseSchema):
deal_id: int
service_ids: List[int]
class DealUpdateProductQuantityRequest(BaseSchema):
deal_id: int
product_id: int
quantity: int
class DealAddProductRequest(BaseSchema):
deal_id: int
product: DealProductSchema
class DealDeleteProductRequest(BaseSchema):
deal_id: int
product_id: int
class DealDeleteProductsRequest(BaseSchema):
deal_id: int
product_ids: List[int]
class DealUpdateGeneralInfoRequest(BaseSchema):
deal_id: int
data: DealGeneralInfoSchema
class DealSummaryReorderRequest(BaseSchema):
deal_id: int
status_id: int
index: int
deadline: datetime.datetime | None = None
comment: str | None = None
class DealDeleteRequest(BaseSchema):
deal_id: int
class DealUpdateProductRequest(BaseSchema):
deal_id: int
product: DealProductSchema
class DealServicesCopyRequest(BaseSchema):
deal_id: int
source_product_id: int
destination_product_ids: List[int]
class DealProductAddKitRequest(BaseSchema):
deal_id: int
product_id: int
kit_id: int
class DealAddKitRequest(BaseSchema):
deal_id: int
kit_id: int
class DealCreateGuestUrlRequest(BaseSchema):
deal_id: int
class DealCompleteRequest(BaseSchema):
deal_id: int
class DealPrefillRequest(BaseSchema):
old_deal_id: int
new_deal_id: int
class DealRecalculatePriceRequest(BaseSchema):
deal_id: int
class ManageEmployeeRequest(BaseSchema):
deal_id: int
user_id: int
is_assign: bool
class CreateDealsFromExcelRequest(BaseSchema):
client_id: int
status_id: int
products: list[ProductFromExcelSchema]
# endregion Requests
# region Responses
class DealUpdateProductQuantityResponse(OkMessageSchema):
pass
class DealDeleteServicesResponse(OkMessageSchema):
pass
class DealGetAllResponse(BaseSchema):
deals: List[DealSchema]
class DealChangeStatusResponse(BaseSchema):
ok: bool
class DealCreateResponse(BaseSchema):
ok: bool
class DealQuickCreateResponse(BaseSchema):
deal_id: int
class DealSummaryResponse(BaseSchema):
summaries: List[DealSummary]
class DealAddServicesResponse(BaseSchema):
ok: bool
message: str
class DealUpdateServiceQuantityResponse(BaseSchema):
ok: bool
message: str
class DealUpdateServiceResponse(OkMessageSchema):
pass
class DealAddServiceResponse(OkMessageSchema):
pass
class DealDeleteServiceResponse(OkMessageSchema):
pass
class DealDeleteProductResponse(OkMessageSchema):
pass
class DealDeleteProductsResponse(OkMessageSchema):
pass
class DealAddProductResponse(OkMessageSchema):
pass
class DealUpdateGeneralInfoResponse(OkMessageSchema):
pass
class DealSummaryReorderResponse(OkMessageSchema):
pass
class DealDeleteResponse(OkMessageSchema):
pass
class DealUpdateProductResponse(OkMessageSchema):
pass
class DealServicesCopyResponse(OkMessageSchema):
pass
class DealProductAddKitResponse(OkMessageSchema):
pass
class DealAddKitResponse(OkMessageSchema):
pass
class DealCreateGuestUrlResponse(OkMessageSchema):
url: str
class DealCompleteResponse(OkMessageSchema):
pass
class DealPrefillResponse(OkMessageSchema):
pass
class DealRecalculatePriceResponse(OkMessageSchema):
pass
class ManageEmployeeResponse(OkMessageSchema):
pass
class GetAvailableEmployeesToAssignResponse(BaseSchema):
employees: list[UserSchema]
class ParseDealsExcelResponse(BaseSchema):
rows: list[ParsedProductRowSchema]
errors: list[str]
class CreateDealsFromExcelResponse(OkMessageSchema):
pass
# endregion Responses

View File

@@ -6,7 +6,7 @@ from schemas.billing import GroupBillRequestSchema
# region Entities # region Entities
class DealGroupSchema(BaseSchema): class CardGroupSchema(BaseSchema):
id: int id: int
name: Optional[str] = None name: Optional[str] = None
lexorank: str lexorank: str
@@ -17,50 +17,50 @@ class DealGroupSchema(BaseSchema):
# region Requests # region Requests
class DealGroupUpdateRequest(BaseSchema): class CardGroupUpdateRequest(BaseSchema):
data: DealGroupSchema data: CardGroupSchema
class DealCreateGroupRequest(BaseSchema): class CreateCardGroupRequest(BaseSchema):
dragging_deal_id: int dragging_card_id: int
hovered_deal_id: int hovered_card_id: int
class DealGroupChangeStatusRequest(BaseSchema): class CardGroupChangeStatusRequest(BaseSchema):
group_id: int group_id: int
new_status: int new_status: int
class DealAddToGroupRequest(BaseSchema): class CardAddToGroupRequest(BaseSchema):
deal_id: int card_id: int
group_id: int group_id: int
class DealRemoveFromGroupRequest(BaseSchema): class CardRemoveFromGroupRequest(BaseSchema):
deal_id: int card_id: int
# endregion # endregion
# region Responses # region Responses
class DealCreateGroupResponse(OkMessageSchema): class CardCreateGroupResponse(OkMessageSchema):
pass pass
class DealGroupUpdateResponse(OkMessageSchema): class CardGroupUpdateResponse(OkMessageSchema):
pass pass
class DealGroupChangeStatusResponse(OkMessageSchema): class CardGroupChangeStatusResponse(OkMessageSchema):
pass pass
class DealAddToGroupResponse(OkMessageSchema): class CardAddToGroupResponse(OkMessageSchema):
pass pass
class DealRemoveFromGroupResponse(OkMessageSchema): class CardRemoveFromGroupResponse(OkMessageSchema):
pass pass
# endregion # endregion

11
schemas/module.py Normal file
View File

@@ -0,0 +1,11 @@
from schemas.base import BaseSchema
# region Entities
class ModuleSchema(BaseSchema):
id: int
key: str
is_deleted: bool
# endregion

View File

@@ -1,4 +1,6 @@
from schemas.attribute import AttributeSchema
from schemas.base import BaseSchema, OkMessageSchema from schemas.base import BaseSchema, OkMessageSchema
from schemas.module import ModuleSchema
# region Entities # region Entities
@@ -10,9 +12,11 @@ class BaseProjectSchema(BaseSchema):
class ProjectSchema(BaseProjectSchema): class ProjectSchema(BaseProjectSchema):
id: int id: int
attributes: list[AttributeSchema]
modules: list[ModuleSchema]
class ProjectSchemaWithCount(ProjectSchema): class FullProjectSchema(ProjectSchema):
boards_count: int boards_count: int
@@ -27,13 +31,14 @@ class CreateProjectRequest(BaseSchema):
class UpdateProjectRequest(BaseSchema): class UpdateProjectRequest(BaseSchema):
project: ProjectSchema project: ProjectSchema
# endregion # endregion
# region Responses # region Responses
class GetProjectsResponse(BaseSchema): class GetProjectsResponse(BaseSchema):
projects: list[ProjectSchemaWithCount] projects: list[FullProjectSchema]
class CreateProjectResponse(OkMessageSchema): class CreateProjectResponse(OkMessageSchema):

View File

@@ -16,7 +16,7 @@ class ServicePriceRangeSchema(BaseSchema):
class ServiceCategorySchema(BaseSchema): class ServiceCategorySchema(BaseSchema):
id: int id: int
name: str name: str
deal_service_rank: str card_service_rank: str
product_service_rank: str product_service_rank: str

View File

@@ -16,7 +16,7 @@ class BoxSchema(BaseSchema):
quantity: int quantity: int
product: Optional[ProductSchema] product: Optional[ProductSchema]
pallet_id: Optional[int] pallet_id: Optional[int]
deal_id: Optional[int] card_id: Optional[int]
class ShippingProductSchema(BaseSchema): class ShippingProductSchema(BaseSchema):
@@ -44,8 +44,8 @@ class CreateBoxInPalletSchema(BaseSchema):
pallet_id: Optional[int] pallet_id: Optional[int]
class CreateBoxInDealSchema(BaseSchema): class CreateBoxInCardSchema(BaseSchema):
deal_id: Optional[int] card_id: Optional[int]
class UpdateBoxSchema(ProductAndQuantitySchema): class UpdateBoxSchema(ProductAndQuantitySchema):
@@ -61,7 +61,7 @@ class UpdateShippingProductRequest(BaseSchema):
class UpdateBoxRequest(BaseSchema): class UpdateBoxRequest(BaseSchema):
data: CreateBoxInDealSchema | CreateBoxInPalletSchema | UpdateBoxSchema data: CreateBoxInCardSchema | CreateBoxInPalletSchema | UpdateBoxSchema
# endregion # endregion

View File

@@ -11,7 +11,7 @@ class ProfitChartDataItem(BaseSchema):
revenue: float revenue: float
profit: float profit: float
expenses: float expenses: float
deals_count: int cards_count: int
class ProfitTableDataItem(BaseSchema): class ProfitTableDataItem(BaseSchema):
@@ -19,7 +19,7 @@ class ProfitTableDataItem(BaseSchema):
revenue: float revenue: float
profit: float profit: float
expenses: Optional[float] = 0 expenses: Optional[float] = 0
deals_count: int cards_count: int
# endregion # endregion
@@ -31,7 +31,7 @@ class CommonProfitFilters(BaseSchema):
base_marketplace_key: str base_marketplace_key: str
project_id: int project_id: int
board_id: int board_id: int
deal_status_id: int card_status_id: int
manager_id: int manager_id: int
expense_tag_id: int expense_tag_id: int
income_tag_id: int income_tag_id: int

View File

@@ -17,7 +17,7 @@ class StatusSchema(BaseStatusSchema):
is_deleted: bool = False is_deleted: bool = False
class DealStatusHistorySchema(BaseSchema): class CardStatusHistorySchema(BaseSchema):
user: UserSchema user: UserSchema
changed_at: datetime changed_at: datetime
from_status: StatusSchema from_status: StatusSchema

View File

@@ -8,7 +8,7 @@ from barcodes.attributes import AttributeWriterFactory
from barcodes.generator.default_generator import DefaultBarcodeGenerator from barcodes.generator.default_generator import DefaultBarcodeGenerator
from barcodes.images_uploader import BarcodeImagesUploader from barcodes.images_uploader import BarcodeImagesUploader
from models import BarcodeTemplate, BarcodeTemplateAttribute, barcode_template_attribute_link, Product, \ from models import BarcodeTemplate, BarcodeTemplateAttribute, barcode_template_attribute_link, Product, \
BarcodeTemplateAdditionalField, BarcodeTemplateSize, Deal, DealProduct BarcodeTemplateAdditionalField, BarcodeTemplateSize, Card, CardProduct
from schemas.barcode import * from schemas.barcode import *
from services.base import BaseService from services.base import BaseService
@@ -113,44 +113,44 @@ class BarcodeService(BaseService):
) )
return filename, pdf_buffer return filename, pdf_buffer
async def get_deal_barcodes_pdf(self, request: GetDealProductsBarcodesPdfRequest) -> Tuple[str, BytesIO]: async def get_card_barcodes_pdf(self, request: GetCardProductsBarcodesPdfRequest) -> Tuple[str, BytesIO]:
stmt = ( stmt = (
select(Deal) select(Card)
.options( .options(
selectinload(Deal.products).joinedload(DealProduct.product).selectinload(Product.client), selectinload(Card.products).joinedload(CardProduct.product).selectinload(Product.client),
selectinload(Deal.products).joinedload(DealProduct.product).joinedload(Product.barcodes), selectinload(Card.products).joinedload(CardProduct.product).joinedload(Product.barcodes),
) )
.filter(Deal.id == request.deal_id) .filter(Card.id == request.card_id)
) )
query = await self.session.execute(stmt) query = await self.session.execute(stmt)
deal: Deal = query.scalar() card: Card = query.scalar()
if not deal: if not card:
raise ValueError('Сделка не найдена') raise ValueError('Сделка не найдена')
uploader = BarcodeImagesUploader() uploader = BarcodeImagesUploader()
barcodes_data: List[Dict[str, str | Product | BarcodeTemplate | int]] = [] barcodes_data: List[Dict[str, str | Product | BarcodeTemplate | int]] = []
for deal_product in deal.products: for card_product in card.products:
if deal_product.product.barcode_image: if card_product.product.barcode_image:
barcodes_data.append({ barcodes_data.append({
"barcode_image_url": uploader.get_abs_path(deal_product.product.barcode_image.filename), "barcode_image_url": uploader.get_abs_path(card_product.product.barcode_image.filename),
"num_duplicates": deal_product.quantity "num_duplicates": card_product.quantity
}) })
else: else:
product_request = GetProductBarcodeRequest( product_request = GetProductBarcodeRequest(
product_id=deal_product.product_id, product_id=card_product.product_id,
barcode="", barcode="",
barcode_template_id=deal_product.product.barcode_template_id, barcode_template_id=card_product.product.barcode_template_id,
) )
barcode_template = await self._get_barcode_template(product_request, deal_product.product) barcode_template = await self._get_barcode_template(product_request, card_product.product)
barcodes_data.append({ barcodes_data.append({
"barcode": deal_product.product.barcodes[0].barcode, "barcode": card_product.product.barcodes[0].barcode,
"product": deal_product.product, "product": card_product.product,
"template": barcode_template, "template": barcode_template,
"num_duplicates": deal_product.quantity "num_duplicates": card_product.quantity
}) })
default_generator = DefaultBarcodeGenerator() default_generator = DefaultBarcodeGenerator()
filename = f'{deal.id}_deal_barcodes.pdf' filename = f'{card.id}_deal_barcodes.pdf'
pdf_buffer = default_generator.generate(barcodes_data) pdf_buffer = default_generator.generate(barcodes_data)
return filename, pdf_buffer return filename, pdf_buffer

View File

@@ -1,4 +1,3 @@
import logging
from io import BytesIO from io import BytesIO
from typing import List from typing import List
from uuid import uuid4 from uuid import uuid4
@@ -16,24 +15,24 @@ from constants import MONTHS, ENV
from external.billing import BillingClient, CreateBillingRequestValue, CreateBillRequestSchema, CreateBillRequestItems, \ from external.billing import BillingClient, CreateBillingRequestValue, CreateBillRequestSchema, CreateBillRequestItems, \
BillStatusUpdateRequest, NotificationChannel, NotifyReceivedBillRequestSchema, DeleteBillRequestSchema, \ BillStatusUpdateRequest, NotificationChannel, NotifyReceivedBillRequestSchema, DeleteBillRequestSchema, \
ProductBillingDocumentPdf, ServiceBillingDocumentPdf ProductBillingDocumentPdf, ServiceBillingDocumentPdf
from models import DealBillRequest, Deal, DealProduct, DealService as DealServiceModel, DealProductService, DealGroup, \ from models import CardBillRequest, Card, CardProduct, CardService as CardServiceModel, CardGroup, \
GroupBillRequest GroupBillRequest
from schemas.billing import * from schemas.billing import *
from services.base import BaseService from services.base import BaseService
from services.deal import DealService from services.card import CardsService
from utils.list_utils import to_locale_number from utils.list_utils import to_locale_number
class BillingService(BaseService): class BillingService(BaseService):
async def _process_deal_update_details( async def _process_card_update_details(
self, self,
request: BillStatusUpdateRequest, request: BillStatusUpdateRequest,
): ):
deal_bill_request = await self._get_deal_bill_by_id(request.listener_transaction_id) bill_request = await self._get_card_bill_by_id(request.listener_transaction_id)
if not deal_bill_request: if not bill_request:
return return
deal_bill_request.pdf_url = request.info.pdf_url bill_request.pdf_url = request.info.pdf_url
deal_bill_request.invoice_number = request.info.invoice_number bill_request.invoice_number = request.info.invoice_number
async def _process_group_update_details( async def _process_group_update_details(
self, self,
@@ -63,19 +62,19 @@ class BillingService(BaseService):
if not response.ok: if not response.ok:
return return
if type(request.listener_transaction_id) is int: if type(request.listener_transaction_id) is int:
await self._process_deal_update_details(request) await self._process_card_update_details(request)
else: else:
await self._process_group_update_details(request) await self._process_group_update_details(request)
await self.session.commit() await self.session.commit()
async def _process_deal_update_verification( async def _process_card_update_verification(
self, self,
request: BillStatusUpdateRequest request: BillStatusUpdateRequest
): ):
deal_bill_request = await self._get_deal_bill_by_id(request.listener_transaction_id) card_bill_request = await self._get_card_bill_by_id(request.listener_transaction_id)
if not deal_bill_request: if not card_bill_request:
return return
deal_bill_request.paid = request.info.payed card_bill_request.paid = request.info.payed
async def _process_group_update_verification( async def _process_group_update_verification(
self, self,
@@ -104,7 +103,7 @@ class BillingService(BaseService):
if not response.ok: if not response.ok:
return return
if type(request.listener_transaction_id) is int: if type(request.listener_transaction_id) is int:
await self._process_deal_update_verification(request) await self._process_card_update_verification(request)
else: else:
await self._process_group_update_verification(request) await self._process_group_update_verification(request)
await self.session.commit() await self.session.commit()
@@ -118,37 +117,37 @@ class BillingService(BaseService):
elif request.channel == NotificationChannel.PAYMENT_VERIFICATION: elif request.channel == NotificationChannel.PAYMENT_VERIFICATION:
await self._process_update_verification(request) await self._process_update_verification(request)
async def create_deal_bill_request(self, deal: Deal): async def create_card_bill_request(self, card: Card):
deal_bill_request = DealBillRequest( card_bill_request = CardBillRequest(
deal_id=deal.id, card_id=card.id,
created_at=datetime.datetime.now() created_at=datetime.datetime.now()
) )
self.session.add(deal_bill_request) self.session.add(card_bill_request)
deal.is_locked = True card.is_locked = True
await self.session.commit() await self.session.commit()
async def create_group_bill_request(self, group: DealGroup): async def create_group_bill_request(self, group: CardGroup):
group_bill_request = GroupBillRequest( group_bill_request = GroupBillRequest(
group_id=group.id, group_id=group.id,
created_at=datetime.datetime.now() created_at=datetime.datetime.now()
) )
self.session.add(group_bill_request) self.session.add(group_bill_request)
for deal in group.deals: for card in group.cards:
deal.is_locked = True card.is_locked = True
await self.session.commit() await self.session.commit()
async def create_deal_billing(self, user, request: CreateDealBillRequest) -> CreateDealBillResponse: async def create_card_billing(self, user, request: CreateCardBillRequest) -> CreateCardBillResponse:
try: try:
deal_service = DealService(self.session) card_service = CardsService(self.session)
billing_client = BillingClient(backend.config.BILLING_API_KEY) billing_client = BillingClient(backend.config.BILLING_API_KEY)
basic_deal: Deal = await deal_service.get_by_id(user, request.deal_id, return_raw=True) basic_card: Card = await card_service.get_by_id(user, request.card_id, return_raw=True)
if basic_deal.group: if basic_card.group:
deals = await self._get_deals_by_group_id(basic_deal.group.id) cards = await self._get_cards_by_group_id(basic_card.group.id)
else: else:
deals = [basic_deal] cards = [basic_card]
(services, products, is_size_needed) = await self._get_products_for_deal(deals) (services, products, is_size_needed) = await self._get_products_for_card(cards)
services: dict[str, ServiceBillingDocumentPdf] services: dict[str, ServiceBillingDocumentPdf]
products: dict[str, ProductBillingDocumentPdf] products: dict[str, ProductBillingDocumentPdf]
is_size_needed: bool is_size_needed: bool
@@ -163,72 +162,72 @@ class BillingService(BaseService):
) )
) )
deal = basic_deal card = basic_card
listener_transaction_id = deal.id listener_transaction_id = card.id
if deal.group: if card.group:
listener_transaction_id = f"group-{basic_deal.group.id}" listener_transaction_id = f"group-{basic_card.group.id}"
inn: str = deal.client.details.inn inn: str = card.client.details.inn
create_bill_request = CreateBillRequestSchema( create_bill_request = CreateBillRequestSchema(
listener_transaction_id=listener_transaction_id, listener_transaction_id=listener_transaction_id,
payer_name=deal.client.name, payer_name=card.client.name,
payer_inn=inn.strip(), payer_inn=inn.strip(),
payer_phone=deal.client.details.phone_number, payer_phone=card.client.details.phone_number,
items=CreateBillRequestItems( items=CreateBillRequestItems(
values=billing_request_values values=billing_request_values
) )
) )
create_bill_response = await billing_client.create(create_bill_request) create_bill_response = await billing_client.create(create_bill_request)
if not create_bill_response.ok: if not create_bill_response.ok:
return CreateDealBillResponse(ok=create_bill_response.ok, message='Ошибка!') return CreateCardBillResponse(ok=create_bill_response.ok, message='Ошибка!')
if basic_deal.group: if basic_card.group:
await self.create_group_bill_request(basic_deal.group) await self.create_group_bill_request(basic_card.group)
else: else:
await self.create_deal_bill_request(basic_deal) await self.create_card_bill_request(basic_card)
return CreateDealBillResponse(ok=create_bill_response.ok, message='Заявка успешно создана!') return CreateCardBillResponse(ok=create_bill_response.ok, message='Заявка успешно создана!')
except Exception as e: except Exception as e:
return CreateDealBillResponse(ok=False, message=str(e)) return CreateCardBillResponse(ok=False, message=str(e))
async def _get_deal_bill_by_id(self, deal_id: int) -> Optional[DealBillRequest]: async def _get_card_bill_by_id(self, card_id: int) -> Optional[CardBillRequest]:
return await self.session.scalar(select(DealBillRequest).where(DealBillRequest.deal_id == deal_id)) return await self.session.scalar(select(CardBillRequest).where(CardBillRequest.card_id == card_id))
async def _get_group_bill_by_id(self, group_id: int) -> Optional[GroupBillRequest]: async def _get_group_bill_by_id(self, group_id: int) -> Optional[GroupBillRequest]:
return await self.session.scalar(select(GroupBillRequest).where(GroupBillRequest.group_id == group_id)) return await self.session.scalar(select(GroupBillRequest).where(GroupBillRequest.group_id == group_id))
async def get_deal_bill_by_id(self, deal_id: int) -> GetDealBillById: async def get_card_bill_by_id(self, card_id: int) -> GetCardBillById:
deal_bill = await self._get_deal_bill_by_id(deal_id) bill = await self._get_card_bill_by_id(card_id)
if not deal_bill: if not bill:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Deal bill was not found') raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Card bill was not found')
return GetDealBillById(deal_bill=DealBillRequestSchema.model_validate(deal_bill)) return GetCardBillById(card_bill=CardBillRequestSchema.model_validate(bill))
async def cancel_deal_billing(self, user, request: CancelDealBillRequest) -> CancelDealBillResponse: async def cancel_card_billing(self, user, request: CancelCardBillRequest) -> CancelCardBillResponse:
try: try:
deal = await self._get_deal_by_id(request.deal_id) card = await self._get_card_by_id(request.card_id)
if not deal: if not card:
return CancelDealBillResponse(ok=False, message='Сделка не найдена') return CancelCardBillResponse(ok=False, message='Сделка не найдена')
if deal.group: if card.group:
bill = await self._get_group_bill_by_id(deal.group.id) bill = await self._get_group_bill_by_id(card.group.id)
if not bill: if not bill:
return CancelDealBillResponse(ok=False, message='Заявка не найдена') return CancelCardBillResponse(ok=False, message='Заявка не найдена')
billing_client = BillingClient(backend.config.BILLING_API_KEY) billing_client = BillingClient(backend.config.BILLING_API_KEY)
response = await billing_client.delete(DeleteBillRequestSchema(listener_transaction_id=deal.group.id)) response = await billing_client.delete(DeleteBillRequestSchema(listener_transaction_id=card.group.id))
else: else:
bill = await self._get_deal_bill_by_id(request.deal_id) bill = await self._get_card_bill_by_id(request.card_id)
if not bill: if not bill:
return CancelDealBillResponse(ok=False, message='Заявка не найдена') return CancelCardBillResponse(ok=False, message='Заявка не найдена')
billing_client = BillingClient(backend.config.BILLING_API_KEY) billing_client = BillingClient(backend.config.BILLING_API_KEY)
response = await billing_client.delete(DeleteBillRequestSchema(listener_transaction_id=request.deal_id)) response = await billing_client.delete(DeleteBillRequestSchema(listener_transaction_id=request.card_id))
if not response.ok: if not response.ok:
return CancelDealBillResponse(ok=False, message='Ошибка') return CancelCardBillResponse(ok=False, message='Ошибка')
await self.session.delete(bill) await self.session.delete(bill)
await self.session.commit() await self.session.commit()
return CancelDealBillResponse(ok=True, message='Заявка успешно отозвана') return CancelCardBillResponse(ok=True, message='Заявка успешно отозвана')
except Exception as e: except Exception as e:
return CancelDealBillResponse(ok=False, message=str(e)) return CancelCardBillResponse(ok=False, message=str(e))
def _gen_key_for_service(self, service: ServiceBillingDocumentPdf) -> str: def _gen_key_for_service(self, service: ServiceBillingDocumentPdf) -> str:
return f"{service.name}-{service.price}" return f"{service.name}-{service.price}"
@@ -237,13 +236,13 @@ class BillingService(BaseService):
article = product.article if product.article else uuid4() article = product.article if product.article else uuid4()
return f"{article}-{product.size}-{product.price}" return f"{article}-{product.size}-{product.price}"
async def _get_products_for_deal(self, deals: list[Deal]) -> tuple[dict, dict, bool]: async def _get_products_for_card(self, cards: list[Card]) -> tuple[dict, dict, bool]:
services: dict[str, ServiceBillingDocumentPdf] = {} services: dict[str, ServiceBillingDocumentPdf] = {}
products: dict[str, ProductBillingDocumentPdf] = {} products: dict[str, ProductBillingDocumentPdf] = {}
is_size_needed: bool = False is_size_needed: bool = False
for deal in deals: for card in cards:
for product in deal.products: for product in card.products:
product_price = 0 product_price = 0
for service in product.services: for service in product.services:
service_data = ServiceBillingDocumentPdf( service_data = ServiceBillingDocumentPdf(
@@ -269,7 +268,7 @@ class BillingService(BaseService):
products[product_key].quantity += product_data.quantity products[product_key].quantity += product_data.quantity
else: else:
products[product_key] = product_data products[product_key] = product_data
for service in deal.services: for service in card.services:
service_data = ServiceBillingDocumentPdf( service_data = ServiceBillingDocumentPdf(
name=service.service.name, name=service.service.name,
price=service.price, price=service.price,
@@ -283,49 +282,49 @@ class BillingService(BaseService):
return services, products, is_size_needed return services, products, is_size_needed
async def _get_deal_by_id(self, deal_id: int) -> Optional[Deal]: async def _get_card_by_id(self, card_id: int) -> Optional[Card]:
deal: Deal | None = await self.session.scalar( card: Card | None = await self.session.scalar(
select(Deal) select(Card)
.where(Deal.id == deal_id) .where(Card.id == card_id)
.options( .options(
selectinload(Deal.products).selectinload(DealProduct.services), selectinload(Card.products).selectinload(CardProduct.services),
selectinload(Deal.services).selectinload(DealServiceModel.service), selectinload(Card.services).selectinload(CardServiceModel.service),
joinedload(Deal.shipping_warehouse), joinedload(Card.shipping_warehouse),
joinedload(Deal.client), joinedload(Card.client),
selectinload(Deal.group).selectinload(DealGroup.deals), selectinload(Card.group).selectinload(CardGroup.cards),
) )
) )
return deal return card
async def _get_deals_by_group_id(self, group_id: int) -> List[Deal]: async def _get_cards_by_group_id(self, group_id: int) -> List[Card]:
group: DealGroup | None = await self.session.scalar( group: CardGroup | None = await self.session.scalar(
select(DealGroup) select(CardGroup)
.where(DealGroup.id == group_id) .where(CardGroup.id == group_id)
.options( .options(
selectinload(DealGroup.deals).selectinload(Deal.products).selectinload(DealProduct.services), selectinload(CardGroup.cards).selectinload(Card.products).selectinload(CardProduct.services),
selectinload(DealGroup.deals).selectinload(Deal.services).selectinload(DealServiceModel.service), selectinload(CardGroup.cards).selectinload(Card.services).selectinload(CardServiceModel.service),
selectinload(DealGroup.deals).joinedload(Deal.shipping_warehouse), selectinload(CardGroup.cards).joinedload(Card.shipping_warehouse),
selectinload(DealGroup.deals).joinedload(Deal.client), selectinload(CardGroup.cards).joinedload(Card.client),
selectinload(DealGroup.deals).selectinload(Deal.group).selectinload(DealGroup.deals), selectinload(CardGroup.cards).selectinload(Card.group).selectinload(CardGroup.cards),
) )
) )
return group.deals if group else [] return group.cards if group else []
async def _create_billing_document_html(self, deal_id: int): async def _create_billing_document_html(self, card_id: int):
deal = await self._get_deal_by_id(deal_id) card = await self._get_card_by_id(card_id)
if not deal: if not card:
return "" return ""
if deal.group: if card.group:
deals = await self._get_deals_by_group_id(deal.group.id) cards = await self._get_cards_by_group_id(card.group.id)
else: else:
deals = [deal] cards = [card]
(services, products, is_size_needed) = await self._get_products_for_deal(deals) (services, products, is_size_needed) = await self._get_products_for_card(cards)
deal_price = sum((service.price * service.quantity for service in services.values())) price = sum((service.price * service.quantity for service in services.values()))
deal_price_words = get_string_by_number(deal_price)[0:-10] price_words = get_string_by_number(price)[0:-10]
deal_price = to_locale_number(deal_price) price = to_locale_number(price)
template = ENV.get_template("bill-of-payment.html") template = ENV.get_template("bill-of-payment.html")
now = datetime.datetime.now() now = datetime.datetime.now()
@@ -334,14 +333,14 @@ class BillingService(BaseService):
"products": products, "products": products,
"services": services, "services": services,
"is_size_needed": is_size_needed, "is_size_needed": is_size_needed,
"deal_price": deal_price, "deal_price": price,
"deal_price_words": deal_price_words, "deal_price_words": price_words,
"deal": deal, "deal": card,
"curr_date": curr_date "curr_date": curr_date
}) })
async def create_billing_document_pdf(self, deal_id) -> BytesIO: async def create_billing_document_pdf(self, card_id) -> BytesIO:
doc = await self._create_billing_document_html(deal_id) doc = await self._create_billing_document_html(card_id)
pdf_file = BytesIO() pdf_file = BytesIO()
HTML(string=doc).write_pdf(pdf_file, stylesheets=[CSS(constants.APP_PATH + '/static/css/bill-of-payment.css')]) HTML(string=doc).write_pdf(pdf_file, stylesheets=[CSS(constants.APP_PATH + '/static/css/bill-of-payment.css')])

View File

@@ -3,7 +3,7 @@ from typing import Optional
from sqlalchemy import select, and_, func from sqlalchemy import select, and_, func
from models import Board, Deal from models import Board, Card
from schemas.board import * from schemas.board import *
from services.base import BaseService from services.base import BaseService
@@ -76,12 +76,12 @@ class BoardService(BaseService):
async def _count_deals_in_progress(self, board_id: int) -> int: async def _count_deals_in_progress(self, board_id: int) -> int:
stmt = ( stmt = (
select(func.count(Deal.id)) select(func.count(Card.id))
.where( .where(
and_( and_(
Deal.board_id == board_id, Card.board_id == board_id,
Deal.is_deleted == False, Card.is_deleted == False,
Deal.is_completed == False, Card.is_completed == False,
) )
) )
) )
@@ -89,8 +89,8 @@ class BoardService(BaseService):
async def _count_deals(self, board_id: int) -> int: async def _count_deals(self, board_id: int) -> int:
stmt = ( stmt = (
select(func.count(Deal.id)) select(func.count(Card.id))
.where(Deal.board_id == board_id) .where(Card.board_id == board_id)
) )
return (await self.session.scalars(stmt)).first() return (await self.session.scalars(stmt)).first()
@@ -111,7 +111,7 @@ class BoardService(BaseService):
await self.session.delete(board) await self.session.delete(board)
else: else:
board.is_deleted = True board.is_deleted = True
for status in board.deal_statuses: for status in board.statuses:
status.is_deleted = True status.is_deleted = True
await self.session.commit() await self.session.commit()

1317
services/card.py Normal file

File diff suppressed because it is too large Load Diff

168
services/card_group.py Normal file
View File

@@ -0,0 +1,168 @@
from lexorank import lexorank
from sqlalchemy import select, insert, update, delete
from sqlalchemy.orm import selectinload
from models import CardService as DealServiceModel, User, Card, CardProduct, Product, GroupBillRequest
from models.card_group import CardGroup, card_relations
from schemas.group import *
from services.base import BaseService
from services.card import CardsService
class CardGroupService(BaseService):
async def get_cards_by_group_id(self, group_id) -> list[Card]:
group: CardGroup | None = await self.session.scalar(
select(CardGroup)
.where(CardGroup.id == group_id)
.options(
selectinload(CardGroup.cards).selectinload(Card.products).selectinload(CardProduct.services),
selectinload(CardGroup.cards).selectinload(Card.products)
.selectinload(CardProduct.product).selectinload(Product.barcodes),
selectinload(CardGroup.cards).selectinload(Card.services).selectinload(DealServiceModel.service),
selectinload(CardGroup.cards).selectinload(Card.status_history),
selectinload(CardGroup.cards).selectinload(Card.group).selectinload(CardGroup.cards),
selectinload(CardGroup.cards).joinedload(Card.client),
selectinload(CardGroup.cards).joinedload(Card.shipping_warehouse),
)
)
return group.cards if group else []
async def create_group_model(self) -> CardGroup:
group = CardGroup(
name='',
lexorank=lexorank.middle(lexorank.Bucket.BUCEKT_0).__str__(),
)
self.session.add(group)
await self.session.flush()
return group
async def create_group(self, user: User, request: CreateCardGroupRequest) -> CardCreateGroupResponse:
try:
group = await self.create_group_model()
for card_id in [request.dragging_card_id, request.hovered_card_id]:
insert_stmt = insert(card_relations).values({
'card_id': card_id,
'group_id': group.id
})
await self.session.execute(insert_stmt)
# changing status if needed on draggable card
dragging_card = await self.session.scalar(
select(Card).where(Card.id == request.dragging_card_id)
)
dropped_card = await self.session.scalar(
select(Card).where(Card.id == request.hovered_card_id)
)
if dragging_card.current_status_id != dropped_card.current_status_id:
card_service = CardsService(self.session)
await card_service.change_status(dragging_card, dropped_card.current_status_id, user)
await self.session.commit()
return CardCreateGroupResponse(ok=True, message="Группа успешно создана")
except Exception as e:
return CardCreateGroupResponse(ok=False, message=str(e))
async def update_group(self, request: CardGroupUpdateRequest) -> CardGroupUpdateResponse:
try:
group = await self.session.scalar(
select(CardGroup).where(CardGroup.id == request.data.id)
)
if not group:
return CardGroupUpdateResponse(ok=False, message="Группа не найдена")
# update by dictionary
request_dict = request.data.model_dump()
request_dict.pop("bill_request", None)
update_stmt = (
update(CardGroup)
.where(CardGroup.id == request.data.id)
.values(**request_dict)
)
await self.session.execute(update_stmt)
await self.session.commit()
return CardGroupUpdateResponse(ok=True, message="Группа успешно обновлена")
except Exception as e:
await self.session.rollback()
return CardGroupUpdateResponse(ok=False, message=str(e))
async def complete_group(self, group_id: int) -> list[Card]:
cards = await self.get_cards_by_group_id(group_id)
for card in cards:
card.is_completed = True
return cards
async def delete_group(self, group_id: int) -> None:
cards = await self.get_cards_by_group_id(group_id)
for card in cards:
card.is_deleted = True
await self.session.commit()
async def change_group_status(
self,
user: User,
request: CardGroupChangeStatusRequest,
) -> CardGroupChangeStatusResponse:
try:
# getting all cards in group
cards = await self.session.scalars(
select(card_relations.c.card_id)
.where(card_relations.c.group_id == request.group_id)
)
card_service = CardsService(self.session)
for card_id in cards:
card = await self.session.scalar(
select(Card).where(Card.id == card_id)
)
await card_service.change_status(card, request.new_status, user)
await self.session.commit()
return CardGroupChangeStatusResponse(ok=True, message="Статус группы успешно изменен")
except Exception as e:
await self.session.rollback()
return CardGroupChangeStatusResponse(ok=False, message=str(e))
async def add_card(self, user: User, request: CardAddToGroupRequest) -> CardAddToGroupResponse:
try:
group_bill_request = await self.session.get(GroupBillRequest, request.group_id)
if group_bill_request:
raise Exception("Нельзя добавить сделку, так как на группу выставлен счёт.")
# changing status if needed
card_id = await self.session.scalar(
select(card_relations.c.card_id)
.where(card_relations.c.group_id == request.group_id)
)
group_card_status = await self.session.scalar(
select(Card.current_status_id)
.where(Card.id == card_id)
)
request_card = await self.session.scalar(
select(Card).where(Card.id == request.card_id)
)
if group_card_status != request_card.current_status_id:
await CardsService(self.session).change_status(request_card, group_card_status, user)
insert_stmt = insert(card_relations).values({
'card_id': request.card_id,
'group_id': request.group_id
})
await self.session.execute(insert_stmt)
await self.session.commit()
return CardAddToGroupResponse(ok=True, message="Сделка успешно добавлена в группу")
except Exception as e:
await self.session.rollback()
return CardAddToGroupResponse(ok=False, message=str(e))
async def remove_card(self, request: CardRemoveFromGroupRequest) -> CardRemoveFromGroupResponse:
try:
delete_stmt = (
delete(card_relations)
.where(
card_relations.c.card_id == request.card_id,
)
)
await self.session.execute(delete_stmt)
await self.session.commit()
return CardRemoveFromGroupResponse(ok=True, message="Сделка успешно удалена из группы")
except Exception as e:
await self.session.rollback()
return CardRemoveFromGroupResponse(ok=False, message=str(e))

File diff suppressed because it is too large Load Diff

View File

@@ -1,168 +0,0 @@
from lexorank import lexorank
from sqlalchemy import select, insert, update, delete
from sqlalchemy.orm import selectinload
from models import DealService as DealServiceModel, User, Deal, DealProduct, Product, GroupBillRequest
from models.deal_group import DealGroup, deal_relations
from schemas.group import *
from services.base import BaseService
from services.deal import DealService
class DealGroupService(BaseService):
async def get_deals_by_group_id(self, group_id) -> list[Deal]:
group: DealGroup | None = await self.session.scalar(
select(DealGroup)
.where(DealGroup.id == group_id)
.options(
selectinload(DealGroup.deals).selectinload(Deal.products).selectinload(DealProduct.services),
selectinload(DealGroup.deals).selectinload(Deal.products)
.selectinload(DealProduct.product).selectinload(Product.barcodes),
selectinload(DealGroup.deals).selectinload(Deal.services).selectinload(DealServiceModel.service),
selectinload(DealGroup.deals).selectinload(Deal.status_history),
selectinload(DealGroup.deals).selectinload(Deal.group).selectinload(DealGroup.deals),
selectinload(DealGroup.deals).joinedload(Deal.client),
selectinload(DealGroup.deals).joinedload(Deal.shipping_warehouse),
)
)
return group.deals if group else []
async def create_group_model(self) -> DealGroup:
group = DealGroup(
name='',
lexorank=lexorank.middle(lexorank.Bucket.BUCEKT_0).__str__(),
)
self.session.add(group)
await self.session.flush()
return group
async def create_group(self, user: User, request: DealCreateGroupRequest) -> DealCreateGroupResponse:
try:
group = await self.create_group_model()
for deal_id in [request.dragging_deal_id, request.hovered_deal_id]:
insert_stmt = insert(deal_relations).values({
'deal_id': deal_id,
'group_id': group.id
})
await self.session.execute(insert_stmt)
# changing status if needed on draggable deal
dragging_deal = await self.session.scalar(
select(Deal).where(Deal.id == request.dragging_deal_id)
)
dropped_deal = await self.session.scalar(
select(Deal).where(Deal.id == request.hovered_deal_id)
)
if dragging_deal.current_status_id != dropped_deal.current_status_id:
deal_service = DealService(self.session)
await deal_service.change_status(dragging_deal, dropped_deal.current_status_id, user)
await self.session.commit()
return DealCreateGroupResponse(ok=True, message="Группа успешно создана")
except Exception as e:
return DealCreateGroupResponse(ok=False, message=str(e))
async def update_group(self, request: DealGroupUpdateRequest) -> DealGroupUpdateResponse:
try:
group = await self.session.scalar(
select(DealGroup).where(DealGroup.id == request.data.id)
)
if not group:
return DealGroupUpdateResponse(ok=False, message="Группа не найдена")
# update by dictionary
request_dict = request.data.model_dump()
request_dict.pop("bill_request", None)
update_stmt = (
update(DealGroup)
.where(DealGroup.id == request.data.id)
.values(**request_dict)
)
await self.session.execute(update_stmt)
await self.session.commit()
return DealGroupUpdateResponse(ok=True, message="Группа успешно обновлена")
except Exception as e:
await self.session.rollback()
return DealGroupUpdateResponse(ok=False, message=str(e))
async def complete_group(self, group_id: int) -> list[Deal]:
deals = await self.get_deals_by_group_id(group_id)
for deal in deals:
deal.is_completed = True
return deals
async def delete_group(self, group_id: int) -> None:
deals = await self.get_deals_by_group_id(group_id)
for deal in deals:
deal.is_deleted = True
await self.session.commit()
async def change_group_status(
self,
user: User,
request: DealGroupChangeStatusRequest,
) -> DealGroupChangeStatusResponse:
try:
# getting all deals in group
deals = await self.session.scalars(
select(deal_relations.c.deal_id)
.where(deal_relations.c.group_id == request.group_id)
)
deal_service = DealService(self.session)
for deal_id in deals:
deal = await self.session.scalar(
select(Deal).where(Deal.id == deal_id)
)
await deal_service.change_status(deal, request.new_status, user)
await self.session.commit()
return DealGroupChangeStatusResponse(ok=True, message="Статус группы успешно изменен")
except Exception as e:
await self.session.rollback()
return DealGroupChangeStatusResponse(ok=False, message=str(e))
async def add_deal(self, user: User, request: DealAddToGroupRequest) -> DealAddToGroupResponse:
try:
group_bill_request = await self.session.get(GroupBillRequest, request.group_id)
if group_bill_request:
raise Exception("Нельзя добавить сделку, так как на группу выставлен счёт.")
# changing status if needed
deal_id = await self.session.scalar(
select(deal_relations.c.deal_id)
.where(deal_relations.c.group_id == request.group_id)
)
group_deal_status = await self.session.scalar(
select(Deal.current_status_id)
.where(Deal.id == deal_id)
)
request_deal = await self.session.scalar(
select(Deal).where(Deal.id == request.deal_id)
)
if group_deal_status != request_deal.current_status_id:
await DealService(self.session).change_status(request_deal, group_deal_status, user)
insert_stmt = insert(deal_relations).values({
'deal_id': request.deal_id,
'group_id': request.group_id
})
await self.session.execute(insert_stmt)
await self.session.commit()
return DealAddToGroupResponse(ok=True, message="Сделка успешно добавлена в группу")
except Exception as e:
await self.session.rollback()
return DealAddToGroupResponse(ok=False, message=str(e))
async def remove_deal(self, request: DealRemoveFromGroupRequest) -> DealRemoveFromGroupResponse:
try:
delete_stmt = (
delete(deal_relations)
.where(
deal_relations.c.deal_id == request.deal_id,
)
)
await self.session.execute(delete_stmt)
await self.session.commit()
return DealRemoveFromGroupResponse(ok=True, message="Сделка успешно удалена из группы")
except Exception as e:
await self.session.rollback()
return DealRemoveFromGroupResponse(ok=False, message=str(e))

View File

@@ -1,6 +1,7 @@
from datetime import datetime from datetime import datetime
from sqlalchemy import select, update, func, delete, and_ from sqlalchemy import select, update, func, delete
from sqlalchemy.orm import selectinload
from models import Project, Board from models import Project, Board
from schemas.project import * from schemas.project import *
@@ -9,28 +10,37 @@ from services.base import BaseService
class ProjectService(BaseService): class ProjectService(BaseService):
async def get_projects(self) -> GetProjectsResponse: async def get_projects(self) -> GetProjectsResponse:
boards_sub = ( board_count_sub = (
select(Board) select(
.where(Board.is_deleted == False) Board.project_id,
func.count(Board.id).label('boards_count'),
)
.group_by(Board.project_id)
.subquery() .subquery()
) )
stmt = ( stmt = (
select( select(
Project.id, Project,
Project.name, func.coalesce(board_count_sub.c.boards_count, 0),
func.count(boards_sub.c.id) )
.outerjoin(board_count_sub, Project.id == board_count_sub.c.project_id)
.options(
selectinload(Project.attributes),
selectinload(Project.modules),
) )
.join(boards_sub, Project.id == boards_sub.c.project_id, isouter=True)
.where(Project.is_deleted == False)
.group_by(Project.id, Project.name)
.order_by(Project.name)
) )
project_data = (await self.session.execute(stmt)).all() project_data = (await self.session.execute(stmt)).all()
projects = [] projects = []
for project_id, name, boards_count in project_data: for project, boards_count in project_data:
project = ProjectSchemaWithCount(id=project_id, name=name, boards_count=boards_count) project_schema = FullProjectSchema(
projects.append(project) id=project.id,
name=project.name,
boards_count=boards_count,
attributes=project.attributes,
modules=project.modules,
)
projects.append(project_schema)
return GetProjectsResponse(projects=projects) return GetProjectsResponse(projects=projects)

View File

@@ -192,8 +192,8 @@ class ServiceService(BaseService):
category_dict = raw_category.model_dump() category_dict = raw_category.model_dump()
del category_dict['id'] del category_dict['id']
last_deal_service_rank = await self.session.scalar( last_deal_service_rank = await self.session.scalar(
select(ServiceCategory.deal_service_rank) select(ServiceCategory.card_service_rank)
.order_by(ServiceCategory.deal_service_rank.desc()) .order_by(ServiceCategory.card_service_rank.desc())
.limit(1) .limit(1)
) )
last_product_service_rank = await self.session.scalar( last_product_service_rank = await self.session.scalar(
@@ -338,7 +338,7 @@ class ServiceService(BaseService):
if not request.move_down and not request.move_up: if not request.move_down and not request.move_up:
return ServiceCategoryReorderResponse(ok=False, message="Не указано действие") return ServiceCategoryReorderResponse(ok=False, message="Не указано действие")
if request.service_type == ServiceType.DEAL_SERVICE: if request.service_type == ServiceType.DEAL_SERVICE:
order_by = ServiceCategory.deal_service_rank order_by = ServiceCategory.card_service_rank
rank_field = 'deal_service_rank' rank_field = 'deal_service_rank'
else: else:
order_by = ServiceCategory.product_service_rank order_by = ServiceCategory.product_service_rank

View File

@@ -1,18 +1,18 @@
from sqlalchemy import select, and_ from sqlalchemy import select, and_
from models import Deal, Pallet, Box from models import Card, Pallet, Box
from models.shipping import ShippingProduct from models.shipping import ShippingProduct
from schemas.shipping import * from schemas.shipping import *
from services.base import BaseService from services.base import BaseService
class ShippingService(BaseService): class ShippingService(BaseService):
async def create_pallet(self, deal_id: int) -> CreatePalletResponse: async def create_pallet(self, card_id: int) -> CreatePalletResponse:
deal = await self.session.get(Deal, deal_id) deal = await self.session.get(Card, card_id)
if not deal: if not deal:
return CreatePalletResponse(ok=False, message="Сделка не найдена") return CreatePalletResponse(ok=False, message="Сделка не найдена")
pallet = Pallet(deal_id=deal_id) pallet = Pallet(card_id=card_id)
self.session.add(pallet) self.session.add(pallet)
await self.session.commit() await self.session.commit()
return CreatePalletResponse(ok=True, message="Паллет успешно создан") return CreatePalletResponse(ok=True, message="Паллет успешно создан")
@@ -36,19 +36,19 @@ class ShippingService(BaseService):
await self.session.commit() await self.session.commit()
return True, "Короб обновлен" return True, "Короб обновлен"
async def _create_box(self, data: CreateBoxInDealSchema | CreateBoxInPalletSchema): async def _create_box(self, data: CreateBoxInCardSchema | CreateBoxInPalletSchema):
box = Box(**data.model_dump()) box = Box(**data.model_dump())
self.session.add(box) self.session.add(box)
await self.session.commit() await self.session.commit()
async def _create_box_in_deal(self, data: CreateBoxInDealSchema) -> tuple[bool, str]: async def _create_box_in_card(self, data: CreateBoxInCardSchema) -> tuple[bool, str]:
deal = await self.session.get(Deal, data.deal_id) card = await self.session.get(Card, data.card_id)
if not deal: if not card:
return False, f"Сделка с ID:{data.deal_id} не найдена" return False, f"Сделка с ID:{data.card_id} не найдена"
await self._create_box(data) await self._create_box(data)
return True, f"Короб для сделки ID:{data.deal_id} добавлен" return True, f"Короб для сделки ID:{data.card_id} добавлен"
async def _create_box_in_pallet(self, data: CreateBoxInPalletSchema) -> tuple[bool, str]: async def _create_box_in_pallet(self, data: CreateBoxInPalletSchema) -> tuple[bool, str]:
pallet = await self.session.get(Pallet, data.pallet_id) pallet = await self.session.get(Pallet, data.pallet_id)
@@ -66,7 +66,7 @@ class ShippingService(BaseService):
elif "pallet_id" in data_keys: elif "pallet_id" in data_keys:
ok, message = await self._create_box_in_pallet(CreateBoxInPalletSchema.model_validate(request.data)) ok, message = await self._create_box_in_pallet(CreateBoxInPalletSchema.model_validate(request.data))
else: else:
ok, message = await self._create_box_in_deal(CreateBoxInDealSchema.model_validate(request.data)) ok, message = await self._create_box_in_card(CreateBoxInCardSchema.model_validate(request.data))
return UpdateBoxResponse(ok=ok, message=message) return UpdateBoxResponse(ok=ok, message=message)

View File

@@ -4,7 +4,7 @@ from fastapi import HTTPException
from sqlalchemy import select, and_, union_all, func, Subquery, literal from sqlalchemy import select, and_, union_all, func, Subquery, literal
from enums.profit_table_group_by import ProfitTableGroupBy from enums.profit_table_group_by import ProfitTableGroupBy
from models import DealService, Deal, DealStatusHistory, DealProductService, DealProduct, Service, Client, \ from models import CardService, Card, CardStatusHistory, CardProductService, CardProduct, Service, Client, \
ShippingWarehouse, BaseMarketplace, User, Project, Board ShippingWarehouse, BaseMarketplace, User, Project, Board
from schemas.statistics import GetProfitChartDataResponse, GetProfitChartDataRequest, ProfitChartDataItem, \ from schemas.statistics import GetProfitChartDataResponse, GetProfitChartDataRequest, ProfitChartDataItem, \
GetProfitTableDataResponse, GetProfitTableDataRequest, ProfitTableDataItem, CommonProfitFilters GetProfitTableDataResponse, GetProfitTableDataRequest, ProfitTableDataItem, CommonProfitFilters
@@ -19,12 +19,12 @@ class ProfitStatisticsService(BaseService):
def _get_sub_deals_created_at(date_from: datetime.date, date_to: datetime.date): def _get_sub_deals_created_at(date_from: datetime.date, date_to: datetime.date):
deals_created_at = ( deals_created_at = (
select( select(
Deal.id.label('deal_id'), Card.id.label('deal_id'),
func.date_trunc( func.date_trunc(
'day', 'day',
Deal.created_at, Card.created_at,
).label('date'), ).label('date'),
Deal.current_status_id, Card.current_status_id,
) )
.subquery() .subquery()
) )
@@ -38,22 +38,22 @@ class ProfitStatisticsService(BaseService):
def _get_sub_status_history(): def _get_sub_status_history():
last_statuses = ( last_statuses = (
select( select(
DealStatusHistory.deal_id, CardStatusHistory.card_id,
func.max(DealStatusHistory.changed_at).label('changed_at') func.max(CardStatusHistory.changed_at).label('changed_at')
) )
.group_by(DealStatusHistory.deal_id) .group_by(CardStatusHistory.card_id)
.subquery() .subquery()
) )
return ( return (
select( select(
Deal.id.label('deal_id'), Card.id.label('deal_id'),
func.date_trunc( func.date_trunc(
'day', 'day',
last_statuses.c.changed_at, last_statuses.c.changed_at,
).label('date'), ).label('date'),
Deal.current_status_id, Card.current_status_id,
) )
.join(last_statuses, last_statuses.c.deal_id == Deal.id) .join(last_statuses, last_statuses.c.deal_id == Card.id)
.subquery() .subquery()
) )
@@ -80,7 +80,7 @@ class ProfitStatisticsService(BaseService):
if is_chart: if is_chart:
data_item = ProfitChartDataItem( data_item = ProfitChartDataItem(
date=row.date.date(), date=row.date.date(),
deals_count=row.deals_count, deals_count=row.cards_count,
profit=row.profit, profit=row.profit,
revenue=row.revenue, revenue=row.revenue,
expenses=row.expenses, expenses=row.expenses,
@@ -88,7 +88,7 @@ class ProfitStatisticsService(BaseService):
else: else:
data_item = ProfitTableDataItem( data_item = ProfitTableDataItem(
grouped_value=row.date.date(), grouped_value=row.date.date(),
deals_count=row.deals_count, deals_count=row.cards_count,
profit=row.profit, profit=row.profit,
revenue=row.revenue, revenue=row.revenue,
expenses=row.expenses, expenses=row.expenses,
@@ -100,25 +100,25 @@ class ProfitStatisticsService(BaseService):
def _get_stmt_deal_services(self, sub_filtered_status_history: Subquery): def _get_stmt_deal_services(self, sub_filtered_status_history: Subquery):
return ( return (
select( select(
Deal.id.label("deal_id"), Card.id.label("deal_id"),
func.date_trunc( func.date_trunc(
"day", "day",
sub_filtered_status_history.c.date, sub_filtered_status_history.c.date,
).label("date"), ).label("date"),
func.sum(DealService.price * DealService.quantity).label("revenue"), func.sum(CardService.price * CardService.quantity).label("revenue"),
func.sum(DealService.price * DealService.quantity).label("profit"), func.sum(CardService.price * CardService.quantity).label("profit"),
) )
.join(DealService, Deal.id == DealService.deal_id) .join(CardService, Card.id == CardService.card_id)
.join(Service, DealService.service_id == Service.id) .join(Service, CardService.service_id == Service.id)
.join(sub_filtered_status_history, Deal.id == sub_filtered_status_history.c.deal_id) .join(sub_filtered_status_history, Card.id == sub_filtered_status_history.c.deal_id)
.where( .where(
and_( and_(
Deal.is_deleted == False, Card.is_deleted == False,
Deal.is_accounted == True, Card.is_accounted == True,
Deal.is_completed == True if self.is_completed_only else True Card.is_completed == True if self.is_completed_only else True
) )
) )
.group_by(Deal.id, "date") .group_by(Card.id, "date")
) )
@staticmethod @staticmethod
@@ -127,18 +127,18 @@ class ProfitStatisticsService(BaseService):
select(Board.id) select(Board.id)
.where(Board.project_id == project_id) .where(Board.project_id == project_id)
) )
return stmt.where(Deal.board_id.in_(board_ids_stmt)) return stmt.where(Card.board_id.in_(board_ids_stmt))
@staticmethod @staticmethod
def _apply_filters(request: CommonProfitFilters, stmt_deal_services, stmt_deal_product_services): def _apply_filters(request: CommonProfitFilters, stmt_deal_services, stmt_deal_product_services):
if request.client_id != -1: if request.client_id != -1:
stmt_deal_services = stmt_deal_services.where(Deal.client_id == request.client_id) stmt_deal_services = stmt_deal_services.where(Card.client_id == request.client_id)
stmt_deal_product_services = stmt_deal_product_services.where(Deal.client_id == request.client_id) stmt_deal_product_services = stmt_deal_product_services.where(Card.client_id == request.client_id)
if request.base_marketplace_key != "all": if request.base_marketplace_key != "all":
stmt_deal_services = stmt_deal_services.where(Deal.base_marketplace_key == request.base_marketplace_key) stmt_deal_services = stmt_deal_services.where(Card.base_marketplace_key == request.base_marketplace_key)
stmt_deal_product_services = stmt_deal_product_services.where( stmt_deal_product_services = stmt_deal_product_services.where(
Deal.base_marketplace_key == request.base_marketplace_key) Card.base_marketplace_key == request.base_marketplace_key)
if request.project_id != -1: if request.project_id != -1:
stmt_deal_services = ProfitStatisticsService._board_ids_for_project(request.project_id, stmt_deal_services) stmt_deal_services = ProfitStatisticsService._board_ids_for_project(request.project_id, stmt_deal_services)
@@ -148,44 +148,44 @@ class ProfitStatisticsService(BaseService):
) )
if request.board_id != -1: if request.board_id != -1:
stmt_deal_services = stmt_deal_services.where(Deal.board_id == request.board_id) stmt_deal_services = stmt_deal_services.where(Card.board_id == request.board_id)
stmt_deal_product_services = stmt_deal_product_services.where(Deal.board_id == request.board_id) stmt_deal_product_services = stmt_deal_product_services.where(Card.board_id == request.board_id)
if request.deal_status_id != -1: if request.card_status_id != -1:
stmt_deal_services = stmt_deal_services.where(Deal.current_status_id == request.deal_status_id) stmt_deal_services = stmt_deal_services.where(Card.current_status_id == request.card_status_id)
stmt_deal_product_services = stmt_deal_product_services.where( stmt_deal_product_services = stmt_deal_product_services.where(
Deal.current_status_id == request.deal_status_id) Card.current_status_id == request.card_status_id)
if request.manager_id != -1: if request.manager_id != -1:
stmt_deal_services = stmt_deal_services.where(Deal.manager_id == request.manager_id) stmt_deal_services = stmt_deal_services.where(Card.manager_id == request.manager_id)
stmt_deal_product_services = stmt_deal_product_services.where(Deal.manager_id == request.manager_id) stmt_deal_product_services = stmt_deal_product_services.where(Card.manager_id == request.manager_id)
return stmt_deal_services, stmt_deal_product_services return stmt_deal_services, stmt_deal_product_services
def _get_stmt_product_services(self): def _get_stmt_product_services(self):
return ( return (
select( select(
Deal.id.label("deal_id"), Card.id.label("deal_id"),
func.sum(DealProductService.price * DealProduct.quantity).label("revenue"), func.sum(CardProductService.price * CardProduct.quantity).label("revenue"),
func.sum(DealProductService.price * DealProduct.quantity).label("profit"), func.sum(CardProductService.price * CardProduct.quantity).label("profit"),
) )
.join(DealProduct, Deal.id == DealProduct.deal_id) .join(CardProduct, Card.id == CardProduct.card_id)
.join( .join(
DealProductService, CardProductService,
and_( and_(
DealProductService.deal_id == Deal.id, CardProductService.card_id == Card.id,
DealProductService.product_id == DealProduct.product_id, CardProductService.product_id == CardProduct.product_id,
) )
) )
.join(Service, DealProductService.service_id == Service.id) .join(Service, CardProductService.service_id == Service.id)
.where( .where(
and_( and_(
Deal.is_deleted == False, Card.is_deleted == False,
Deal.is_accounted == True, Card.is_accounted == True,
Deal.is_completed == True if self.is_completed_only else True, Card.is_completed == True if self.is_completed_only else True,
) )
) )
.group_by(Deal.id) .group_by(Card.id)
) )
@staticmethod @staticmethod
@@ -208,7 +208,7 @@ class ProfitStatisticsService(BaseService):
deals = ( deals = (
select( select(
stmt.c.date, stmt.c.date,
func.count(stmt.c.deal_id).label("deals_count"), func.count(stmt.c.card_id).label("deals_count"),
func.sum(stmt.c.revenue).label("revenue"), func.sum(stmt.c.revenue).label("revenue"),
func.sum(stmt.c.profit).label("profit"), func.sum(stmt.c.profit).label("profit"),
) )
@@ -246,12 +246,12 @@ class ProfitStatisticsService(BaseService):
select( select(
Client.id, Client.id,
Client.name.label("grouped_value"), Client.name.label("grouped_value"),
func.count(stmt.c.deal_id).label("deals_count"), func.count(stmt.c.card_id).label("deals_count"),
func.sum(stmt.c.revenue).label("revenue"), func.sum(stmt.c.revenue).label("revenue"),
func.sum(stmt.c.profit).label("profit"), func.sum(stmt.c.profit).label("profit"),
) )
.join(Deal, Deal.id == stmt.c.deal_id) .join(Card, Card.id == stmt.c.card_id)
.join(Client, Client.id == Deal.client_id) .join(Client, Client.id == Card.client_id)
.group_by(Client.id, Client.name) .group_by(Client.id, Client.name)
) )
@@ -261,12 +261,12 @@ class ProfitStatisticsService(BaseService):
select( select(
Project.id, Project.id,
Project.name.label("grouped_value"), Project.name.label("grouped_value"),
func.count(stmt.c.deal_id).label("deals_count"), func.count(stmt.c.card_id).label("deals_count"),
func.sum(stmt.c.revenue).label("revenue"), func.sum(stmt.c.revenue).label("revenue"),
func.sum(stmt.c.profit).label("profit"), func.sum(stmt.c.profit).label("profit"),
) )
.join(Deal, Deal.id == stmt.c.deal_id) .join(Card, Card.id == stmt.c.card_id)
.join(Board, Board.id == Deal.board_id) .join(Board, Board.id == Card.board_id)
.join(Project, Project.id == Board.project_id) .join(Project, Project.id == Board.project_id)
.group_by(Project.id, Project.name) .group_by(Project.id, Project.name)
) )
@@ -277,12 +277,12 @@ class ProfitStatisticsService(BaseService):
select( select(
Board.id, Board.id,
Board.name.label("grouped_value"), Board.name.label("grouped_value"),
func.count(stmt.c.deal_id).label("deals_count"), func.count(stmt.c.card_id).label("deals_count"),
func.sum(stmt.c.revenue).label("revenue"), func.sum(stmt.c.revenue).label("revenue"),
func.sum(stmt.c.profit).label("profit"), func.sum(stmt.c.profit).label("profit"),
) )
.join(Deal, Deal.id == stmt.c.deal_id) .join(Card, Card.id == stmt.c.card_id)
.join(Board, Board.id == Deal.board_id) .join(Board, Board.id == Card.board_id)
.group_by(Board.id, Board.name) .group_by(Board.id, Board.name)
) )
@@ -290,13 +290,13 @@ class ProfitStatisticsService(BaseService):
def _join_and_group_by_statuses(stmt): def _join_and_group_by_statuses(stmt):
return ( return (
select( select(
Deal.current_status_id.label("grouped_value"), Card.current_status_id.label("grouped_value"),
func.count(stmt.c.deal_id).label("deals_count"), func.count(stmt.c.card_id).label("deals_count"),
func.sum(stmt.c.revenue).label("revenue"), func.sum(stmt.c.revenue).label("revenue"),
func.sum(stmt.c.profit).label("profit"), func.sum(stmt.c.profit).label("profit"),
) )
.join(Deal, Deal.id == stmt.c.deal_id) .join(Card, Card.id == stmt.c.card_id)
.group_by(Deal.current_status_id) .group_by(Card.current_status_id)
) )
@staticmethod @staticmethod
@@ -306,12 +306,12 @@ class ProfitStatisticsService(BaseService):
ShippingWarehouse.id, ShippingWarehouse.id,
ShippingWarehouse.name.label("grouped_value"), ShippingWarehouse.name.label("grouped_value"),
ShippingWarehouse.is_deleted, ShippingWarehouse.is_deleted,
func.count(stmt.c.deal_id).label("deals_count"), func.count(stmt.c.card_id).label("deals_count"),
func.sum(stmt.c.revenue).label("revenue"), func.sum(stmt.c.revenue).label("revenue"),
func.sum(stmt.c.profit).label("profit"), func.sum(stmt.c.profit).label("profit"),
) )
.join(Deal, Deal.id == stmt.c.deal_id) .join(Card, Card.id == stmt.c.card_id)
.join(ShippingWarehouse, Deal.shipping_warehouse_id == ShippingWarehouse.id) .join(ShippingWarehouse, Card.shipping_warehouse_id == ShippingWarehouse.id)
.where(ShippingWarehouse.is_deleted == False) .where(ShippingWarehouse.is_deleted == False)
.group_by(ShippingWarehouse.is_deleted, ShippingWarehouse.id, ShippingWarehouse.name) .group_by(ShippingWarehouse.is_deleted, ShippingWarehouse.id, ShippingWarehouse.name)
) )
@@ -321,12 +321,12 @@ class ProfitStatisticsService(BaseService):
return ( return (
select( select(
BaseMarketplace.name.label("grouped_value"), BaseMarketplace.name.label("grouped_value"),
func.count(stmt.c.deal_id).label("deals_count"), func.count(stmt.c.card_id).label("deals_count"),
func.sum(stmt.c.revenue).label("revenue"), func.sum(stmt.c.revenue).label("revenue"),
func.sum(stmt.c.profit).label("profit"), func.sum(stmt.c.profit).label("profit"),
) )
.join(Deal, Deal.id == stmt.c.deal_id) .join(Card, Card.id == stmt.c.card_id)
.join(BaseMarketplace, Deal.base_marketplace_key == BaseMarketplace.key) .join(BaseMarketplace, Card.base_marketplace_key == BaseMarketplace.key)
.group_by(BaseMarketplace.name) .group_by(BaseMarketplace.name)
) )
@@ -341,12 +341,12 @@ class ProfitStatisticsService(BaseService):
select( select(
managers.c.id, managers.c.id,
(managers.c.first_name + " " + managers.c.second_name).label("grouped_value"), (managers.c.first_name + " " + managers.c.second_name).label("grouped_value"),
func.count(stmt.c.deal_id).label("deals_count"), func.count(stmt.c.card_id).label("deals_count"),
func.sum(stmt.c.revenue).label("revenue"), func.sum(stmt.c.revenue).label("revenue"),
func.sum(stmt.c.profit).label("profit"), func.sum(stmt.c.profit).label("profit"),
) )
.join(Deal, Deal.id == stmt.c.deal_id) .join(Card, Card.id == stmt.c.card_id)
.join(managers, managers.c.id == Deal.manager_id) .join(managers, managers.c.id == Card.manager_id)
.group_by(managers.c.id, "grouped_value") .group_by(managers.c.id, "grouped_value")
) )
@@ -384,7 +384,7 @@ class ProfitStatisticsService(BaseService):
self.is_completed_only = request.is_completed_only self.is_completed_only = request.is_completed_only
self.filters = request self.filters = request
sub_deals_dates = self._get_deals_dates(request.deal_status_id) sub_deals_dates = self._get_deals_dates(request.card_status_id)
stmt_deal_services = self._get_stmt_deal_services(sub_deals_dates) stmt_deal_services = self._get_stmt_deal_services(sub_deals_dates)
stmt_deal_product_services = self._get_stmt_product_services() stmt_deal_product_services = self._get_stmt_product_services()
@@ -425,7 +425,7 @@ class ProfitStatisticsService(BaseService):
self.date_from, self.date_to = request.date_range self.date_from, self.date_to = request.date_range
self.filters = request self.filters = request
sub_deals_dates = self._get_deals_dates(request.deal_status_id) sub_deals_dates = self._get_deals_dates(request.card_status_id)
stmt_deal_services = self._get_stmt_deal_services(sub_deals_dates) stmt_deal_services = self._get_stmt_deal_services(sub_deals_dates)
@@ -436,12 +436,12 @@ class ProfitStatisticsService(BaseService):
stmt_deal_product_services.c.deal_id, stmt_deal_product_services.c.deal_id,
func.date_trunc( func.date_trunc(
"day", "day",
Deal.created_at Card.created_at
).label("date"), ).label("date"),
stmt_deal_product_services.c.revenue.label("revenue"), stmt_deal_product_services.c.revenue.label("revenue"),
stmt_deal_product_services.c.profit.label("profit"), stmt_deal_product_services.c.profit.label("profit"),
) )
.join(Deal, Deal.id == stmt_deal_product_services.c.deal_id) .join(Card, Card.id == stmt_deal_product_services.c.deal_id)
) )
stmt_deal_services, stmt_deal_product_services = self._apply_filters( stmt_deal_services, stmt_deal_product_services = self._apply_filters(

View File

@@ -2,30 +2,30 @@ from typing import Optional
from sqlalchemy import select, and_, func from sqlalchemy import select, and_, func
from models import DealStatus, Deal from models import CardStatus, Card
from schemas.status import * from schemas.status import *
from services.base import BaseService from services.base import BaseService
class StatusService(BaseService): class StatusService(BaseService):
async def _get_statuses_for_board(self, board_id: int) -> list[DealStatus]: async def _get_statuses_for_board(self, board_id: int) -> list[CardStatus]:
stmt = ( stmt = (
select(DealStatus) select(CardStatus)
.where( .where(
and_( and_(
DealStatus.board_id == board_id, CardStatus.board_id == board_id,
DealStatus.is_deleted == False, CardStatus.is_deleted == False,
) )
) )
.order_by(DealStatus.ordinal_number) .order_by(CardStatus.ordinal_number)
) )
statuses = (await self.session.scalars(stmt)).all() statuses = (await self.session.scalars(stmt)).all()
return list(statuses) return list(statuses)
async def _get_status_by_id(self, status_id: int) -> Optional[DealStatus]: async def _get_status_by_id(self, status_id: int) -> Optional[CardStatus]:
stmt = ( stmt = (
select(DealStatus) select(CardStatus)
.where(DealStatus.id == status_id) .where(CardStatus.id == status_id)
) )
status = await self.session.scalar(stmt) status = await self.session.scalar(stmt)
return status return status
@@ -38,7 +38,7 @@ class StatusService(BaseService):
statuses[-1].is_finishing = False statuses[-1].is_finishing = False
ordinal_number = statuses[-1].ordinal_number + 1 ordinal_number = statuses[-1].ordinal_number + 1
status = DealStatus( status = CardStatus(
**request.status.model_dump(), **request.status.model_dump(),
ordinal_number=ordinal_number, ordinal_number=ordinal_number,
is_finishing=True, is_finishing=True,
@@ -81,12 +81,12 @@ class StatusService(BaseService):
async def _count_deals_in_progress(self, status_id: int) -> int: async def _count_deals_in_progress(self, status_id: int) -> int:
stmt = ( stmt = (
select(func.count(Deal.id)) select(func.count(Card.id))
.where( .where(
and_( and_(
Deal.current_status_id == status_id, Card.current_status_id == status_id,
Deal.is_deleted == False, Card.is_deleted == False,
Deal.is_completed == False, Card.is_completed == False,
) )
) )
) )
@@ -94,12 +94,12 @@ class StatusService(BaseService):
async def _count_deals(self, status_id: int) -> int: async def _count_deals(self, status_id: int) -> int:
stmt = ( stmt = (
select(func.count(Deal.id)) select(func.count(Card.id))
.where(Deal.current_status_id == status_id) .where(Card.current_status_id == status_id)
) )
return (await self.session.scalars(stmt)).first() return (await self.session.scalars(stmt)).first()
async def _set_finishing_flag_to_prev_status(self, status: DealStatus): async def _set_finishing_flag_to_prev_status(self, status: CardStatus):
statuses = await self._get_statuses_for_board(status.board_id) statuses = await self._get_statuses_for_board(status.board_id)
if len(statuses) < 2: if len(statuses) < 2:
return return