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 models import DealProduct, Deal, DealStatusHistory
from models import CardProduct, Card, CardStatusHistory
class DealTechSpecProductData(TypedDict):
deal: Deal
last_status: DealStatusHistory
deal: Card
last_status: CardStatusHistory
total_one_product: int
quantity: int
additional_info: Optional[str]
# Поле для группировки товаров с одним артикулом и вывода таблицы [Штрихкод, Размер, Кол-во, Короба]
deal_products: List[DealProduct]
deal_products: List[CardProduct]
# Поле для группировки товаров из нескольких сделок и вывода таблицы [Склад отгрузки, Кол-во]
warehouses_and_quantities: List[Tuple[str, int]]
class DealTechSpecData(TypedDict):
deals: List[Deal]
deals: List[Card]
products: Dict[str, DealTechSpecProductData]
product_images: Tuple
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 generators.deal_pdf_generator.deal_data import DealTechSpecProductData, DealTechSpecData
from models import Deal, DealProduct, DealService as DealServiceModel, Product, DealGroup
from services.deal_group import DealGroupService
from models import Card, CardProduct, CardService as DealServiceModel, Product, CardGroup
from services.card_group import CardGroupService
from utils.images_fetcher import fetch_images
# Генерация ключа для группировки 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(
str(service.service_id) for service in deal_product.services
)
@@ -48,10 +48,10 @@ class DealTechSpecPdfGenerator:
"deal_ids_header": "",
"deal_status_str": DEAL_STATUS_STR,
}
self.deal: Deal
self.deal: Card
@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] = {}
additional_info: Optional[str]
@@ -61,7 +61,7 @@ class DealTechSpecPdfGenerator:
if key not in products:
products[key] = {
"deal": deal_product.deal,
"deal": deal_product.card,
"deal_products": [deal_product],
"quantity": deal_product.quantity,
"additional_info": deal_product.product.additional_info,
@@ -75,18 +75,18 @@ class DealTechSpecPdfGenerator:
return products
async def _get_deal_by_id(self, deal_id: int) -> Optional[Deal]:
deal: Deal | None = await self._session.scalar(
select(Deal)
.where(Deal.id == deal_id)
async def _get_deal_by_id(self, deal_id: int) -> Optional[Card]:
deal: Card | None = await self._session.scalar(
select(Card)
.where(Card.id == deal_id)
.options(
selectinload(Deal.products).selectinload(DealProduct.services),
selectinload(Deal.products).selectinload(DealProduct.product).selectinload(Product.barcodes),
selectinload(Deal.services).selectinload(DealServiceModel.service),
selectinload(Deal.status_history),
selectinload(Deal.group).selectinload(DealGroup.deals),
joinedload(Deal.client),
joinedload(Deal.shipping_warehouse),
selectinload(Card.products).selectinload(CardProduct.services),
selectinload(Card.products).selectinload(CardProduct.product).selectinload(Product.barcodes),
selectinload(Card.services).selectinload(DealServiceModel.service),
selectinload(Card.status_history),
selectinload(Card.group).selectinload(CardGroup.cards),
joinedload(Card.client),
joinedload(Card.shipping_warehouse),
)
)
return deal
@@ -94,7 +94,7 @@ class DealTechSpecPdfGenerator:
def _set_deals_ids_header(self):
self.deal_doc["deal_ids_header"] = f"ID: {self.deal.id}"
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):
deal = await self._get_deal_by_id(deal_id)
@@ -105,7 +105,7 @@ class DealTechSpecPdfGenerator:
self._set_deals_ids_header()
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:
self.deal_doc["deals"].append(d)
grouped_products = await self._group_deal_products_by_products(d.products)

View File

@@ -42,7 +42,7 @@ class PriceListPdfGenerator:
# Определяем функцию сортировки категорий по рангу
def category_sort_key(category):
if service_type == ServiceType.DEAL_SERVICE:
return category.deal_service_rank
return category.card_service_rank
else:
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}
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_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 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
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 = (
select(Deal)
.where(Deal.id == deal_id)
select(Card)
.where(Card.id == deal_id)
.options(
joinedload(Deal.shipping_warehouse),
selectinload(Deal.pallets),
joinedload(Card.shipping_warehouse),
selectinload(Card.pallets),
)
)
deal = (await self._session.execute(stmt)).one_or_none()
@@ -93,7 +93,7 @@ class ShippingQRCodeGenerator(BasePdfCardGenerator):
func.count(Box.id).label("box_count"),
)
.join(Box, isouter=True)
.where(Pallet.deal_id == deal_id)
.where(Pallet.card_id == deal_id)
.group_by(Pallet.id)
)
pallets = (await self._session.execute(stmt_boxes_on_pallets)).all()

View File

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

View File

@@ -1,10 +1,13 @@
from sqlalchemy.orm import configure_mappers
from .auth import *
from .project import *
from .module import *
from .board import *
from .status import *
from .deal import *
from .attribute import *
from .card import *
from .auth import *
from .card import *
from .client import *
from .service import *
from .product import *
@@ -15,7 +18,7 @@ from .marketplace import *
from .payroll import *
from .billing import *
from .marketplace_products import *
from .deal_group import *
from .card_group import *
from .transaction import *
from .residues 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:
from models.payroll import PayRate, PaymentRecord
from models import Deal, DealEmployees
from models import Card, CardEmployees
role_permissions = Table(
'role_permissions',
@@ -115,7 +115,7 @@ class User(BaseModel):
foreign_keys="WorkShift.user_id"
)
managed_deals: Mapped[list["Deal"]] = relationship(
managed_cards: Mapped[list["Card"]] = relationship(
back_populates="manager",
uselist=True,
)
@@ -127,7 +127,7 @@ class User(BaseModel):
cascade="all, delete-orphan"
)
deals: Mapped[list['DealEmployees']] = relationship(
cards: Mapped[list['CardEmployees']] = relationship(
back_populates='user',
lazy='selectin'
)

View File

@@ -35,14 +35,17 @@ class BarcodeTemplate(BaseModel):
__tablename__ = 'barcode_templates'
id = Column(Integer, autoincrement=True, primary_key=True, index=True)
name = Column(String, nullable=False, index=True, comment='Название шаблона')
attributes = relationship('BarcodeTemplateAttribute',
secondary=barcode_template_attribute_link,
lazy='selectin'
)
additional_attributes = relationship('BarcodeTemplateAdditionalField',
lazy='selectin',
back_populates='barcode_template',
cascade="all, delete")
attributes = relationship(
'BarcodeTemplateAttribute',
secondary=barcode_template_attribute_link,
lazy='selectin',
)
additional_attributes = relationship(
'BarcodeTemplateAdditionalField',
lazy='selectin',
back_populates='barcode_template',
cascade="all, delete",
)
additional_field = Column(String, nullable=True, 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
if TYPE_CHECKING:
from models import Deal, DealGroup
from models import Card, CardGroup
class DealBillRequest(BaseModel):
__tablename__ = 'deal_bill_requests'
class CardBillRequest(BaseModel):
__tablename__ = 'card_bill_requests'
deal_id: Mapped[int] = mapped_column(ForeignKey('deals.id'),
nullable=False,
primary_key=True,
unique=True)
deal: Mapped['Deal'] = relationship(back_populates='bill_request')
card_id: Mapped[int] = mapped_column(
ForeignKey('cards.id'),
nullable=False,
primary_key=True,
unique=True,
)
card: Mapped['Card'] = relationship(back_populates='bill_request')
created_at: Mapped[datetime.datetime] = mapped_column(nullable=False)
paid: Mapped[bool] = mapped_column(nullable=False, default=False)
@@ -29,11 +31,13 @@ class DealBillRequest(BaseModel):
class GroupBillRequest(BaseModel):
__tablename__ = 'group_bill_requests'
group_id: Mapped[int] = mapped_column(ForeignKey('deal_groups.id'),
nullable=False,
primary_key=True,
unique=True)
group: Mapped['DealGroup'] = relationship(back_populates='bill_request')
group_id: Mapped[int] = mapped_column(
ForeignKey('card_groups.id'),
nullable=False,
primary_key=True,
unique=True,
)
group: Mapped['CardGroup'] = relationship(back_populates='bill_request')
created_at: Mapped[datetime.datetime] = mapped_column(nullable=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.orm import Mapped, mapped_column, relationship
from models import BaseModel
from models.base import BaseModel
if TYPE_CHECKING:
from models import Project, DealStatus, Deal
from models import Project, CardStatus, Card
class Board(BaseModel):
@@ -26,6 +26,6 @@ class Board(BaseModel):
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
if TYPE_CHECKING:
from models import Deal
from models import Card
class DealGroup(BaseModel):
__tablename__ = 'deal_groups'
class CardGroup(BaseModel):
__tablename__ = 'card_groups'
id: Mapped[int] = mapped_column(
primary_key=True
)
@@ -21,9 +21,9 @@ class DealGroup(BaseModel):
lexorank: Mapped[str] = mapped_column(
nullable=False
)
deals: Mapped[list["Deal"]] = relationship(
cards: Mapped[list["Card"]] = relationship(
back_populates='group',
secondary='deal_relations'
secondary='card_relations'
)
bill_request: Mapped[Optional['GroupBillRequest']] = relationship(
back_populates='group',
@@ -31,9 +31,9 @@ class DealGroup(BaseModel):
)
deal_relations = Table(
'deal_relations',
card_relations = Table(
'card_relations',
BaseModel.metadata,
Column('deal_id', ForeignKey('deals.id'), primary_key=True, unique=True),
Column('group_id', ForeignKey('deal_groups.id'), primary_key=True)
Column('card_id', ForeignKey('cards.id'), primary_key=True, unique=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 sqlalchemy import Column, Integer, String, DateTime, ForeignKey
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, Mapped, mapped_column
from models import BaseModel
if TYPE_CHECKING:
from models import ResidualPallet, ResidualBox
from models import ResidualPallet, ResidualBox, Product, BarcodeTemplate, User
class Client(BaseModel):
__tablename__ = 'clients'
id = Column(Integer, autoincrement=True, primary_key=True, index=True)
name = Column(String, nullable=False, unique=True, comment='Название клиента')
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(nullable=False, unique=True, comment='Название клиента')
# TODO replace with additional model
company_name = Column(String,
nullable=False,
server_default='',
comment='Название компании')
company_name: Mapped[str] = mapped_column(
nullable=False,
server_default='',
comment='Название компании',
)
created_at = Column(DateTime, nullable=False, comment='Дата создания')
created_at: Mapped[datetime] = mapped_column(nullable=False, comment='Дата создания')
products = relationship('Product', back_populates='client')
details = relationship('ClientDetails', uselist=False, back_populates='client', cascade='all, delete',
lazy='joined')
products: Mapped[list['Product']] = relationship('Product', back_populates='client')
details: Mapped['ClientDetails'] = relationship(
uselist=False,
back_populates='client',
cascade='all, delete',
lazy='joined',
)
barcode_template_id = Column(Integer, ForeignKey('barcode_templates.id'), nullable=True)
barcode_template = relationship('BarcodeTemplate', lazy='selectin')
barcode_template_id: Mapped[int] = mapped_column(ForeignKey('barcode_templates.id'), nullable=True)
barcode_template: Mapped['BarcodeTemplate'] = relationship('BarcodeTemplate', lazy='selectin')
comment: Mapped[Optional[str]] = mapped_column(nullable=True, server_default=None, comment='Комментарий')
pallets: Mapped[list["ResidualPallet"]] = relationship(back_populates='client', lazy='selectin')
boxes: Mapped[list["ResidualBox"]] = 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')
class ClientDetails(BaseModel):
__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 = relationship('Client', back_populates='details', cascade='all, delete', uselist=False)
client_id: Mapped[int] = mapped_column(ForeignKey('clients.id'), unique=True, nullable=False, comment='ID клиента')
client: Mapped[Client] = relationship('Client', back_populates='details', cascade='all, delete', uselist=False)
telegram = Column(String)
phone_number = Column(String)
inn = Column(String)
email = Column(String)
telegram: Mapped[Optional[str]] = mapped_column()
phone_number: Mapped[Optional[str]] = mapped_column()
inn: Mapped[Optional[str]] = mapped_column()
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 = 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()
#
modified_by_user_id: Mapped[int] = mapped_column(ForeignKey('users.id'), nullable=False)
modified_by_user: Mapped['User'] = relationship('User')

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.orm import relationship, Mapped
from sqlalchemy.testing.schema import mapped_column
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, Mapped, mapped_column
from models import BaseModel
from models.base import BaseModel
if TYPE_CHECKING:
from models import Marketplace
from models import Client, BarcodeTemplate, WildberriesProduct, OzonProduct
class Product(BaseModel):
__tablename__ = 'products'
id = Column(Integer, autoincrement=True, primary_key=True, index=True)
name = Column(String, nullable=False, index=True)
article = Column(String, nullable=False, default='', server_default='', index=True)
factory_article = Column(String, nullable=False, default='', server_default='', index=True)
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(nullable=False, index=True)
article: Mapped[str] = mapped_column(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 = relationship('Client', back_populates='products')
barcodes = relationship('ProductBarcode', back_populates='product', cascade="all, delete-orphan")
client_id: Mapped[int] = mapped_column(ForeignKey('clients.id'), nullable=False)
client: Mapped['Client'] = relationship('Client', back_populates='products')
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 = relationship('BarcodeTemplate', lazy='joined')
barcode_template_id: Mapped[Optional[int]] = mapped_column(ForeignKey('barcode_templates.id'), nullable=True)
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
# TODO move to another table
brand = Column(String, nullable=True, comment='Бренд')
color = Column(String, nullable=True, comment='Цвет')
composition = Column(String, nullable=True, comment='Состав')
size = Column(String, nullable=True, comment='Размер')
additional_info = Column(String, nullable=True, comment='Дополнительное поле')
images = relationship('ProductImage',
back_populates='product',
lazy='selectin',
cascade="all, delete-orphan")
brand: Mapped[Optional[str]] = mapped_column(nullable=True, comment='Бренд')
color: Mapped[Optional[str]] = mapped_column(nullable=True, comment='Цвет')
composition: Mapped[Optional[str]] = mapped_column(nullable=True, comment='Состав')
size: Mapped[Optional[str]] = mapped_column(nullable=True, comment='Размер')
additional_info: Mapped[Optional[str]] = mapped_column(nullable=True, comment='Дополнительное поле')
images: Mapped[list['ProductImage']] = relationship(
'ProductImage',
back_populates='product',
lazy='selectin',
cascade='all, delete-orphan',
)
wildberries_products = relationship('WildberriesProduct',
back_populates='product',
cascade="all, delete-orphan",
uselist=True)
wildberries_products: Mapped[list['WildberriesProduct']] = relationship(
'WildberriesProduct',
back_populates='product',
cascade='all, delete-orphan',
uselist=True,
)
ozon_products = relationship('OzonProduct',
back_populates='product',
cascade="all, delete-orphan",
uselist=True)
ozon_products: Mapped[list['OzonProduct']] = relationship(
'OzonProduct',
back_populates='product',
cascade='all, delete-orphan',
uselist=True,
)
class ProductImage(BaseModel):
__tablename__ = 'product_images'
id = Column(Integer, autoincrement=True, primary_key=True, index=True)
product_id = Column(Integer, ForeignKey('products.id'), nullable=False)
product: Mapped["Product"] = relationship(back_populates='images')
id: Mapped[int] = mapped_column(primary_key=True)
product_id: Mapped[int] = mapped_column(ForeignKey('products.id'), nullable=False)
product: Mapped['Product'] = relationship(back_populates='images')
image_url = Column(String, nullable=False)
image_url: Mapped[str] = mapped_column(nullable=False)
class ProductBarcode(BaseModel):
__tablename__ = 'product_barcodes'
product_id = Column(Integer, ForeignKey('products.id'), nullable=False, comment='ID товара', primary_key=True)
product: Mapped["Product"] = relationship(back_populates='barcodes')
product_id: Mapped[int] = mapped_column(
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):
__tablename__ = 'product_barcode_images'
product_id = Column(Integer, ForeignKey('products.id'), primary_key=True, comment='ID товара')
product: Mapped["Product"] = relationship(back_populates='barcode_image')
product_id: Mapped[int] = mapped_column(ForeignKey('products.id'), primary_key=True, comment='ID товара')
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 models import BaseModel
from models.base import BaseModel
if TYPE_CHECKING:
from board import Board
from attribute import Attribute
from module import Module
class Project(BaseModel):
__tablename__ = "projects"
__tablename__ = 'projects'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(nullable=False)
created_at: Mapped[datetime] = mapped_column(nullable=False)
is_deleted: Mapped[bool] = mapped_column(default=False)
boards: Mapped[list["Board"]] = relationship(
"Board",
back_populates="project",
lazy="noload",
boards: Mapped[list['Board']] = relationship(
'Board',
back_populates='project',
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,115 +1,138 @@
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 models import Product
from models.base import BaseModel
deal_product_service_employees = Table(
'deal_product_service_employees',
if TYPE_CHECKING:
from models import Card, Service, User
card_product_service_employees = Table(
'card_product_service_employees',
BaseModel.metadata,
Column('deal_id', primary_key=True),
Column('card_id', primary_key=True),
Column('service_id', primary_key=True),
Column('product_id', primary_key=True),
Column('user_id', ForeignKey('users.id'), primary_key=True),
ForeignKeyConstraint(
['deal_id', 'product_id', 'service_id'],
['deal_product_services.deal_id', 'deal_product_services.product_id', 'deal_product_services.service_id']
['card_id', 'product_id', 'service_id'],
['card_product_services.card_id', 'card_product_services.product_id', 'card_product_services.service_id']
)
)
deal_service_employees = Table(
'deal_service_employees',
card_service_employees = Table(
'card_service_employees',
BaseModel.metadata,
Column('deal_id', primary_key=True),
Column('card_id', primary_key=True),
Column('service_id', primary_key=True),
Column('user_id', ForeignKey('users.id'), primary_key=True),
ForeignKeyConstraint(
['deal_id', 'service_id'],
['deal_services.deal_id', 'deal_services.service_id']
['card_id', 'service_id'],
['card_services.card_id', 'card_services.service_id']
)
)
class DealService(BaseModel):
__tablename__ = 'deal_services'
deal_id = Column(Integer, ForeignKey('deals.id'),
nullable=False,
comment='ID Сделки',
primary_key=True)
deal = relationship('Deal', back_populates='services')
class CardService(BaseModel):
__tablename__ = 'card_services'
card_id: Mapped[int] = mapped_column(
ForeignKey('cards.id'),
comment='ID Сделки',
primary_key=True,
)
card: Mapped['Card'] = relationship('Card', back_populates='services')
service_id = Column(Integer, ForeignKey('services.id'), nullable=False, comment='ID Услуги', primary_key=True)
service = relationship('Service')
service_id: Mapped[int] = mapped_column(ForeignKey('services.id'), nullable=False, comment='ID Услуги', primary_key=True)
service: Mapped['Service'] = relationship('Service')
quantity = Column(Integer, nullable=False, comment='Кол-во услуги')
price = Column(Integer, nullable=False, server_default='0', comment='Цена услуги')
quantity: Mapped[int] = mapped_column(nullable=False, 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='Фиксированная цена')
employees = relationship('User', secondary=deal_service_employees)
employees: Mapped[list['User']] = relationship('User', secondary=card_service_employees)
__table_args__ = (
UniqueConstraint('deal_id', 'service_id', name='uix_deal_service'),
UniqueConstraint('card_id', 'service_id', name='uix_card_service'),
)
class DealProductService(BaseModel):
__tablename__ = 'deal_product_services'
deal_id = Column(Integer, primary_key=True, nullable=False, comment='ID Сделки')
class CardProductService(BaseModel):
__tablename__ = 'card_product_services'
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='Фиксированная цена')
deal_product = relationship('DealProduct',
back_populates='services',
primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, "
"DealProductService.product_id == DealProduct.product_id)",
foreign_keys=[deal_id, product_id])
card_product: Mapped['CardProduct'] = relationship(
'CardProduct',
back_populates='services',
primaryjoin="and_(CardProductService.card_id == CardProduct.card_id, "
"CardProductService.product_id == CardProduct.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__ = (
ForeignKeyConstraint(
['deal_id', 'product_id'],
['deal_products.deal_id', 'deal_products.product_id']
['card_id', 'product_id'],
['card_products.card_id', 'card_products.product_id']
),
)
class DealProduct(BaseModel):
__tablename__ = 'deal_products'
deal_id = Column(Integer, ForeignKey('deals.id'), primary_key=True, nullable=False, comment='ID Сделки')
product_id = Column(Integer, ForeignKey('products.id'), primary_key=True, nullable=False, comment='ID Продукта')
quantity = Column(Integer, nullable=False, comment='Кол-во продукта')
comment = Column(String, nullable=False, server_default='', comment='Комментарий к товару')
class CardProduct(BaseModel):
__tablename__ = 'card_products'
card_id: Mapped[int] = mapped_column(ForeignKey('cards.id'), primary_key=True, nullable=False, comment='ID карточки')
product_id: Mapped[int] = mapped_column(
ForeignKey('products.id'),
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',
back_populates='products',
foreign_keys=[deal_id])
product = relationship(
card: Mapped['Card'] = relationship(
'Card',
back_populates='products',
foreign_keys=[card_id],
)
product: Mapped['Product'] = relationship(
'Product',
lazy='joined',
foreign_keys=[product_id],
)
services = relationship('DealProductService',
back_populates='deal_product',
cascade="all, delete-orphan",
primaryjoin="and_(DealProductService.deal_id == DealProduct.deal_id, "
"DealProductService.product_id == DealProduct.product_id)",
foreign_keys=[DealProductService.deal_id, DealProductService.product_id],
lazy='selectin',
order_by="desc(DealProductService.service_id)"
)
services: Mapped[list['CardProductService']] = relationship(
'CardProductService',
back_populates='card_product',
cascade="all, delete-orphan",
primaryjoin="and_(CardProductService.card_id == CardProduct.card_id, "
"CardProductService.product_id == CardProduct.product_id)",
foreign_keys=[CardProductService.card_id, CardProductService.product_id],
lazy='selectin',
order_by="desc(CardProductService.service_id)"
)
barcode_template_attribute_link = Table(

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

View File

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

View File

@@ -1,16 +1,17 @@
from datetime import datetime
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 models import BaseModel
if TYPE_CHECKING:
from models import Board
from models import Board, Card, User
class DealStatus(BaseModel):
__tablename__ = "deal_statuses"
class CardStatus(BaseModel):
__tablename__ = 'card_statuses'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(nullable=False)
@@ -19,43 +20,44 @@ class DealStatus(BaseModel):
is_deleted: Mapped[bool] = mapped_column(default=False, 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):
__tablename__ = 'deals_status_history'
id = Column(Integer, autoincrement=True, primary_key=True, index=True)
class CardStatusHistory(BaseModel):
__tablename__ = 'cards_status_history'
id: Mapped[int] = mapped_column(primary_key=True)
deal_id = Column(Integer, ForeignKey('deals.id'), nullable=False, comment='ID сделки')
deal = relationship('Deal', back_populates='status_history')
card_id: Mapped[int] = mapped_column(ForeignKey('cards.id'), nullable=False, comment='ID карточки')
card: Mapped['Card'] = relationship('Card', back_populates='status_history')
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
user = relationship('User')
user_id: Mapped[int] = mapped_column(ForeignKey('users.id'), nullable=False)
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(
ForeignKey('deal_statuses.id'),
ForeignKey('card_statuses.id'),
nullable=False,
comment='Предыдущий статус',
)
from_status: Mapped[DealStatus] = relationship(
'DealStatus',
from_status: Mapped[CardStatus] = relationship(
'CardStatus',
foreign_keys=[from_status_id],
lazy='joined',
)
to_status_id: Mapped[int] = mapped_column(
ForeignKey('deal_statuses.id'),
ForeignKey('card_statuses.id'),
nullable=False,
comment='Новый статус',
)
to_status: Mapped[DealStatus] = relationship(
'DealStatus',
to_status: Mapped[CardStatus] = relationship(
'CardStatus',
foreign_keys=[to_status_id],
lazy='joined',
)
next_status_deadline = Column(DateTime,
comment='Дедлайн до которого сделку нужно перевести на следующий этап')
comment = Column(String, nullable=False, comment='Коментарий', server_default='')
next_status_deadline: Mapped[datetime] = mapped_column(
comment='Дедлайн до которого сделку нужно перевести на следующий этап',
)
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 models import ProductBarcode, Product, ShippingWarehouse, BaseMarketplace
from schemas.deal import ParsedProductRowSchema, ParseDealsExcelResponse, ParsedCityBreakdownSchema, \
from schemas.card import ParsedProductRowSchema, ParseCardsExcelResponse, ParsedCityBreakdownSchema, \
OptionalShippingWarehouseSchema
from schemas.marketplace import MarketplaceSchema, BaseMarketplaceSchema
from schemas.product import ProductSchema
@@ -133,7 +133,7 @@ class DealParser:
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.start()
@@ -144,4 +144,4 @@ class DealParser:
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 .deal import deal_router
from .group import deal_group_router
from .card import card_router
from .group import card_group_router
from .client import client_router
from .service import service_router
from .product import product_router

View File

@@ -29,34 +29,34 @@ async def webhook(
@billing_router.post(
'/create-deal-bill',
operation_id='create_deal_bill',
response_model=CreateDealBillResponse
response_model=CreateCardBillResponse
)
async def create_deal_bill(
session: SessionDependency,
request: CreateDealBillRequest,
request: CreateCardBillRequest,
user: CurrentUserDependency
):
return await BillingService(session).create_deal_billing(user, request)
return await BillingService(session).create_card_billing(user, request)
@billing_router.post(
'/cancel-deal-bill',
operation_id='cancel_deal_bill',
response_model=CancelDealBillResponse
response_model=CancelCardBillResponse
)
async def cancel_deal_billing(
session: SessionDependency,
request: CancelDealBillRequest,
request: CancelCardBillRequest,
user: CurrentUserDependency
):
return await BillingService(session).cancel_deal_billing(user, request)
return await BillingService(session).cancel_card_billing(user, request)
@billing_router.get(
'/deal-bill-request/{deal_id}',
response_model=GetDealBillById,
response_model=GetCardBillById,
operation_id='get_deal_bill_by_id'
)
async def get_deal_bill_by_id(
deal_id: int,
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 models import User
from parsers import DealParser
from schemas.barcode import GetDealProductsBarcodesPdfRequest, GetDealProductsBarcodesPdfResponse
from schemas.deal import *
from schemas.barcode import GetCardProductsBarcodesPdfRequest, GetCardProductsBarcodesPdfResponse
from schemas.card import *
from services.auth import get_current_user, authorized_user, guest_user
from services.barcode import BarcodeService
from services.billing import BillingService
from services.deal import DealService
from services.card import CardsService
deal_router = APIRouter(
prefix='/deal',
tags=['deal'],
card_router = APIRouter(
prefix='/card',
tags=['card'],
)
# region Deal
# region Card
@deal_router.post(
@card_router.post(
'/delete',
response_model=DealDeleteResponse,
operation_id='deleteDeal',
response_model=CardDeleteResponse,
operation_id='deleteCard',
dependencies=[Depends(authorized_user)]
)
async def delete(
request: DealDeleteRequest,
request: CardDeleteRequest,
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',
response_model=DealCompleteResponse,
operation_id='completeDeal',
response_model=CardCompleteResponse,
operation_id='completeCard',
dependencies=[Depends(authorized_user)]
)
async def complete(
request: DealCompleteRequest,
request: CardCompleteRequest,
session: SessionDependency,
user: CurrentUserDependency
):
return await DealService(session).complete(user, request)
return await CardsService(session).complete(user, request)
@deal_router.post(
@card_router.post(
'/quickCreate',
response_model=DealQuickCreateResponse,
response_model=CardQuickCreateResponse,
dependencies=[Depends(authorized_user)]
)
async def quick_create(
request: DealQuickCreateRequest,
request: CardQuickCreateRequest,
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealChangeStatusResponse,
response_model=CardChangeStatusResponse,
dependencies=[Depends(authorized_user)]
)
async def change_status(
request: DealChangeStatusRequest,
request: CardChangeStatusRequest,
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealSummaryResponse,
operation_id='getDealSummaries',
response_model=CardSummaryResponse,
operation_id='getCardSummaries',
dependencies=[Depends(authorized_user)]
)
async def get_summary(
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealSummaryResponse,
operation_id='reorderDealSummaries',
response_model=CardSummaryResponse,
operation_id='reorderCardSummaries',
dependencies=[Depends(authorized_user)]
)
async def reorder(
session: Annotated[AsyncSession, Depends(get_session)],
request: DealSummaryReorderRequest,
request: CardSummaryReorderRequest,
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',
response_model=DealGetAllResponse,
operation_id='getAllDeals',
response_model=CardGetAllResponse,
operation_id='getAllCards',
dependencies=[Depends(authorized_user)]
)
async def get_all(
session: Annotated[AsyncSession, Depends(get_session)]
):
return await DealService(session).get_all()
return await CardsService(session).get_all()
# endpoint to get deal by id
@deal_router.get(
'/get/{deal_id}',
response_model=DealSchema,
operation_id='getDealById',
# endpoint to get card by id
@card_router.get(
'/get/{card_id}',
response_model=CardSchema,
operation_id='getCardById',
dependencies=[Depends(guest_user)]
)
async def get_deal_by_id(
deal_id: int,
async def get_card_by_id(
card_id: int,
user: CurrentUserDependency,
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',
response_model=DealUpdateGeneralInfoResponse,
operation_id='updateDealGeneralInfo',
response_model=CardUpdateGeneralInfoResponse,
operation_id='updateCardGeneralInfo',
dependencies=[Depends(authorized_user)]
)
async def update_general_info(
request: DealUpdateGeneralInfoRequest,
request: CardUpdateGeneralInfoRequest,
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealAddKitResponse,
operation_id='add_kit_to_deal'
response_model=CardAddKitResponse,
operation_id='add_kit_to_card'
)
async def add_kit_to_deal(
async def add_kit_to_card(
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',
response_model=DealCreateGuestUrlResponse,
response_model=CardCreateGuestUrlResponse,
operation_id='create_deal_guest_url',
dependencies=[Depends(authorized_user)]
)
async def create_guest_url(
session: SessionDependency,
request: DealCreateGuestUrlRequest,
request: CardCreateGuestUrlRequest,
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}',
operation_id='get_billing_document',
# dependencies=[Depends(authorized_user)],
@@ -186,7 +185,7 @@ async def get_billing_document(
return Response(pdf_file.getvalue(), media_type='application/pdf')
@deal_router.get(
@card_router.get(
'/tech-spec/{deal_id}',
operation_id='get_deal_tech_spec',
# dependencies=[Depends(authorized_user)],
@@ -199,33 +198,33 @@ async def get_deal_tech_spec(
return Response(pdf_file.getvalue(), media_type='application/pdf')
@deal_router.post(
@card_router.post(
'/prefill',
response_model=DealPrefillResponse,
operation_id='prefill_deal',
response_model=CardPrefillResponse,
operation_id='prefill_card',
dependencies=[Depends(authorized_user)]
)
async def post_prefill_deal(
async def post_prefill_card(
session: SessionDependency,
request: DealPrefillRequest,
request: CardPrefillRequest,
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',
response_model=DealRecalculatePriceResponse,
operation_id='recalculate_deal_price',
response_model=CardRecalculatePriceResponse,
operation_id='recalculate_card_price',
)
async def recalculate_deal_price(
async def recalculate_card_price(
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',
response_model=ManageEmployeeResponse,
operation_id='manage_employee',
@@ -234,24 +233,24 @@ async def manage_employee(
session: SessionDependency,
request: ManageEmployeeRequest,
):
return await DealService(session).manage_employee(request)
return await CardsService(session).manage_employee(request)
@deal_router.get(
'/employee/available/{deal_id}',
@card_router.get(
'/employee/available/{card_id}',
response_model=GetAvailableEmployeesToAssignResponse,
operation_id='get_available_employees_to_assign',
)
async def get_available_employees_to_assign(
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',
response_model=ParseDealsExcelResponse,
response_model=ParseCardsExcelResponse,
operation_id='parse_deals_excel',
)
async def parse_deals_excel(
@@ -262,221 +261,220 @@ async def parse_deals_excel(
return await DealParser(session).parse(file_bytes)
@deal_router.post(
@card_router.post(
'/prefill/excel/create',
response_model=CreateDealsFromExcelResponse,
response_model=CreateCardsFromExcelResponse,
operation_id='create_deals_excel',
)
async def create_deals_from_excel(
session: Annotated[AsyncSession, Depends(get_session)],
request: CreateDealsFromExcelRequest,
request: CreateCardsFromExcelRequest,
user: CurrentUserDependency,
):
return await DealService(session).create_deals_from_excel(request, user)
return await CardsService(session).create_cards_from_excel(request, user)
# endregion
# region Deal services
# region Card services
@deal_router.post(
@card_router.post(
'/services/add/multiple',
response_model=DealAddServicesResponse,
operation_id='addMultipleDealServices',
response_model=CardAddServicesResponse,
operation_id='add_multiple_card_services',
dependencies=[Depends(guest_user)]
)
async def services_add(
request: DealAddServicesRequest,
request: CardAddServicesRequest,
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',
response_model=DealAddServiceResponse,
operation_id='addDealService',
response_model=CardAddServiceResponse,
operation_id='add_card_service',
dependencies=[Depends(guest_user)]
)
async def services_add(
request: DealAddServiceRequest,
request: CardAddServiceRequest,
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealUpdateServiceQuantityResponse,
operation_id='updateDealServiceQuantity',
response_model=CardUpdateServiceQuantityResponse,
operation_id='update_card_service_quantity',
dependencies=[Depends(guest_user)]
)
async def services_update_quantity(
request: DealUpdateServiceQuantityRequest,
request: CardUpdateServiceQuantityRequest,
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealUpdateServiceResponse,
operation_id='updateDealService',
response_model=CardUpdateServiceResponse,
operation_id='update_card_service',
dependencies=[Depends(guest_user)]
)
async def services_update(
request: DealUpdateServiceRequest,
request: CardUpdateServiceRequest,
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealDeleteServiceResponse,
operation_id='deleteDealService',
response_model=CardDeleteServiceResponse,
operation_id='delete_card_service',
dependencies=[Depends(guest_user)]
)
async def services_delete(
request: DealDeleteServiceRequest,
request: CardDeleteServiceRequest,
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealDeleteServicesResponse,
operation_id='deleteMultipleDealServices',
response_model=CardDeleteServicesResponse,
operation_id='delete_multiple_card_services',
dependencies=[Depends(guest_user)]
)
async def services_delete(
request: DealDeleteServicesRequest,
request: CardDeleteServicesRequest,
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealServicesCopyResponse,
response_model=CardServicesCopyResponse,
operation_id='copy_product_services',
dependencies=[Depends(guest_user)]
)
async def services_copy(
session: SessionDependency,
request: DealServicesCopyRequest,
request: CardServicesCopyRequest,
user: CurrentUserDependency
):
return await DealService(session).copy_services(user, request)
return await CardsService(session).copy_services(user, request)
# endregion
# region Deal products
@deal_router.post(
# region Card products
@card_router.post(
'/products/update-quantity',
response_model=DealUpdateProductQuantityResponse,
operation_id='updateDealProductQuantity',
response_model=CardUpdateProductQuantityResponse,
operation_id='update_card_product_quantity',
dependencies=[Depends(guest_user)]
)
async def products_update(
request: DealUpdateProductQuantityRequest,
request: CardUpdateProductQuantityRequest,
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealAddProductResponse,
operation_id='addDealProduct',
response_model=CardAddProductResponse,
operation_id='add_card_product',
dependencies=[Depends(guest_user)]
)
async def products_add(
request: DealAddProductRequest,
request: CardAddProductRequest,
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealDeleteProductResponse,
operation_id='deleteDealProduct',
response_model=CardDeleteProductResponse,
operation_id='delete_card_product',
dependencies=[Depends(guest_user)]
)
async def products_delete(
request: DealDeleteProductRequest,
request: CardDeleteProductRequest,
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealDeleteProductsResponse,
operation_id='deleteMultipleDealProducts',
response_model=CardDeleteProductsResponse,
operation_id='delete_multiple_card_products',
dependencies=[Depends(guest_user)]
)
async def products_delete(
request: DealDeleteProductsRequest,
request: CardDeleteProductsRequest,
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealUpdateProductResponse,
operation_id='updateDealProduct',
response_model=CardUpdateProductResponse,
operation_id='update_card_product',
dependencies=[Depends(guest_user)]
)
async def products_update(
request: DealUpdateProductRequest,
request: CardUpdateProductRequest,
session: Annotated[AsyncSession, Depends(get_session)],
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',
response_model=DealProductAddKitResponse,
operation_id='add_kit_to_deal_product',
response_model=CardProductAddKitResponse,
operation_id='add_kit_to_card_product',
dependencies=[Depends(guest_user)]
)
async def add_kit_to_deal_product(
async def add_kit_to_card_product(
session: SessionDependency,
request: DealProductAddKitRequest,
request: CardProductAddKitRequest,
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',
operation_id='get_deal_products_barcodes_pdf',
response_model=GetDealProductsBarcodesPdfResponse
operation_id='get_card_products_barcodes_pdf',
response_model=GetCardProductsBarcodesPdfResponse
)
async def get_deal_products_barcodes_pdf(
request: GetDealProductsBarcodesPdfRequest,
async def get_card_products_barcodes_pdf(
request: GetCardProductsBarcodesPdfRequest,
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
base64_string = base64.b64encode(pdf_buffer.read()).decode('utf-8')
return GetDealProductsBarcodesPdfResponse(
return GetCardProductsBarcodesPdfResponse(
base64_string=base64_string,
filename=filename,
mime_type='application/pdf'

View File

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

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
class GetDealProductsBarcodesPdfRequest(BaseSchema):
deal_id: int
class GetCardProductsBarcodesPdfRequest(BaseSchema):
card_id: int
# endregion
@@ -131,7 +131,7 @@ class GetProductBarcodePdfResponse(BaseSchema):
mime_type: str
class GetDealProductsBarcodesPdfResponse(BaseSchema):
class GetCardProductsBarcodesPdfResponse(BaseSchema):
base64_string: str
filename: str
mime_type: str

View File

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

View File

@@ -13,7 +13,7 @@ class BaseBoardSchema(BaseSchema):
class BoardSchema(BaseBoardSchema):
id: int
ordinal_number: int
deal_statuses: list[StatusSchema]
statuses: list[StatusSchema]
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
class DealGroupSchema(BaseSchema):
class CardGroupSchema(BaseSchema):
id: int
name: Optional[str] = None
lexorank: str
@@ -17,50 +17,50 @@ class DealGroupSchema(BaseSchema):
# region Requests
class DealGroupUpdateRequest(BaseSchema):
data: DealGroupSchema
class CardGroupUpdateRequest(BaseSchema):
data: CardGroupSchema
class DealCreateGroupRequest(BaseSchema):
dragging_deal_id: int
hovered_deal_id: int
class CreateCardGroupRequest(BaseSchema):
dragging_card_id: int
hovered_card_id: int
class DealGroupChangeStatusRequest(BaseSchema):
class CardGroupChangeStatusRequest(BaseSchema):
group_id: int
new_status: int
class DealAddToGroupRequest(BaseSchema):
deal_id: int
class CardAddToGroupRequest(BaseSchema):
card_id: int
group_id: int
class DealRemoveFromGroupRequest(BaseSchema):
deal_id: int
class CardRemoveFromGroupRequest(BaseSchema):
card_id: int
# endregion
# region Responses
class DealCreateGroupResponse(OkMessageSchema):
class CardCreateGroupResponse(OkMessageSchema):
pass
class DealGroupUpdateResponse(OkMessageSchema):
class CardGroupUpdateResponse(OkMessageSchema):
pass
class DealGroupChangeStatusResponse(OkMessageSchema):
class CardGroupChangeStatusResponse(OkMessageSchema):
pass
class DealAddToGroupResponse(OkMessageSchema):
class CardAddToGroupResponse(OkMessageSchema):
pass
class DealRemoveFromGroupResponse(OkMessageSchema):
class CardRemoveFromGroupResponse(OkMessageSchema):
pass
# 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.module import ModuleSchema
# region Entities
@@ -10,9 +12,11 @@ class BaseProjectSchema(BaseSchema):
class ProjectSchema(BaseProjectSchema):
id: int
attributes: list[AttributeSchema]
modules: list[ModuleSchema]
class ProjectSchemaWithCount(ProjectSchema):
class FullProjectSchema(ProjectSchema):
boards_count: int
@@ -27,13 +31,14 @@ class CreateProjectRequest(BaseSchema):
class UpdateProjectRequest(BaseSchema):
project: ProjectSchema
# endregion
# region Responses
class GetProjectsResponse(BaseSchema):
projects: list[ProjectSchemaWithCount]
projects: list[FullProjectSchema]
class CreateProjectResponse(OkMessageSchema):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
import logging
from io import BytesIO
from typing import List
from uuid import uuid4
@@ -16,24 +15,24 @@ from constants import MONTHS, ENV
from external.billing import BillingClient, CreateBillingRequestValue, CreateBillRequestSchema, CreateBillRequestItems, \
BillStatusUpdateRequest, NotificationChannel, NotifyReceivedBillRequestSchema, DeleteBillRequestSchema, \
ProductBillingDocumentPdf, ServiceBillingDocumentPdf
from models import DealBillRequest, Deal, DealProduct, DealService as DealServiceModel, DealProductService, DealGroup, \
from models import CardBillRequest, Card, CardProduct, CardService as CardServiceModel, CardGroup, \
GroupBillRequest
from schemas.billing import *
from services.base import BaseService
from services.deal import DealService
from services.card import CardsService
from utils.list_utils import to_locale_number
class BillingService(BaseService):
async def _process_deal_update_details(
async def _process_card_update_details(
self,
request: BillStatusUpdateRequest,
):
deal_bill_request = await self._get_deal_bill_by_id(request.listener_transaction_id)
if not deal_bill_request:
bill_request = await self._get_card_bill_by_id(request.listener_transaction_id)
if not bill_request:
return
deal_bill_request.pdf_url = request.info.pdf_url
deal_bill_request.invoice_number = request.info.invoice_number
bill_request.pdf_url = request.info.pdf_url
bill_request.invoice_number = request.info.invoice_number
async def _process_group_update_details(
self,
@@ -63,19 +62,19 @@ class BillingService(BaseService):
if not response.ok:
return
if type(request.listener_transaction_id) is int:
await self._process_deal_update_details(request)
await self._process_card_update_details(request)
else:
await self._process_group_update_details(request)
await self.session.commit()
async def _process_deal_update_verification(
async def _process_card_update_verification(
self,
request: BillStatusUpdateRequest
):
deal_bill_request = await self._get_deal_bill_by_id(request.listener_transaction_id)
if not deal_bill_request:
card_bill_request = await self._get_card_bill_by_id(request.listener_transaction_id)
if not card_bill_request:
return
deal_bill_request.paid = request.info.payed
card_bill_request.paid = request.info.payed
async def _process_group_update_verification(
self,
@@ -104,7 +103,7 @@ class BillingService(BaseService):
if not response.ok:
return
if type(request.listener_transaction_id) is int:
await self._process_deal_update_verification(request)
await self._process_card_update_verification(request)
else:
await self._process_group_update_verification(request)
await self.session.commit()
@@ -118,37 +117,37 @@ class BillingService(BaseService):
elif request.channel == NotificationChannel.PAYMENT_VERIFICATION:
await self._process_update_verification(request)
async def create_deal_bill_request(self, deal: Deal):
deal_bill_request = DealBillRequest(
deal_id=deal.id,
async def create_card_bill_request(self, card: Card):
card_bill_request = CardBillRequest(
card_id=card.id,
created_at=datetime.datetime.now()
)
self.session.add(deal_bill_request)
deal.is_locked = True
self.session.add(card_bill_request)
card.is_locked = True
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_id=group.id,
created_at=datetime.datetime.now()
)
self.session.add(group_bill_request)
for deal in group.deals:
deal.is_locked = True
for card in group.cards:
card.is_locked = True
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:
deal_service = DealService(self.session)
card_service = CardsService(self.session)
billing_client = BillingClient(backend.config.BILLING_API_KEY)
basic_deal: Deal = await deal_service.get_by_id(user, request.deal_id, return_raw=True)
if basic_deal.group:
deals = await self._get_deals_by_group_id(basic_deal.group.id)
basic_card: Card = await card_service.get_by_id(user, request.card_id, return_raw=True)
if basic_card.group:
cards = await self._get_cards_by_group_id(basic_card.group.id)
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]
products: dict[str, ProductBillingDocumentPdf]
is_size_needed: bool
@@ -163,72 +162,72 @@ class BillingService(BaseService):
)
)
deal = basic_deal
listener_transaction_id = deal.id
if deal.group:
listener_transaction_id = f"group-{basic_deal.group.id}"
inn: str = deal.client.details.inn
card = basic_card
listener_transaction_id = card.id
if card.group:
listener_transaction_id = f"group-{basic_card.group.id}"
inn: str = card.client.details.inn
create_bill_request = CreateBillRequestSchema(
listener_transaction_id=listener_transaction_id,
payer_name=deal.client.name,
payer_name=card.client.name,
payer_inn=inn.strip(),
payer_phone=deal.client.details.phone_number,
payer_phone=card.client.details.phone_number,
items=CreateBillRequestItems(
values=billing_request_values
)
)
create_bill_response = await billing_client.create(create_bill_request)
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:
await self.create_group_bill_request(basic_deal.group)
if basic_card.group:
await self.create_group_bill_request(basic_card.group)
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:
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]:
return await self.session.scalar(select(DealBillRequest).where(DealBillRequest.deal_id == deal_id))
async def _get_card_bill_by_id(self, card_id: int) -> Optional[CardBillRequest]:
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]:
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:
deal_bill = await self._get_deal_bill_by_id(deal_id)
if not deal_bill:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Deal bill was not found')
return GetDealBillById(deal_bill=DealBillRequestSchema.model_validate(deal_bill))
async def get_card_bill_by_id(self, card_id: int) -> GetCardBillById:
bill = await self._get_card_bill_by_id(card_id)
if not bill:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Card bill was not found')
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:
deal = await self._get_deal_by_id(request.deal_id)
if not deal:
return CancelDealBillResponse(ok=False, message='Сделка не найдена')
card = await self._get_card_by_id(request.card_id)
if not card:
return CancelCardBillResponse(ok=False, message='Сделка не найдена')
if deal.group:
bill = await self._get_group_bill_by_id(deal.group.id)
if card.group:
bill = await self._get_group_bill_by_id(card.group.id)
if not bill:
return CancelDealBillResponse(ok=False, message='Заявка не найдена')
return CancelCardBillResponse(ok=False, message='Заявка не найдена')
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:
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:
return CancelDealBillResponse(ok=False, message='Заявка не найдена')
return CancelCardBillResponse(ok=False, message='Заявка не найдена')
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:
return CancelDealBillResponse(ok=False, message='Ошибка')
return CancelCardBillResponse(ok=False, message='Ошибка')
await self.session.delete(bill)
await self.session.commit()
return CancelDealBillResponse(ok=True, message='Заявка успешно отозвана')
return CancelCardBillResponse(ok=True, message='Заявка успешно отозвана')
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:
return f"{service.name}-{service.price}"
@@ -237,13 +236,13 @@ class BillingService(BaseService):
article = product.article if product.article else uuid4()
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] = {}
products: dict[str, ProductBillingDocumentPdf] = {}
is_size_needed: bool = False
for deal in deals:
for product in deal.products:
for card in cards:
for product in card.products:
product_price = 0
for service in product.services:
service_data = ServiceBillingDocumentPdf(
@@ -269,7 +268,7 @@ class BillingService(BaseService):
products[product_key].quantity += product_data.quantity
else:
products[product_key] = product_data
for service in deal.services:
for service in card.services:
service_data = ServiceBillingDocumentPdf(
name=service.service.name,
price=service.price,
@@ -283,49 +282,49 @@ class BillingService(BaseService):
return services, products, is_size_needed
async def _get_deal_by_id(self, deal_id: int) -> Optional[Deal]:
deal: Deal | None = await self.session.scalar(
select(Deal)
.where(Deal.id == deal_id)
async def _get_card_by_id(self, card_id: int) -> Optional[Card]:
card: Card | None = await self.session.scalar(
select(Card)
.where(Card.id == card_id)
.options(
selectinload(Deal.products).selectinload(DealProduct.services),
selectinload(Deal.services).selectinload(DealServiceModel.service),
joinedload(Deal.shipping_warehouse),
joinedload(Deal.client),
selectinload(Deal.group).selectinload(DealGroup.deals),
selectinload(Card.products).selectinload(CardProduct.services),
selectinload(Card.services).selectinload(CardServiceModel.service),
joinedload(Card.shipping_warehouse),
joinedload(Card.client),
selectinload(Card.group).selectinload(CardGroup.cards),
)
)
return deal
return card
async def _get_deals_by_group_id(self, group_id: int) -> List[Deal]:
group: DealGroup | None = await self.session.scalar(
select(DealGroup)
.where(DealGroup.id == group_id)
async def _get_cards_by_group_id(self, group_id: int) -> List[Card]:
group: CardGroup | None = await self.session.scalar(
select(CardGroup)
.where(CardGroup.id == group_id)
.options(
selectinload(DealGroup.deals).selectinload(Deal.products).selectinload(DealProduct.services),
selectinload(DealGroup.deals).selectinload(Deal.services).selectinload(DealServiceModel.service),
selectinload(DealGroup.deals).joinedload(Deal.shipping_warehouse),
selectinload(DealGroup.deals).joinedload(Deal.client),
selectinload(DealGroup.deals).selectinload(Deal.group).selectinload(DealGroup.deals),
selectinload(CardGroup.cards).selectinload(Card.products).selectinload(CardProduct.services),
selectinload(CardGroup.cards).selectinload(Card.services).selectinload(CardServiceModel.service),
selectinload(CardGroup.cards).joinedload(Card.shipping_warehouse),
selectinload(CardGroup.cards).joinedload(Card.client),
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):
deal = await self._get_deal_by_id(deal_id)
if not deal:
async def _create_billing_document_html(self, card_id: int):
card = await self._get_card_by_id(card_id)
if not card:
return ""
if deal.group:
deals = await self._get_deals_by_group_id(deal.group.id)
if card.group:
cards = await self._get_cards_by_group_id(card.group.id)
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()))
deal_price_words = get_string_by_number(deal_price)[0:-10]
deal_price = to_locale_number(deal_price)
price = sum((service.price * service.quantity for service in services.values()))
price_words = get_string_by_number(price)[0:-10]
price = to_locale_number(price)
template = ENV.get_template("bill-of-payment.html")
now = datetime.datetime.now()
@@ -334,14 +333,14 @@ class BillingService(BaseService):
"products": products,
"services": services,
"is_size_needed": is_size_needed,
"deal_price": deal_price,
"deal_price_words": deal_price_words,
"deal": deal,
"deal_price": price,
"deal_price_words": price_words,
"deal": card,
"curr_date": curr_date
})
async def create_billing_document_pdf(self, deal_id) -> BytesIO:
doc = await self._create_billing_document_html(deal_id)
async def create_billing_document_pdf(self, card_id) -> BytesIO:
doc = await self._create_billing_document_html(card_id)
pdf_file = BytesIO()
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 models import Board, Deal
from models import Board, Card
from schemas.board import *
from services.base import BaseService
@@ -76,12 +76,12 @@ class BoardService(BaseService):
async def _count_deals_in_progress(self, board_id: int) -> int:
stmt = (
select(func.count(Deal.id))
select(func.count(Card.id))
.where(
and_(
Deal.board_id == board_id,
Deal.is_deleted == False,
Deal.is_completed == False,
Card.board_id == board_id,
Card.is_deleted == False,
Card.is_completed == False,
)
)
)
@@ -89,8 +89,8 @@ class BoardService(BaseService):
async def _count_deals(self, board_id: int) -> int:
stmt = (
select(func.count(Deal.id))
.where(Deal.board_id == board_id)
select(func.count(Card.id))
.where(Card.board_id == board_id)
)
return (await self.session.scalars(stmt)).first()
@@ -111,7 +111,7 @@ class BoardService(BaseService):
await self.session.delete(board)
else:
board.is_deleted = True
for status in board.deal_statuses:
for status in board.statuses:
status.is_deleted = True
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 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 schemas.project import *
@@ -9,28 +10,37 @@ from services.base import BaseService
class ProjectService(BaseService):
async def get_projects(self) -> GetProjectsResponse:
boards_sub = (
select(Board)
.where(Board.is_deleted == False)
board_count_sub = (
select(
Board.project_id,
func.count(Board.id).label('boards_count'),
)
.group_by(Board.project_id)
.subquery()
)
stmt = (
select(
Project.id,
Project.name,
func.count(boards_sub.c.id)
Project,
func.coalesce(board_count_sub.c.boards_count, 0),
)
.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()
projects = []
for project_id, name, boards_count in project_data:
project = ProjectSchemaWithCount(id=project_id, name=name, boards_count=boards_count)
projects.append(project)
for project, boards_count in project_data:
project_schema = FullProjectSchema(
id=project.id,
name=project.name,
boards_count=boards_count,
attributes=project.attributes,
modules=project.modules,
)
projects.append(project_schema)
return GetProjectsResponse(projects=projects)

View File

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

View File

@@ -1,18 +1,18 @@
from sqlalchemy import select, and_
from models import Deal, Pallet, Box
from models import Card, Pallet, Box
from models.shipping import ShippingProduct
from schemas.shipping import *
from services.base import BaseService
class ShippingService(BaseService):
async def create_pallet(self, deal_id: int) -> CreatePalletResponse:
deal = await self.session.get(Deal, deal_id)
async def create_pallet(self, card_id: int) -> CreatePalletResponse:
deal = await self.session.get(Card, card_id)
if not deal:
return CreatePalletResponse(ok=False, message="Сделка не найдена")
pallet = Pallet(deal_id=deal_id)
pallet = Pallet(card_id=card_id)
self.session.add(pallet)
await self.session.commit()
return CreatePalletResponse(ok=True, message="Паллет успешно создан")
@@ -36,19 +36,19 @@ class ShippingService(BaseService):
await self.session.commit()
return True, "Короб обновлен"
async def _create_box(self, data: CreateBoxInDealSchema | CreateBoxInPalletSchema):
async def _create_box(self, data: CreateBoxInCardSchema | CreateBoxInPalletSchema):
box = Box(**data.model_dump())
self.session.add(box)
await self.session.commit()
async def _create_box_in_deal(self, data: CreateBoxInDealSchema) -> tuple[bool, str]:
deal = await self.session.get(Deal, data.deal_id)
if not deal:
return False, f"Сделка с ID:{data.deal_id} не найдена"
async def _create_box_in_card(self, data: CreateBoxInCardSchema) -> tuple[bool, str]:
card = await self.session.get(Card, data.card_id)
if not card:
return False, f"Сделка с ID:{data.card_id} не найдена"
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]:
pallet = await self.session.get(Pallet, data.pallet_id)
@@ -66,7 +66,7 @@ class ShippingService(BaseService):
elif "pallet_id" in data_keys:
ok, message = await self._create_box_in_pallet(CreateBoxInPalletSchema.model_validate(request.data))
else:
ok, message = await self._create_box_in_deal(CreateBoxInDealSchema.model_validate(request.data))
ok, message = await self._create_box_in_card(CreateBoxInCardSchema.model_validate(request.data))
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 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
from schemas.statistics import GetProfitChartDataResponse, GetProfitChartDataRequest, ProfitChartDataItem, \
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):
deals_created_at = (
select(
Deal.id.label('deal_id'),
Card.id.label('deal_id'),
func.date_trunc(
'day',
Deal.created_at,
Card.created_at,
).label('date'),
Deal.current_status_id,
Card.current_status_id,
)
.subquery()
)
@@ -38,22 +38,22 @@ class ProfitStatisticsService(BaseService):
def _get_sub_status_history():
last_statuses = (
select(
DealStatusHistory.deal_id,
func.max(DealStatusHistory.changed_at).label('changed_at')
CardStatusHistory.card_id,
func.max(CardStatusHistory.changed_at).label('changed_at')
)
.group_by(DealStatusHistory.deal_id)
.group_by(CardStatusHistory.card_id)
.subquery()
)
return (
select(
Deal.id.label('deal_id'),
Card.id.label('deal_id'),
func.date_trunc(
'day',
last_statuses.c.changed_at,
).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()
)
@@ -80,7 +80,7 @@ class ProfitStatisticsService(BaseService):
if is_chart:
data_item = ProfitChartDataItem(
date=row.date.date(),
deals_count=row.deals_count,
deals_count=row.cards_count,
profit=row.profit,
revenue=row.revenue,
expenses=row.expenses,
@@ -88,7 +88,7 @@ class ProfitStatisticsService(BaseService):
else:
data_item = ProfitTableDataItem(
grouped_value=row.date.date(),
deals_count=row.deals_count,
deals_count=row.cards_count,
profit=row.profit,
revenue=row.revenue,
expenses=row.expenses,
@@ -100,25 +100,25 @@ class ProfitStatisticsService(BaseService):
def _get_stmt_deal_services(self, sub_filtered_status_history: Subquery):
return (
select(
Deal.id.label("deal_id"),
Card.id.label("deal_id"),
func.date_trunc(
"day",
sub_filtered_status_history.c.date,
).label("date"),
func.sum(DealService.price * DealService.quantity).label("revenue"),
func.sum(DealService.price * DealService.quantity).label("profit"),
func.sum(CardService.price * CardService.quantity).label("revenue"),
func.sum(CardService.price * CardService.quantity).label("profit"),
)
.join(DealService, Deal.id == DealService.deal_id)
.join(Service, DealService.service_id == Service.id)
.join(sub_filtered_status_history, Deal.id == sub_filtered_status_history.c.deal_id)
.join(CardService, Card.id == CardService.card_id)
.join(Service, CardService.service_id == Service.id)
.join(sub_filtered_status_history, Card.id == sub_filtered_status_history.c.deal_id)
.where(
and_(
Deal.is_deleted == False,
Deal.is_accounted == True,
Deal.is_completed == True if self.is_completed_only else True
Card.is_deleted == False,
Card.is_accounted == True,
Card.is_completed == True if self.is_completed_only else True
)
)
.group_by(Deal.id, "date")
.group_by(Card.id, "date")
)
@staticmethod
@@ -127,18 +127,18 @@ class ProfitStatisticsService(BaseService):
select(Board.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
def _apply_filters(request: CommonProfitFilters, stmt_deal_services, stmt_deal_product_services):
if request.client_id != -1:
stmt_deal_services = stmt_deal_services.where(Deal.client_id == request.client_id)
stmt_deal_product_services = stmt_deal_product_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(Card.client_id == request.client_id)
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(
Deal.base_marketplace_key == request.base_marketplace_key)
Card.base_marketplace_key == request.base_marketplace_key)
if request.project_id != -1:
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:
stmt_deal_services = stmt_deal_services.where(Deal.board_id == request.board_id)
stmt_deal_product_services = stmt_deal_product_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(Card.board_id == request.board_id)
if request.deal_status_id != -1:
stmt_deal_services = stmt_deal_services.where(Deal.current_status_id == request.deal_status_id)
if request.card_status_id != -1:
stmt_deal_services = stmt_deal_services.where(Card.current_status_id == request.card_status_id)
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:
stmt_deal_services = stmt_deal_services.where(Deal.manager_id == request.manager_id)
stmt_deal_product_services = stmt_deal_product_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(Card.manager_id == request.manager_id)
return stmt_deal_services, stmt_deal_product_services
def _get_stmt_product_services(self):
return (
select(
Deal.id.label("deal_id"),
func.sum(DealProductService.price * DealProduct.quantity).label("revenue"),
func.sum(DealProductService.price * DealProduct.quantity).label("profit"),
Card.id.label("deal_id"),
func.sum(CardProductService.price * CardProduct.quantity).label("revenue"),
func.sum(CardProductService.price * CardProduct.quantity).label("profit"),
)
.join(DealProduct, Deal.id == DealProduct.deal_id)
.join(CardProduct, Card.id == CardProduct.card_id)
.join(
DealProductService,
CardProductService,
and_(
DealProductService.deal_id == Deal.id,
DealProductService.product_id == DealProduct.product_id,
CardProductService.card_id == Card.id,
CardProductService.product_id == CardProduct.product_id,
)
)
.join(Service, DealProductService.service_id == Service.id)
.join(Service, CardProductService.service_id == Service.id)
.where(
and_(
Deal.is_deleted == False,
Deal.is_accounted == True,
Deal.is_completed == True if self.is_completed_only else True,
Card.is_deleted == False,
Card.is_accounted == True,
Card.is_completed == True if self.is_completed_only else True,
)
)
.group_by(Deal.id)
.group_by(Card.id)
)
@staticmethod
@@ -208,7 +208,7 @@ class ProfitStatisticsService(BaseService):
deals = (
select(
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.profit).label("profit"),
)
@@ -246,12 +246,12 @@ class ProfitStatisticsService(BaseService):
select(
Client.id,
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.profit).label("profit"),
)
.join(Deal, Deal.id == stmt.c.deal_id)
.join(Client, Client.id == Deal.client_id)
.join(Card, Card.id == stmt.c.card_id)
.join(Client, Client.id == Card.client_id)
.group_by(Client.id, Client.name)
)
@@ -261,12 +261,12 @@ class ProfitStatisticsService(BaseService):
select(
Project.id,
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.profit).label("profit"),
)
.join(Deal, Deal.id == stmt.c.deal_id)
.join(Board, Board.id == Deal.board_id)
.join(Card, Card.id == stmt.c.card_id)
.join(Board, Board.id == Card.board_id)
.join(Project, Project.id == Board.project_id)
.group_by(Project.id, Project.name)
)
@@ -277,12 +277,12 @@ class ProfitStatisticsService(BaseService):
select(
Board.id,
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.profit).label("profit"),
)
.join(Deal, Deal.id == stmt.c.deal_id)
.join(Board, Board.id == Deal.board_id)
.join(Card, Card.id == stmt.c.card_id)
.join(Board, Board.id == Card.board_id)
.group_by(Board.id, Board.name)
)
@@ -290,13 +290,13 @@ class ProfitStatisticsService(BaseService):
def _join_and_group_by_statuses(stmt):
return (
select(
Deal.current_status_id.label("grouped_value"),
func.count(stmt.c.deal_id).label("deals_count"),
Card.current_status_id.label("grouped_value"),
func.count(stmt.c.card_id).label("deals_count"),
func.sum(stmt.c.revenue).label("revenue"),
func.sum(stmt.c.profit).label("profit"),
)
.join(Deal, Deal.id == stmt.c.deal_id)
.group_by(Deal.current_status_id)
.join(Card, Card.id == stmt.c.card_id)
.group_by(Card.current_status_id)
)
@staticmethod
@@ -306,12 +306,12 @@ class ProfitStatisticsService(BaseService):
ShippingWarehouse.id,
ShippingWarehouse.name.label("grouped_value"),
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.profit).label("profit"),
)
.join(Deal, Deal.id == stmt.c.deal_id)
.join(ShippingWarehouse, Deal.shipping_warehouse_id == ShippingWarehouse.id)
.join(Card, Card.id == stmt.c.card_id)
.join(ShippingWarehouse, Card.shipping_warehouse_id == ShippingWarehouse.id)
.where(ShippingWarehouse.is_deleted == False)
.group_by(ShippingWarehouse.is_deleted, ShippingWarehouse.id, ShippingWarehouse.name)
)
@@ -321,12 +321,12 @@ class ProfitStatisticsService(BaseService):
return (
select(
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.profit).label("profit"),
)
.join(Deal, Deal.id == stmt.c.deal_id)
.join(BaseMarketplace, Deal.base_marketplace_key == BaseMarketplace.key)
.join(Card, Card.id == stmt.c.card_id)
.join(BaseMarketplace, Card.base_marketplace_key == BaseMarketplace.key)
.group_by(BaseMarketplace.name)
)
@@ -341,12 +341,12 @@ class ProfitStatisticsService(BaseService):
select(
managers.c.id,
(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.profit).label("profit"),
)
.join(Deal, Deal.id == stmt.c.deal_id)
.join(managers, managers.c.id == Deal.manager_id)
.join(Card, Card.id == stmt.c.card_id)
.join(managers, managers.c.id == Card.manager_id)
.group_by(managers.c.id, "grouped_value")
)
@@ -384,7 +384,7 @@ class ProfitStatisticsService(BaseService):
self.is_completed_only = request.is_completed_only
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_product_services = self._get_stmt_product_services()
@@ -425,7 +425,7 @@ class ProfitStatisticsService(BaseService):
self.date_from, self.date_to = request.date_range
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)
@@ -436,12 +436,12 @@ class ProfitStatisticsService(BaseService):
stmt_deal_product_services.c.deal_id,
func.date_trunc(
"day",
Deal.created_at
Card.created_at
).label("date"),
stmt_deal_product_services.c.revenue.label("revenue"),
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(

View File

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