feat: modules, products, services, services kits
This commit is contained in:
8
main.py
8
main.py
@ -1,18 +1,19 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.middleware.gzip import GZipMiddleware
|
||||
from fastapi.responses import ORJSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.middleware.gzip import GZipMiddleware
|
||||
from fastapi_endpoints import auto_include_routers
|
||||
|
||||
import modules
|
||||
import routers
|
||||
from utils.auto_include_routers import auto_include_routers
|
||||
|
||||
origins = ["http://localhost:3000"]
|
||||
|
||||
app = FastAPI(
|
||||
separate_input_output_schemas=True,
|
||||
default_response_class=ORJSONResponse,
|
||||
root_path="/api"
|
||||
root_path="/api",
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
@ -28,5 +29,6 @@ app.add_middleware(
|
||||
)
|
||||
|
||||
auto_include_routers(app, routers)
|
||||
auto_include_routers(app, modules, True)
|
||||
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
from sqlalchemy.orm import configure_mappers
|
||||
|
||||
from modules.fulfillment_base.models import * # noqa: F401
|
||||
from .base import BaseModel as BaseModel
|
||||
from .board import Board as Board
|
||||
from .built_in_module import ( # noqa: F401
|
||||
BuiltInModule as BuiltInModule,
|
||||
project_built_in_module as project_built_in_module,
|
||||
built_in_module_dependencies as built_in_module_dependencies,
|
||||
)
|
||||
from .deal import Deal as Deal
|
||||
from .project import Project as Project
|
||||
from .status import Status as Status, CardStatusHistory as CardStatusHistory
|
||||
|
||||
58
models/built_in_module.py
Normal file
58
models/built_in_module.py
Normal file
@ -0,0 +1,58 @@
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
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_built_in_module = Table(
|
||||
"project_built_in_module",
|
||||
BaseModel.metadata,
|
||||
Column("project_id", ForeignKey("projects.id"), primary_key=True),
|
||||
Column("module_id", ForeignKey("built_in_modules.id"), primary_key=True),
|
||||
)
|
||||
|
||||
|
||||
built_in_module_dependencies = Table(
|
||||
"built_in_module_dependencies",
|
||||
BaseModel.metadata,
|
||||
Column("module_id", ForeignKey("built_in_modules.id"), primary_key=True),
|
||||
Column("depends_on_id", ForeignKey("built_in_modules.id"), primary_key=True),
|
||||
)
|
||||
|
||||
|
||||
class BuiltInModule(BaseModel):
|
||||
__tablename__ = "built_in_modules"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
key: Mapped[str] = mapped_column(unique=True)
|
||||
label: Mapped[str] = mapped_column()
|
||||
description: Mapped[str] = mapped_column()
|
||||
icon_name: Mapped[Optional[str]] = mapped_column(unique=True)
|
||||
is_deleted: Mapped[bool] = mapped_column(default=False)
|
||||
|
||||
depends_on: Mapped[list["BuiltInModule"]] = relationship(
|
||||
secondary=built_in_module_dependencies,
|
||||
primaryjoin="BuiltInModule.id == built_in_module_dependencies.c.module_id",
|
||||
secondaryjoin="BuiltInModule.id == built_in_module_dependencies.c.depends_on_id",
|
||||
back_populates="depended_on_by",
|
||||
lazy="immediate",
|
||||
)
|
||||
|
||||
depended_on_by: Mapped[list["BuiltInModule"]] = relationship(
|
||||
secondary="built_in_module_dependencies",
|
||||
primaryjoin="BuiltInModule.id == built_in_module_dependencies.c.depends_on_id",
|
||||
secondaryjoin="BuiltInModule.id == built_in_module_dependencies.c.module_id",
|
||||
back_populates="depends_on",
|
||||
lazy="noload",
|
||||
)
|
||||
|
||||
projects: Mapped[list["Project"]] = relationship(
|
||||
uselist=True,
|
||||
secondary="project_built_in_module",
|
||||
back_populates="built_in_modules",
|
||||
lazy="noload",
|
||||
)
|
||||
@ -1,6 +1,6 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import DateTime
|
||||
from sqlalchemy import DateTime, Numeric
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
|
||||
@ -21,3 +21,11 @@ class CreatedAtMixin:
|
||||
default=lambda: datetime.now(timezone.utc),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
|
||||
class PriceMixin:
|
||||
price: Mapped[float] = mapped_column(Numeric(12, 2), comment="Стоимость")
|
||||
|
||||
|
||||
class CostMixin:
|
||||
cost: Mapped[float] = mapped_column(Numeric(12, 2), comment="Себестоимость")
|
||||
|
||||
@ -6,7 +6,7 @@ from models.base import BaseModel
|
||||
from models.mixins import SoftDeleteMixin, CreatedAtMixin, IdMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from models import Board
|
||||
from models import Board, BuiltInModule
|
||||
|
||||
|
||||
class Project(BaseModel, IdMixin, SoftDeleteMixin, CreatedAtMixin):
|
||||
@ -18,3 +18,10 @@ class Project(BaseModel, IdMixin, SoftDeleteMixin, CreatedAtMixin):
|
||||
back_populates="project",
|
||||
lazy="noload",
|
||||
)
|
||||
|
||||
built_in_modules: Mapped[list["BuiltInModule"]] = relationship(
|
||||
secondary="project_built_in_module",
|
||||
back_populates="projects",
|
||||
lazy="selectin",
|
||||
order_by="asc(BuiltInModule.id)",
|
||||
)
|
||||
|
||||
0
modules/__init__.py
Normal file
0
modules/__init__.py
Normal file
0
modules/fulfillment_base/__init__.py
Normal file
0
modules/fulfillment_base/__init__.py
Normal file
1
modules/fulfillment_base/enums/__init__.py
Normal file
1
modules/fulfillment_base/enums/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .service import *
|
||||
7
modules/fulfillment_base/enums/service.py
Normal file
7
modules/fulfillment_base/enums/service.py
Normal file
@ -0,0 +1,7 @@
|
||||
from enum import IntEnum, unique
|
||||
|
||||
|
||||
@unique
|
||||
class ServiceType(IntEnum):
|
||||
DEAL_SERVICE = 0
|
||||
PRODUCT_SERVICE = 1
|
||||
6
modules/fulfillment_base/models/__init__.py
Normal file
6
modules/fulfillment_base/models/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
from .deal_product import DealProduct as DealProduct, DealProductService as DealProductService
|
||||
from .deal_service import (
|
||||
DealService as DealService,
|
||||
)
|
||||
from .product import Product as Product
|
||||
from .service import Service as Service
|
||||
60
modules/fulfillment_base/models/deal_product.py
Normal file
60
modules/fulfillment_base/models/deal_product.py
Normal file
@ -0,0 +1,60 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import ForeignKey, ForeignKeyConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from models.base import BaseModel
|
||||
from models.mixins import PriceMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from models import Deal, Service, Product
|
||||
|
||||
|
||||
class DealProduct(BaseModel):
|
||||
__tablename__ = "fulfillment_base_deal_products"
|
||||
|
||||
deal_id: Mapped[int] = mapped_column(ForeignKey("deals.id"), primary_key=True)
|
||||
product_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("fulfillment_base_products.id"), primary_key=True
|
||||
)
|
||||
|
||||
quantity: Mapped[int] = mapped_column(default=1)
|
||||
comment: Mapped[str] = mapped_column(comment="Комментарий к товару")
|
||||
|
||||
deal: Mapped["Deal"] = relationship(backref="deal_products")
|
||||
product: Mapped["Product"] = relationship()
|
||||
|
||||
product_services: Mapped[list["DealProductService"]] = relationship(
|
||||
back_populates="deal_product",
|
||||
primaryjoin="and_(DealProduct.deal_id==DealProductService.deal_id, DealProduct.product_id==DealProductService.product_id)",
|
||||
)
|
||||
|
||||
|
||||
class DealProductService(BaseModel, PriceMixin):
|
||||
__tablename__ = "fulfillment_base_deal_products_services"
|
||||
|
||||
deal_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
product_id: Mapped[int] = mapped_column(primary_key=True)
|
||||
service_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("fulfillment_base_services.id"), primary_key=True
|
||||
)
|
||||
|
||||
is_fixed_price: Mapped[bool] = mapped_column(
|
||||
default=False, server_default="0", comment="Фиксированная цена"
|
||||
)
|
||||
|
||||
deal_product: Mapped["DealProduct"] = relationship(
|
||||
back_populates="product_services",
|
||||
primaryjoin="and_(DealProductService.deal_id==DealProduct.deal_id, DealProductService.product_id==DealProduct.product_id)",
|
||||
)
|
||||
service: Mapped["Service"] = relationship()
|
||||
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(
|
||||
["deal_id", "product_id"],
|
||||
[
|
||||
"fulfillment_base_deal_products.deal_id",
|
||||
"fulfillment_base_deal_products.product_id",
|
||||
],
|
||||
),
|
||||
)
|
||||
28
modules/fulfillment_base/models/deal_service.py
Normal file
28
modules/fulfillment_base/models/deal_service.py
Normal file
@ -0,0 +1,28 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from models.base import BaseModel
|
||||
from models.mixins import PriceMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from models import Deal
|
||||
from modules.fulfillment_base.models import Service
|
||||
|
||||
|
||||
class DealService(BaseModel, PriceMixin):
|
||||
__tablename__ = "fulfillment_base_deal_services"
|
||||
|
||||
deal_id: Mapped[int] = mapped_column(ForeignKey("deals.id"), primary_key=True)
|
||||
deal: Mapped["Deal"] = relationship(backref="deal_services")
|
||||
|
||||
service_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("fulfillment_base_services.id"), primary_key=True
|
||||
)
|
||||
service: Mapped["Service"] = relationship()
|
||||
|
||||
quantity: Mapped[int] = mapped_column(default=1)
|
||||
is_fixed_price: Mapped[bool] = mapped_column(
|
||||
default=False, server_default="0", comment="Фиксированная цена"
|
||||
)
|
||||
40
modules/fulfillment_base/models/product.py
Normal file
40
modules/fulfillment_base/models/product.py
Normal file
@ -0,0 +1,40 @@
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from models.base import BaseModel
|
||||
from models.mixins import IdMixin, SoftDeleteMixin
|
||||
|
||||
|
||||
class Product(BaseModel, IdMixin, SoftDeleteMixin):
|
||||
__tablename__ = "fulfillment_base_products"
|
||||
|
||||
name: Mapped[str] = mapped_column()
|
||||
article: Mapped[str] = mapped_column(index=True)
|
||||
factory_article: Mapped[str] = mapped_column(
|
||||
index=True, default="", server_default=""
|
||||
)
|
||||
brand: Mapped[Optional[str]] = mapped_column(comment="Бренд")
|
||||
color: Mapped[Optional[str]] = mapped_column(comment="Цвет")
|
||||
composition: Mapped[Optional[str]] = mapped_column(comment="Состав")
|
||||
size: Mapped[Optional[str]] = mapped_column(comment="Размер")
|
||||
additional_info: Mapped[Optional[str]] = mapped_column(
|
||||
comment="Дополнительная информация"
|
||||
)
|
||||
|
||||
images: Mapped[list["ProductImage"]] = relationship(
|
||||
"ProductImage",
|
||||
back_populates="product",
|
||||
lazy="selectin",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
|
||||
class ProductImage(BaseModel, IdMixin):
|
||||
__tablename__ = "fulfillment_base_product_images"
|
||||
|
||||
product_id: Mapped[int] = mapped_column(ForeignKey("fulfillment_base_products.id"))
|
||||
product: Mapped["Product"] = relationship(back_populates="images")
|
||||
|
||||
image_url: Mapped[str] = mapped_column(nullable=False)
|
||||
73
modules/fulfillment_base/models/service.py
Normal file
73
modules/fulfillment_base/models/service.py
Normal file
@ -0,0 +1,73 @@
|
||||
from sqlalchemy import ForeignKey, Table, Column
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from models.base import BaseModel
|
||||
from models.mixins import IdMixin, SoftDeleteMixin, CostMixin, PriceMixin
|
||||
from modules.fulfillment_base import enums
|
||||
|
||||
services_kit_services = Table(
|
||||
"fulfillment_base_services_kit_services",
|
||||
BaseModel.metadata,
|
||||
Column("services_kit_id", ForeignKey("fulfillment_base_services_kits.id")),
|
||||
Column("service_id", ForeignKey("fulfillment_base_services.id")),
|
||||
)
|
||||
|
||||
|
||||
class Service(BaseModel, IdMixin, SoftDeleteMixin, PriceMixin, CostMixin):
|
||||
__tablename__ = "fulfillment_base_services"
|
||||
|
||||
name: Mapped[str] = mapped_column(index=True)
|
||||
|
||||
price_ranges: Mapped[list["ServicePriceRange"]] = relationship(
|
||||
back_populates="service",
|
||||
lazy="selectin",
|
||||
order_by="asc(ServicePriceRange.from_quantity)",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
category_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("fulfillment_base_service_categories.id"),
|
||||
comment="ID категории услуги",
|
||||
)
|
||||
category: Mapped["ServiceCategory"] = relationship("ServiceCategory", lazy="joined")
|
||||
|
||||
service_type: Mapped[int] = mapped_column(
|
||||
server_default=f"{enums.service.ServiceType.DEAL_SERVICE}",
|
||||
comment="Тип услуги",
|
||||
)
|
||||
lexorank: Mapped[str] = mapped_column(comment="Ранг услуги")
|
||||
|
||||
|
||||
class ServiceCategory(BaseModel, IdMixin):
|
||||
__tablename__ = "fulfillment_base_service_categories"
|
||||
|
||||
name: Mapped[str] = mapped_column()
|
||||
is_deleted: Mapped[bool] = mapped_column(
|
||||
default=False, comment="Удалена ли категория"
|
||||
)
|
||||
|
||||
deal_service_rank: Mapped[str] = mapped_column(comment="Ранг услуги для сделки")
|
||||
product_service_rank: Mapped[str] = mapped_column(comment="Ранг услуги для товара")
|
||||
|
||||
|
||||
class ServicesKit(BaseModel, IdMixin):
|
||||
__tablename__ = "fulfillment_base_services_kits"
|
||||
|
||||
name: Mapped[str] = mapped_column()
|
||||
service_type: Mapped[int] = mapped_column(
|
||||
server_default=f"{enums.ServiceType.DEAL_SERVICE}",
|
||||
comment="Тип услуги",
|
||||
)
|
||||
services: Mapped[list["Service"]] = relationship(
|
||||
secondary=services_kit_services, lazy="selectin"
|
||||
)
|
||||
|
||||
|
||||
class ServicePriceRange(BaseModel, IdMixin, PriceMixin):
|
||||
__tablename__ = "fulfillment_base_service_price_ranges"
|
||||
|
||||
service_id: Mapped[int] = mapped_column(ForeignKey("fulfillment_base_services.id"))
|
||||
service: Mapped[Service] = relationship(back_populates="price_ranges")
|
||||
|
||||
from_quantity: Mapped[int] = mapped_column(comment="От количества")
|
||||
to_quantity: Mapped[int] = mapped_column(comment="До количества")
|
||||
6
modules/fulfillment_base/repositories/__init__.py
Normal file
6
modules/fulfillment_base/repositories/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
from .deal_product import DealProductRepository as DealProductRepository
|
||||
from .product_service import ProductServiceRepository as ProductServiceRepository
|
||||
from .deal_service import DealServiceRepository as DealServiceRepository
|
||||
from .product import ProductRepository as ProductRepository
|
||||
from .service import ServiceRepository as ServiceRepository
|
||||
from .services_kit import ServicesKitRepository as ServicesKitRepository
|
||||
71
modules/fulfillment_base/repositories/deal_product.py
Normal file
71
modules/fulfillment_base/repositories/deal_product.py
Normal file
@ -0,0 +1,71 @@
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import Select, select
|
||||
from sqlalchemy.orm import joinedload, selectinload
|
||||
|
||||
from modules.fulfillment_base.models import DealProductService
|
||||
from modules.fulfillment_base.models.deal_product import DealProduct
|
||||
from modules.fulfillment_base.models.service import ServicesKit
|
||||
from modules.fulfillment_base.schemas.deal_product import (
|
||||
UpdateDealProductSchema,
|
||||
CreateDealProductSchema,
|
||||
)
|
||||
from repositories.base import BaseRepository
|
||||
from repositories.mixins import RepGetAllMixin, RepUpdateMixin
|
||||
|
||||
|
||||
class DealProductRepository(
|
||||
BaseRepository,
|
||||
RepGetAllMixin[DealProduct],
|
||||
RepUpdateMixin[DealProduct, UpdateDealProductSchema],
|
||||
):
|
||||
entity_class = DealProduct
|
||||
|
||||
def _process_get_all_stmt_with_args(self, stmt: Select, *args) -> Select:
|
||||
deal_id = args[0]
|
||||
return (
|
||||
stmt.options(
|
||||
joinedload(DealProduct.product),
|
||||
selectinload(DealProduct.product_services).joinedload(
|
||||
DealProductService.service
|
||||
),
|
||||
)
|
||||
.where(DealProduct.deal_id == deal_id)
|
||||
.order_by(DealProduct.product_id)
|
||||
)
|
||||
|
||||
async def get_by_id(self, deal_id: int, product_id: int) -> Optional[DealProduct]:
|
||||
stmt = (
|
||||
select(DealProduct)
|
||||
.options(
|
||||
joinedload(DealProduct.product),
|
||||
selectinload(DealProduct.product_services).joinedload(
|
||||
DealProductService.service
|
||||
),
|
||||
)
|
||||
.where(DealProduct.deal_id == deal_id, DealProduct.product_id == product_id)
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def create(self, data: CreateDealProductSchema):
|
||||
deal_product = DealProduct(**data.model_dump())
|
||||
self.session.add(deal_product)
|
||||
await self.session.commit()
|
||||
|
||||
async def delete(self, obj: DealProduct):
|
||||
await self.session.delete(obj)
|
||||
await self.session.commit()
|
||||
|
||||
async def add_services_kit(
|
||||
self, deal_product: DealProduct, services_kit: ServicesKit
|
||||
):
|
||||
for service in services_kit.services:
|
||||
deal_product_service = DealProductService(
|
||||
deal_id=deal_product.deal_id,
|
||||
product_id=deal_product.product_id,
|
||||
service_id=service.id,
|
||||
price=service.price,
|
||||
)
|
||||
self.session.add(deal_product_service)
|
||||
await self.session.commit()
|
||||
48
modules/fulfillment_base/repositories/deal_service.py
Normal file
48
modules/fulfillment_base/repositories/deal_service.py
Normal file
@ -0,0 +1,48 @@
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import Select, select
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from modules.fulfillment_base.models import DealService
|
||||
from modules.fulfillment_base.schemas.deal_service import (
|
||||
UpdateDealServiceSchema,
|
||||
CreateDealServiceSchema,
|
||||
)
|
||||
from repositories.base import BaseRepository
|
||||
from repositories.mixins import RepGetAllMixin, RepUpdateMixin
|
||||
|
||||
|
||||
class DealServiceRepository(
|
||||
BaseRepository,
|
||||
RepGetAllMixin[DealService],
|
||||
RepUpdateMixin[DealService, UpdateDealServiceSchema],
|
||||
):
|
||||
entity_class = DealService
|
||||
|
||||
def _process_get_all_stmt_with_args(self, stmt: Select, *args) -> Select:
|
||||
deal_id = args[0]
|
||||
return (
|
||||
stmt.options(
|
||||
joinedload(DealService.service),
|
||||
)
|
||||
.where(DealService.deal_id == deal_id)
|
||||
.order_by(DealService.service_id)
|
||||
)
|
||||
|
||||
async def get_by_id(self, deal_id: int, service_id: int) -> Optional[DealService]:
|
||||
stmt = (
|
||||
select(DealService)
|
||||
.options(joinedload(DealService.service))
|
||||
.where(DealService.deal_id == deal_id, DealService.service_id == service_id)
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def create(self, data: CreateDealServiceSchema):
|
||||
deal_service = DealService(**data.model_dump())
|
||||
self.session.add(deal_service)
|
||||
await self.session.commit()
|
||||
|
||||
async def delete(self, obj: DealService):
|
||||
await self.session.delete(obj)
|
||||
await self.session.commit()
|
||||
33
modules/fulfillment_base/repositories/product.py
Normal file
33
modules/fulfillment_base/repositories/product.py
Normal file
@ -0,0 +1,33 @@
|
||||
from modules.fulfillment_base.models import Product
|
||||
from modules.fulfillment_base.schemas.product import (
|
||||
CreateProductSchema,
|
||||
UpdateProductSchema,
|
||||
)
|
||||
from repositories.mixins import *
|
||||
from schemas.base import PaginationSchema
|
||||
|
||||
|
||||
class ProductRepository(
|
||||
BaseRepository,
|
||||
RepGetAllMixin[Product],
|
||||
RepDeleteMixin[Product],
|
||||
RepCreateMixin[Product, CreateProductSchema],
|
||||
RepUpdateMixin[Product, UpdateProductSchema],
|
||||
RepGetByIdMixin[Product],
|
||||
):
|
||||
entity_class = Product
|
||||
|
||||
def _process_get_all_stmt_with_args(self, stmt: Select, *args) -> Select:
|
||||
search_input = args[0]
|
||||
pagination: PaginationSchema = args[1]
|
||||
if search_input:
|
||||
stmt = stmt.where(Product.name.ilike(f"%{search_input}%"))
|
||||
if pagination.items_per_page and pagination.page:
|
||||
stmt = self._apply_pagination(
|
||||
stmt, pagination.page, pagination.items_per_page
|
||||
)
|
||||
|
||||
return stmt
|
||||
|
||||
async def update(self, product: Product, data: UpdateProductSchema) -> Product:
|
||||
return await self._apply_update_data_to_model(product, data, True)
|
||||
84
modules/fulfillment_base/repositories/product_service.py
Normal file
84
modules/fulfillment_base/repositories/product_service.py
Normal file
@ -0,0 +1,84 @@
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import select, delete
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from modules.fulfillment_base.models import DealProductService
|
||||
from modules.fulfillment_base.schemas.product_service import *
|
||||
from repositories.base import BaseRepository
|
||||
from repositories.mixins import RepUpdateMixin
|
||||
|
||||
|
||||
class ProductServiceRepository(
|
||||
BaseRepository,
|
||||
RepUpdateMixin[DealProductService, UpdateProductServiceSchema],
|
||||
):
|
||||
entity_class = DealProductService
|
||||
session: AsyncSession
|
||||
|
||||
async def get_by_id(
|
||||
self, deal_id: int, product_id: int, service_id: int
|
||||
) -> Optional[DealProductService]:
|
||||
stmt = (
|
||||
select(DealProductService)
|
||||
.options(
|
||||
joinedload(DealProductService.service),
|
||||
)
|
||||
.where(
|
||||
DealProductService.deal_id == deal_id,
|
||||
DealProductService.product_id == product_id,
|
||||
DealProductService.service_id == service_id,
|
||||
)
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def create(self, data: CreateProductServiceSchema):
|
||||
deal_product_service = DealProductService(**data.model_dump())
|
||||
self.session.add(deal_product_service)
|
||||
await self.session.commit()
|
||||
|
||||
async def delete(self, obj: DealProductService):
|
||||
await self.session.delete(obj)
|
||||
await self.session.commit()
|
||||
|
||||
async def get_product_services(
|
||||
self, deal_id: int, product_id: int
|
||||
) -> list[DealProductService]:
|
||||
stmt = (
|
||||
select(DealProductService)
|
||||
.options(
|
||||
joinedload(DealProductService.service),
|
||||
)
|
||||
.where(
|
||||
DealProductService.deal_id == deal_id,
|
||||
DealProductService.product_id == product_id,
|
||||
)
|
||||
)
|
||||
return list(await self.session.scalars(stmt))
|
||||
|
||||
async def delete_product_services(self, deal_id: int, product_ids: list[int]):
|
||||
stmt = delete(DealProductService).where(
|
||||
DealProductService.deal_id == deal_id,
|
||||
DealProductService.product_id.in_(product_ids),
|
||||
)
|
||||
await self.session.execute(stmt)
|
||||
await self.session.flush()
|
||||
|
||||
async def duplicate_services(
|
||||
self, deal_id: int, product_ids: list[int], services: list[DealProductService]
|
||||
):
|
||||
await self.delete_product_services(deal_id, product_ids)
|
||||
|
||||
for product_id in product_ids:
|
||||
for prod_service in services:
|
||||
product_service = DealProductService(
|
||||
deal_id=deal_id,
|
||||
product_id=product_id,
|
||||
service_id=prod_service.service.id,
|
||||
price=prod_service.price,
|
||||
is_fixed_price=prod_service.is_fixed_price,
|
||||
)
|
||||
self.session.add(product_service)
|
||||
await self.session.commit()
|
||||
20
modules/fulfillment_base/repositories/service.py
Normal file
20
modules/fulfillment_base/repositories/service.py
Normal file
@ -0,0 +1,20 @@
|
||||
from modules.fulfillment_base.models import Service
|
||||
from modules.fulfillment_base.schemas.service import (
|
||||
CreateServiceSchema,
|
||||
UpdateServiceSchema,
|
||||
)
|
||||
from repositories.mixins import *
|
||||
|
||||
|
||||
class ServiceRepository(
|
||||
BaseRepository,
|
||||
RepGetAllMixin[Service],
|
||||
RepDeleteMixin[Service],
|
||||
RepCreateMixin[Service, CreateServiceSchema],
|
||||
RepUpdateMixin[Service, UpdateServiceSchema],
|
||||
RepGetByIdMixin[Service],
|
||||
):
|
||||
entity_class = Service
|
||||
|
||||
async def update(self, service: Service, data: UpdateServiceSchema) -> Service:
|
||||
return await self._apply_update_data_to_model(service, data, True)
|
||||
22
modules/fulfillment_base/repositories/services_kit.py
Normal file
22
modules/fulfillment_base/repositories/services_kit.py
Normal file
@ -0,0 +1,22 @@
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from modules.fulfillment_base.models.service import ServicesKit
|
||||
from modules.fulfillment_base.schemas.services_kit import (
|
||||
CreateServicesKitSchema,
|
||||
UpdateServicesKitSchema,
|
||||
)
|
||||
from repositories.mixins import *
|
||||
|
||||
|
||||
class ServicesKitRepository(
|
||||
RepCrudMixin[ServicesKit, CreateServicesKitSchema, UpdateServicesKitSchema],
|
||||
):
|
||||
entity_class = ServicesKit
|
||||
|
||||
def _process_get_by_id_stmt(self, stmt: Select) -> Select:
|
||||
return stmt.options(selectinload(ServicesKit.services))
|
||||
|
||||
async def update(
|
||||
self, service: ServicesKit, data: UpdateServicesKitSchema
|
||||
) -> ServicesKit:
|
||||
return await self._apply_update_data_to_model(service, data, True)
|
||||
0
modules/fulfillment_base/routers/__init__.py
Normal file
0
modules/fulfillment_base/routers/__init__.py
Normal file
137
modules/fulfillment_base/routers/deal_product.py
Normal file
137
modules/fulfillment_base/routers/deal_product.py
Normal file
@ -0,0 +1,137 @@
|
||||
from fastapi import APIRouter, Path
|
||||
|
||||
from backend.dependecies import SessionDependency
|
||||
from modules.fulfillment_base.schemas.deal_product import *
|
||||
from modules.fulfillment_base.schemas.product_service import *
|
||||
from modules.fulfillment_base.services import DealProductService, ProductServiceService
|
||||
|
||||
router = APIRouter(tags=["deal-product"])
|
||||
|
||||
# region DealProduct
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{dealId}",
|
||||
response_model=GetDealProductsResponse,
|
||||
operation_id="get_deal_products",
|
||||
)
|
||||
async def get_deal_products(
|
||||
session: SessionDependency,
|
||||
deal_id: int = Path(alias="dealId"),
|
||||
):
|
||||
return await DealProductService(session).get_all(deal_id)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=CreateDealProductResponse,
|
||||
operation_id="create_deal_product",
|
||||
)
|
||||
async def create_deal_product(
|
||||
session: SessionDependency,
|
||||
request: CreateDealProductRequest,
|
||||
):
|
||||
return await DealProductService(session).create(request)
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/{dealId}/product/{productId}",
|
||||
response_model=UpdateDealProductResponse,
|
||||
operation_id="update_deal_product",
|
||||
)
|
||||
async def update_deal_product(
|
||||
session: SessionDependency,
|
||||
request: UpdateDealProductRequest,
|
||||
deal_id: int = Path(alias="dealId"),
|
||||
product_id: int = Path(alias="productId"),
|
||||
):
|
||||
return await DealProductService(session).update(deal_id, product_id, request)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{dealId}/product/{productId}",
|
||||
response_model=DeleteDealProductResponse,
|
||||
operation_id="delete_deal_product",
|
||||
)
|
||||
async def delete_deal_product(
|
||||
session: SessionDependency,
|
||||
deal_id: int = Path(alias="dealId"),
|
||||
product_id: int = Path(alias="productId"),
|
||||
):
|
||||
return await DealProductService(session).delete(deal_id, product_id)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/add-services-kit",
|
||||
response_model=DealProductAddKitResponse,
|
||||
operation_id="add_kit_to_deal_product",
|
||||
)
|
||||
async def add_kit_to_deal_product(
|
||||
session: SessionDependency,
|
||||
request: DealProductAddKitRequest,
|
||||
):
|
||||
return await DealProductService(session).add_services_kit(request)
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
# region DealProductService
|
||||
|
||||
|
||||
@router.post(
|
||||
"/service",
|
||||
response_model=CreateProductServiceResponse,
|
||||
operation_id="create_deal_product_service",
|
||||
)
|
||||
async def create_deal_product_service(
|
||||
session: SessionDependency,
|
||||
request: CreateProductServiceRequest,
|
||||
):
|
||||
return await ProductServiceService(session).create(request)
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/{dealId}/product/{productId}/service/{serviceId}",
|
||||
response_model=UpdateProductServiceResponse,
|
||||
operation_id="update_deal_product_service",
|
||||
)
|
||||
async def update_deal_product_service(
|
||||
session: SessionDependency,
|
||||
request: UpdateProductServiceRequest,
|
||||
deal_id: int = Path(alias="dealId"),
|
||||
product_id: int = Path(alias="productId"),
|
||||
service_id: int = Path(alias="serviceId"),
|
||||
):
|
||||
return await ProductServiceService(session).update(
|
||||
deal_id, product_id, service_id, request
|
||||
)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{dealId}/product/{productId}/service/{serviceId}",
|
||||
response_model=DeleteProductServiceResponse,
|
||||
operation_id="delete_deal_product_service",
|
||||
)
|
||||
async def delete_deal_product_service(
|
||||
session: SessionDependency,
|
||||
deal_id: int = Path(alias="dealId"),
|
||||
product_id: int = Path(alias="productId"),
|
||||
service_id: int = Path(alias="serviceId"),
|
||||
):
|
||||
return await ProductServiceService(session).delete(deal_id, product_id, service_id)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/services/duplicate",
|
||||
response_model=ProductServicesDuplicateResponse,
|
||||
operation_id="duplicate_product_services",
|
||||
)
|
||||
async def copy_product_services(
|
||||
session: SessionDependency,
|
||||
request: ProductServicesDuplicateRequest,
|
||||
):
|
||||
return await ProductServiceService(session).duplicate_product_services(request)
|
||||
|
||||
|
||||
# endregion
|
||||
58
modules/fulfillment_base/routers/deal_service.py
Normal file
58
modules/fulfillment_base/routers/deal_service.py
Normal file
@ -0,0 +1,58 @@
|
||||
from fastapi import APIRouter, Path
|
||||
|
||||
from backend.dependecies import SessionDependency
|
||||
from modules.fulfillment_base.schemas.deal_service import *
|
||||
from modules.fulfillment_base.services import DealServiceService
|
||||
|
||||
router = APIRouter(tags=["deal-service"])
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{dealId}",
|
||||
response_model=GetDealServicesResponse,
|
||||
operation_id="get_deal_services",
|
||||
)
|
||||
async def get_deal_services(
|
||||
session: SessionDependency,
|
||||
deal_id: int = Path(alias="dealId"),
|
||||
):
|
||||
return await DealServiceService(session).get_all(deal_id)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=CreateDealServiceResponse,
|
||||
operation_id="create_deal_service",
|
||||
)
|
||||
async def create_deal_service(
|
||||
session: SessionDependency,
|
||||
request: CreateDealServiceRequest,
|
||||
):
|
||||
return await DealServiceService(session).create(request)
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/{dealId}/service/{serviceId}",
|
||||
response_model=UpdateDealServiceResponse,
|
||||
operation_id="update_deal_service",
|
||||
)
|
||||
async def update_deal_service(
|
||||
session: SessionDependency,
|
||||
request: UpdateDealServiceRequest,
|
||||
deal_id: int = Path(alias="dealId"),
|
||||
service_id: int = Path(alias="serviceId"),
|
||||
):
|
||||
return await DealServiceService(session).update(deal_id, service_id, request)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{dealId}/service/{serviceId}",
|
||||
response_model=DeleteDealServiceResponse,
|
||||
operation_id="delete_deal_service",
|
||||
)
|
||||
async def delete_deal_service(
|
||||
session: SessionDependency,
|
||||
deal_id: int = Path(alias="dealId"),
|
||||
service_id: int = Path(alias="serviceId"),
|
||||
):
|
||||
return await DealServiceService(session).delete(deal_id, service_id)
|
||||
57
modules/fulfillment_base/routers/product.py
Normal file
57
modules/fulfillment_base/routers/product.py
Normal file
@ -0,0 +1,57 @@
|
||||
from fastapi import APIRouter, Path, Query
|
||||
|
||||
from backend.dependecies import SessionDependency, PaginationDependency
|
||||
from modules.fulfillment_base.schemas.product import *
|
||||
from modules.fulfillment_base.services import ProductService
|
||||
|
||||
router = APIRouter(tags=["product"])
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=GetProductsResponse,
|
||||
operation_id="get_products",
|
||||
)
|
||||
async def get_products(
|
||||
session: SessionDependency,
|
||||
pagination: PaginationDependency,
|
||||
search_input: Optional[str] = Query(alias="searchInput", default=None),
|
||||
):
|
||||
return await ProductService(session).get_all(search_input, pagination)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=CreateProductResponse,
|
||||
operation_id="create_product",
|
||||
)
|
||||
async def create_product(
|
||||
session: SessionDependency,
|
||||
request: CreateProductRequest,
|
||||
):
|
||||
return await ProductService(session).create(request)
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/{pk}",
|
||||
response_model=UpdateProductResponse,
|
||||
operation_id="update_product",
|
||||
)
|
||||
async def update_product(
|
||||
session: SessionDependency,
|
||||
request: UpdateProductRequest,
|
||||
pk: int = Path(),
|
||||
):
|
||||
return await ProductService(session).update(pk, request)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{pk}",
|
||||
response_model=DeleteProductResponse,
|
||||
operation_id="delete_product",
|
||||
)
|
||||
async def delete_product(
|
||||
session: SessionDependency,
|
||||
pk: int = Path(),
|
||||
):
|
||||
return await ProductService(session).delete(pk)
|
||||
55
modules/fulfillment_base/routers/service.py
Normal file
55
modules/fulfillment_base/routers/service.py
Normal file
@ -0,0 +1,55 @@
|
||||
from fastapi import APIRouter, Path
|
||||
|
||||
from backend.dependecies import SessionDependency
|
||||
from modules.fulfillment_base.schemas.service import *
|
||||
from modules.fulfillment_base.services import ServiceModelService
|
||||
|
||||
router = APIRouter(tags=["service"])
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=GetServicesResponse,
|
||||
operation_id="get_services",
|
||||
)
|
||||
async def get_services(
|
||||
session: SessionDependency,
|
||||
):
|
||||
return await ServiceModelService(session).get_all()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=CreateServiceResponse,
|
||||
operation_id="create_service",
|
||||
)
|
||||
async def create_service(
|
||||
session: SessionDependency,
|
||||
request: CreateServiceRequest,
|
||||
):
|
||||
return await ServiceModelService(session).create(request)
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/{pk}",
|
||||
response_model=UpdateServiceResponse,
|
||||
operation_id="update_service",
|
||||
)
|
||||
async def update_service(
|
||||
session: SessionDependency,
|
||||
request: UpdateServiceRequest,
|
||||
pk: int = Path(),
|
||||
):
|
||||
return await ServiceModelService(session).update(pk, request)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{pk}",
|
||||
response_model=DeleteServiceResponse,
|
||||
operation_id="delete_service",
|
||||
)
|
||||
async def delete_service(
|
||||
session: SessionDependency,
|
||||
pk: int = Path(),
|
||||
):
|
||||
return await ServiceModelService(session).delete(pk)
|
||||
55
modules/fulfillment_base/routers/services_kit.py
Normal file
55
modules/fulfillment_base/routers/services_kit.py
Normal file
@ -0,0 +1,55 @@
|
||||
from fastapi import APIRouter, Path
|
||||
|
||||
from backend.dependecies import SessionDependency
|
||||
from modules.fulfillment_base.schemas.services_kit import *
|
||||
from modules.fulfillment_base.services import ServicesKitService
|
||||
|
||||
router = APIRouter(tags=["services-kit"])
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=GetServicesKitResponse,
|
||||
operation_id="get_services_kits",
|
||||
)
|
||||
async def get_services_kits(
|
||||
session: SessionDependency,
|
||||
):
|
||||
return await ServicesKitService(session).get_all()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=CreateServicesKitResponse,
|
||||
operation_id="create_services_kit",
|
||||
)
|
||||
async def create_services_kit(
|
||||
session: SessionDependency,
|
||||
request: CreateServicesKitRequest,
|
||||
):
|
||||
return await ServicesKitService(session).create(request)
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/{pk}",
|
||||
response_model=UpdateServicesKitResponse,
|
||||
operation_id="update_services_kit",
|
||||
)
|
||||
async def update_services_kit(
|
||||
session: SessionDependency,
|
||||
request: UpdateServicesKitRequest,
|
||||
pk: int = Path(),
|
||||
):
|
||||
return await ServicesKitService(session).update(pk, request)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{pk}",
|
||||
response_model=DeleteServicesKitResponse,
|
||||
operation_id="delete_services_kit",
|
||||
)
|
||||
async def delete_services_kit(
|
||||
session: SessionDependency,
|
||||
pk: int = Path(),
|
||||
):
|
||||
return await ServicesKitService(session).delete(pk)
|
||||
74
modules/fulfillment_base/schemas/deal_product.py
Normal file
74
modules/fulfillment_base/schemas/deal_product.py
Normal file
@ -0,0 +1,74 @@
|
||||
from modules.fulfillment_base.schemas.product import ProductSchema
|
||||
from modules.fulfillment_base.schemas.product_service import ProductServiceSchema
|
||||
from schemas.base import BaseSchema, BaseResponse
|
||||
|
||||
|
||||
# region Entity
|
||||
|
||||
|
||||
class DealProductSchema(BaseSchema):
|
||||
deal_id: int
|
||||
product_id: int
|
||||
product: ProductSchema
|
||||
quantity: int
|
||||
comment: str
|
||||
product_services: list[ProductServiceSchema]
|
||||
|
||||
|
||||
class CreateDealProductSchema(BaseSchema):
|
||||
deal_id: int
|
||||
product_id: int
|
||||
quantity: int
|
||||
comment: str
|
||||
|
||||
|
||||
class UpdateDealProductSchema(BaseSchema):
|
||||
quantity: int
|
||||
comment: str
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Request
|
||||
|
||||
|
||||
class CreateDealProductRequest(BaseSchema):
|
||||
entity: CreateDealProductSchema
|
||||
|
||||
|
||||
class UpdateDealProductRequest(BaseSchema):
|
||||
entity: UpdateDealProductSchema
|
||||
|
||||
|
||||
class DealProductAddKitRequest(BaseSchema):
|
||||
deal_id: int
|
||||
product_id: int
|
||||
kit_id: int
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Response
|
||||
|
||||
|
||||
class GetDealProductsResponse(BaseSchema):
|
||||
items: list[DealProductSchema]
|
||||
|
||||
|
||||
class CreateDealProductResponse(BaseResponse):
|
||||
entity: DealProductSchema
|
||||
|
||||
|
||||
class UpdateDealProductResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
class DeleteDealProductResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
class DealProductAddKitResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
# endregion
|
||||
64
modules/fulfillment_base/schemas/deal_service.py
Normal file
64
modules/fulfillment_base/schemas/deal_service.py
Normal file
@ -0,0 +1,64 @@
|
||||
from modules.fulfillment_base.schemas.service import ServiceSchema
|
||||
from schemas.base import BaseSchema, BaseResponse
|
||||
|
||||
|
||||
# region Entity
|
||||
|
||||
|
||||
class DealServiceSchema(BaseSchema):
|
||||
deal_id: int
|
||||
service_id: int
|
||||
service: ServiceSchema
|
||||
quantity: int
|
||||
price: float
|
||||
is_fixed_price: bool
|
||||
|
||||
|
||||
class CreateDealServiceSchema(BaseSchema):
|
||||
deal_id: int
|
||||
service_id: int
|
||||
quantity: int
|
||||
price: float
|
||||
|
||||
|
||||
class UpdateDealServiceSchema(BaseSchema):
|
||||
quantity: int
|
||||
price: float
|
||||
is_fixed_price: bool
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Request
|
||||
|
||||
|
||||
class CreateDealServiceRequest(BaseSchema):
|
||||
entity: CreateDealServiceSchema
|
||||
|
||||
|
||||
class UpdateDealServiceRequest(BaseSchema):
|
||||
entity: UpdateDealServiceSchema
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Response
|
||||
|
||||
|
||||
class GetDealServicesResponse(BaseSchema):
|
||||
items: list[DealServiceSchema]
|
||||
|
||||
|
||||
class CreateDealServiceResponse(BaseResponse):
|
||||
entity: DealServiceSchema
|
||||
|
||||
|
||||
class UpdateDealServiceResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
class DeleteDealServiceResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
# endregion
|
||||
77
modules/fulfillment_base/schemas/product.py
Normal file
77
modules/fulfillment_base/schemas/product.py
Normal file
@ -0,0 +1,77 @@
|
||||
from typing import Optional
|
||||
|
||||
from schemas.base import BaseSchema, BaseResponse
|
||||
|
||||
|
||||
# region Entity
|
||||
|
||||
|
||||
class ProductImageSchema(BaseSchema):
|
||||
id: int
|
||||
product_id: int
|
||||
image_url: str
|
||||
|
||||
|
||||
class CreateProductSchema(BaseSchema):
|
||||
name: str
|
||||
article: str
|
||||
factory_article: str
|
||||
brand: Optional[str]
|
||||
color: Optional[str]
|
||||
composition: Optional[str]
|
||||
size: Optional[str]
|
||||
additional_info: Optional[str]
|
||||
|
||||
|
||||
class ProductSchema(CreateProductSchema):
|
||||
id: int
|
||||
|
||||
|
||||
class UpdateProductSchema(BaseSchema):
|
||||
name: Optional[str] = None
|
||||
article: Optional[str] = None
|
||||
factory_article: Optional[str] = None
|
||||
brand: Optional[str] = None
|
||||
color: Optional[str] = None
|
||||
composition: Optional[str] = None
|
||||
size: Optional[str] = None
|
||||
additional_info: Optional[str] = None
|
||||
|
||||
images: list[ProductImageSchema] | None = []
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Request
|
||||
|
||||
|
||||
class CreateProductRequest(BaseSchema):
|
||||
entity: CreateProductSchema
|
||||
|
||||
|
||||
class UpdateProductRequest(BaseSchema):
|
||||
entity: UpdateProductSchema
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Response
|
||||
|
||||
|
||||
class GetProductsResponse(BaseSchema):
|
||||
items: list[ProductSchema]
|
||||
|
||||
|
||||
class CreateProductResponse(BaseResponse):
|
||||
entity: ProductSchema
|
||||
|
||||
|
||||
class UpdateProductResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
class DeleteProductResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
# endregion
|
||||
69
modules/fulfillment_base/schemas/product_service.py
Normal file
69
modules/fulfillment_base/schemas/product_service.py
Normal file
@ -0,0 +1,69 @@
|
||||
from modules.fulfillment_base.schemas.service import ServiceSchema
|
||||
from schemas.base import BaseSchema, BaseResponse
|
||||
|
||||
|
||||
# region Entity
|
||||
|
||||
|
||||
class ProductServiceSchema(BaseSchema):
|
||||
deal_id: int
|
||||
product_id: int
|
||||
service_id: int
|
||||
service: ServiceSchema
|
||||
price: float
|
||||
is_fixed_price: bool
|
||||
|
||||
|
||||
class CreateProductServiceSchema(BaseSchema):
|
||||
deal_id: int
|
||||
product_id: int
|
||||
service_id: int
|
||||
price: float
|
||||
|
||||
|
||||
class UpdateProductServiceSchema(BaseSchema):
|
||||
price: float
|
||||
is_fixed_price: bool
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Request
|
||||
|
||||
|
||||
class CreateProductServiceRequest(BaseSchema):
|
||||
entity: CreateProductServiceSchema
|
||||
|
||||
|
||||
class UpdateProductServiceRequest(BaseSchema):
|
||||
entity: UpdateProductServiceSchema
|
||||
|
||||
|
||||
class ProductServicesDuplicateRequest(BaseSchema):
|
||||
deal_id: int
|
||||
source_deal_product_id: int
|
||||
target_deal_product_ids: list[int]
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Response
|
||||
|
||||
|
||||
class CreateProductServiceResponse(BaseResponse):
|
||||
entity: ProductServiceSchema
|
||||
|
||||
|
||||
class UpdateProductServiceResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
class DeleteProductServiceResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
class ProductServicesDuplicateResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
# endregion
|
||||
76
modules/fulfillment_base/schemas/service.py
Normal file
76
modules/fulfillment_base/schemas/service.py
Normal file
@ -0,0 +1,76 @@
|
||||
from typing import Optional
|
||||
|
||||
from schemas.base import BaseSchema, BaseResponse
|
||||
|
||||
|
||||
# region Entity
|
||||
|
||||
|
||||
class ServicePriceRangeSchema(BaseSchema):
|
||||
id: int | None
|
||||
from_quantity: int
|
||||
to_quantity: int
|
||||
price: float
|
||||
|
||||
|
||||
class ServiceCategorySchema(BaseSchema):
|
||||
id: int
|
||||
name: str
|
||||
deal_service_rank: str
|
||||
product_service_rank: str
|
||||
|
||||
|
||||
class ServiceSchema(BaseSchema):
|
||||
id: int
|
||||
name: str
|
||||
category: ServiceCategorySchema
|
||||
price: float
|
||||
service_type: int
|
||||
price_ranges: list[ServicePriceRangeSchema]
|
||||
cost: Optional[float]
|
||||
lexorank: str
|
||||
|
||||
|
||||
class UpdateServiceSchema(ServiceSchema):
|
||||
pass
|
||||
|
||||
|
||||
class CreateServiceSchema(ServiceSchema):
|
||||
pass
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Request
|
||||
|
||||
|
||||
class CreateServiceRequest(BaseSchema):
|
||||
entity: CreateServiceSchema
|
||||
|
||||
|
||||
class UpdateServiceRequest(BaseSchema):
|
||||
entity: UpdateServiceSchema
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Response
|
||||
|
||||
|
||||
class GetServicesResponse(BaseSchema):
|
||||
items: list[ServiceSchema]
|
||||
|
||||
|
||||
class CreateServiceResponse(BaseResponse):
|
||||
entity: ServiceSchema
|
||||
|
||||
|
||||
class UpdateServiceResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
class DeleteServiceResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
# endregion
|
||||
60
modules/fulfillment_base/schemas/services_kit.py
Normal file
60
modules/fulfillment_base/schemas/services_kit.py
Normal file
@ -0,0 +1,60 @@
|
||||
from modules.fulfillment_base.schemas.service import ServiceSchema
|
||||
from schemas.base import BaseSchema, BaseResponse
|
||||
|
||||
|
||||
# region Entity
|
||||
|
||||
|
||||
class BaseServicesKitSchema(BaseSchema):
|
||||
name: str
|
||||
service_type: int
|
||||
|
||||
|
||||
class ServicesKitSchema(BaseServicesKitSchema):
|
||||
id: int
|
||||
services: list[ServiceSchema]
|
||||
|
||||
|
||||
class CreateServicesKitSchema(BaseServicesKitSchema):
|
||||
services_ids: list[int]
|
||||
|
||||
|
||||
class UpdateServicesKitSchema(BaseServicesKitSchema):
|
||||
services_ids: list[int]
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Request
|
||||
|
||||
|
||||
class CreateServicesKitRequest(BaseSchema):
|
||||
entity: CreateServicesKitSchema
|
||||
|
||||
|
||||
class UpdateServicesKitRequest(BaseSchema):
|
||||
entity: UpdateServicesKitSchema
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Response
|
||||
|
||||
|
||||
class GetServicesKitResponse(BaseSchema):
|
||||
items: list[ServicesKitSchema]
|
||||
|
||||
|
||||
class CreateServicesKitResponse(BaseResponse):
|
||||
entity: ServicesKitSchema
|
||||
|
||||
|
||||
class UpdateServicesKitResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
class DeleteServicesKitResponse(BaseResponse):
|
||||
pass
|
||||
|
||||
|
||||
# endregion
|
||||
6
modules/fulfillment_base/services/__init__.py
Normal file
6
modules/fulfillment_base/services/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
from .deal_product import DealProductService as DealProductService
|
||||
from .deal_service import DealServiceService as DealServiceService
|
||||
from .product import ProductService as ProductService
|
||||
from .product_service import ProductServiceService as ProductServiceService
|
||||
from .service import ServiceModelService as ServiceModelService
|
||||
from .services_kit import ServicesKitService as ServicesKitService
|
||||
72
modules/fulfillment_base/services/deal_product.py
Normal file
72
modules/fulfillment_base/services/deal_product.py
Normal file
@ -0,0 +1,72 @@
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from modules.fulfillment_base.models import DealProduct
|
||||
from modules.fulfillment_base.repositories import (
|
||||
DealProductRepository,
|
||||
ServicesKitRepository,
|
||||
ProductServiceRepository,
|
||||
)
|
||||
from modules.fulfillment_base.schemas.deal_product import *
|
||||
from services.mixins import ServiceGetAllMixin
|
||||
|
||||
|
||||
class DealProductService(ServiceGetAllMixin[DealProduct, DealProductSchema]):
|
||||
schema_class = DealProductSchema
|
||||
entity_not_found_msg = "Связь товара со сделкой не найдена"
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.repository = DealProductRepository(session)
|
||||
|
||||
async def create(
|
||||
self, request: CreateDealProductRequest
|
||||
) -> CreateDealProductResponse:
|
||||
await self.repository.create(request.entity)
|
||||
deal_product = await self.repository.get_by_id(
|
||||
request.entity.deal_id, request.entity.product_id
|
||||
)
|
||||
return CreateDealProductResponse(
|
||||
entity=DealProductSchema.model_validate(deal_product),
|
||||
message="Товар добавлен в сделку",
|
||||
)
|
||||
|
||||
async def update(
|
||||
self, deal_id: int, product_id: int, data: UpdateDealProductRequest
|
||||
) -> UpdateDealProductResponse:
|
||||
entity = await self.repository.get_by_id(deal_id, product_id)
|
||||
if not entity:
|
||||
raise HTTPException(status_code=404, detail=self.entity_not_found_msg)
|
||||
|
||||
await self.repository.update(entity, data.entity)
|
||||
return UpdateDealProductResponse(message="Товар сделки обновлен")
|
||||
|
||||
async def delete(self, deal_id: int, product_id: int) -> DeleteDealProductResponse:
|
||||
entity = await self.repository.get_by_id(deal_id, product_id)
|
||||
if not entity:
|
||||
raise HTTPException(status_code=404, detail=self.entity_not_found_msg)
|
||||
|
||||
await self.repository.delete(entity)
|
||||
return DeleteDealProductResponse(message="Товар удален из сделки")
|
||||
|
||||
async def add_services_kit(
|
||||
self, request: DealProductAddKitRequest
|
||||
) -> DealProductAddKitResponse:
|
||||
services_kit_repo = ServicesKitRepository(self.repository.session)
|
||||
services_kit = await services_kit_repo.get_by_id(request.kit_id)
|
||||
if not services_kit:
|
||||
raise HTTPException(status_code=404, detail="Набор услуг не найден")
|
||||
|
||||
deal_product = await self.repository.get_by_id(
|
||||
request.deal_id, request.product_id
|
||||
)
|
||||
if not deal_product:
|
||||
raise HTTPException(status_code=404, detail=self.entity_not_found_msg)
|
||||
|
||||
product_service_repo = ProductServiceRepository(self.repository.session)
|
||||
await product_service_repo.delete_product_services(
|
||||
request.deal_id, [request.product_id]
|
||||
)
|
||||
|
||||
await self.repository.add_services_kit(deal_product, services_kit)
|
||||
|
||||
return DealProductAddKitResponse(message="Комплект добавлен в товар")
|
||||
45
modules/fulfillment_base/services/deal_service.py
Normal file
45
modules/fulfillment_base/services/deal_service.py
Normal file
@ -0,0 +1,45 @@
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from modules.fulfillment_base.models import DealService
|
||||
from modules.fulfillment_base.repositories import DealServiceRepository
|
||||
from modules.fulfillment_base.schemas.deal_service import *
|
||||
from services.mixins import ServiceGetAllMixin
|
||||
|
||||
|
||||
class DealServiceService(ServiceGetAllMixin[DealService, DealServiceSchema]):
|
||||
schema_class = DealServiceSchema
|
||||
entity_not_found_msg = "Связь услуги со сделкой не найдена"
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.repository = DealServiceRepository(session)
|
||||
|
||||
async def create(
|
||||
self, request: CreateDealServiceRequest
|
||||
) -> CreateDealServiceResponse:
|
||||
await self.repository.create(request.entity)
|
||||
deal_service = await self.repository.get_by_id(
|
||||
request.entity.deal_id, request.entity.service_id
|
||||
)
|
||||
return CreateDealServiceResponse(
|
||||
entity=DealServiceSchema.model_validate(deal_service),
|
||||
message="Услуга добавлена в сделку",
|
||||
)
|
||||
|
||||
async def update(
|
||||
self, deal_id: int, service_id: int, data: UpdateDealServiceRequest
|
||||
) -> UpdateDealServiceResponse:
|
||||
entity = await self.repository.get_by_id(deal_id, service_id)
|
||||
if not entity:
|
||||
raise HTTPException(status_code=404, detail=self.entity_not_found_msg)
|
||||
|
||||
await self.repository.update(entity, data.entity)
|
||||
return UpdateDealServiceResponse(message="Услуга сделки обновлена")
|
||||
|
||||
async def delete(self, deal_id: int, service_id: int) -> DeleteDealServiceResponse:
|
||||
entity = await self.repository.get_by_id(deal_id, service_id)
|
||||
if not entity:
|
||||
raise HTTPException(status_code=404, detail=self.entity_not_found_msg)
|
||||
|
||||
await self.repository.delete(entity)
|
||||
return DeleteDealServiceResponse(message="Услуга удалена из сделки")
|
||||
27
modules/fulfillment_base/services/product.py
Normal file
27
modules/fulfillment_base/services/product.py
Normal file
@ -0,0 +1,27 @@
|
||||
from modules.fulfillment_base.models import Product
|
||||
from modules.fulfillment_base.repositories import ProductRepository
|
||||
from modules.fulfillment_base.schemas.product import (
|
||||
CreateProductRequest,
|
||||
ProductSchema,
|
||||
UpdateProductRequest,
|
||||
)
|
||||
from services.mixins import *
|
||||
|
||||
|
||||
class ProductService(
|
||||
ServiceGetAllMixin[Product, ProductSchema],
|
||||
ServiceCreateMixin[Product, CreateProductRequest, ProductSchema],
|
||||
ServiceUpdateMixin[Product, UpdateProductRequest],
|
||||
ServiceDeleteMixin[Product],
|
||||
):
|
||||
schema_class = ProductSchema
|
||||
entity_not_found_msg = "Товар не найден"
|
||||
entity_deleted_msg = "Товар успешно удален"
|
||||
entity_updated_msg = "Товар успешно обновлен"
|
||||
entity_created_msg = "Товар успешно создан"
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.repository = ProductRepository(session)
|
||||
|
||||
async def is_soft_delete(self, product: ProductSchema) -> bool:
|
||||
return True
|
||||
66
modules/fulfillment_base/services/product_service.py
Normal file
66
modules/fulfillment_base/services/product_service.py
Normal file
@ -0,0 +1,66 @@
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from modules.fulfillment_base.models import DealProductService
|
||||
from modules.fulfillment_base.repositories import ProductServiceRepository
|
||||
from modules.fulfillment_base.schemas.product_service import *
|
||||
|
||||
|
||||
class ProductServiceService:
|
||||
schema_class = ProductServiceSchema
|
||||
entity_not_found_msg = "Связь услуги с товаром не найдена"
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.repository = ProductServiceRepository(session)
|
||||
|
||||
async def create(
|
||||
self, request: CreateProductServiceRequest
|
||||
) -> CreateProductServiceResponse:
|
||||
await self.repository.create(request.entity)
|
||||
deal_product = await self.repository.get_by_id(
|
||||
request.entity.deal_id,
|
||||
request.entity.product_id,
|
||||
request.entity.service_id,
|
||||
)
|
||||
return CreateProductServiceResponse(
|
||||
entity=ProductServiceSchema.model_validate(deal_product),
|
||||
message="Услуга добавлена к товару",
|
||||
)
|
||||
|
||||
async def update(
|
||||
self,
|
||||
deal_id: int,
|
||||
product_id: int,
|
||||
service_id: int,
|
||||
data: UpdateProductServiceRequest,
|
||||
) -> UpdateProductServiceResponse:
|
||||
entity = await self.repository.get_by_id(deal_id, product_id, service_id)
|
||||
if not entity:
|
||||
raise HTTPException(status_code=404, detail=self.entity_not_found_msg)
|
||||
|
||||
await self.repository.update(entity, data.entity)
|
||||
return UpdateProductServiceResponse(message="Услуга обновлена")
|
||||
|
||||
async def delete(
|
||||
self, deal_id: int, product_id: int, service_id: int
|
||||
) -> DeleteProductServiceResponse:
|
||||
entity = await self.repository.get_by_id(deal_id, product_id, service_id)
|
||||
if not entity:
|
||||
raise HTTPException(status_code=404, detail=self.entity_not_found_msg)
|
||||
|
||||
await self.repository.delete(entity)
|
||||
return DeleteProductServiceResponse(message="Товар удален из сделки")
|
||||
|
||||
async def duplicate_product_services(
|
||||
self, request: ProductServicesDuplicateRequest
|
||||
) -> ProductServicesDuplicateResponse:
|
||||
services_to_copy: list[
|
||||
DealProductService
|
||||
] = await self.repository.get_product_services(
|
||||
request.deal_id, request.source_deal_product_id
|
||||
)
|
||||
|
||||
await self.repository.duplicate_services(
|
||||
request.deal_id, request.target_deal_product_ids, services_to_copy
|
||||
)
|
||||
return ProductServicesDuplicateResponse(message="Услуги продублированы")
|
||||
27
modules/fulfillment_base/services/service.py
Normal file
27
modules/fulfillment_base/services/service.py
Normal file
@ -0,0 +1,27 @@
|
||||
from modules.fulfillment_base.models import Service
|
||||
from modules.fulfillment_base.repositories import ServiceRepository
|
||||
from modules.fulfillment_base.schemas.service import (
|
||||
ServiceSchema,
|
||||
CreateServiceRequest,
|
||||
UpdateServiceRequest,
|
||||
)
|
||||
from services.mixins import *
|
||||
|
||||
|
||||
class ServiceModelService(
|
||||
ServiceGetAllMixin[Service, ServiceSchema],
|
||||
ServiceCreateMixin[Service, CreateServiceRequest, ServiceSchema],
|
||||
ServiceUpdateMixin[Service, UpdateServiceRequest],
|
||||
ServiceDeleteMixin[Service],
|
||||
):
|
||||
schema_class = ServiceSchema
|
||||
entity_not_found_msg = "Услуга не найдена"
|
||||
entity_deleted_msg = "Услуга успешно удалена"
|
||||
entity_updated_msg = "Услуга успешно обновлена"
|
||||
entity_created_msg = "Услуга успешно создана"
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.repository = ServiceRepository(session)
|
||||
|
||||
async def is_soft_delete(self, service: ServiceSchema) -> bool:
|
||||
return True
|
||||
24
modules/fulfillment_base/services/services_kit.py
Normal file
24
modules/fulfillment_base/services/services_kit.py
Normal file
@ -0,0 +1,24 @@
|
||||
from modules.fulfillment_base.models.service import ServicesKit
|
||||
from modules.fulfillment_base.repositories import ServicesKitRepository
|
||||
from modules.fulfillment_base.schemas.services_kit import (
|
||||
ServicesKitSchema,
|
||||
CreateServicesKitRequest,
|
||||
UpdateServicesKitRequest,
|
||||
)
|
||||
from services.mixins import *
|
||||
|
||||
|
||||
class ServicesKitService(
|
||||
ServiceCrudMixin[ServicesKit, ServicesKitSchema, CreateServicesKitRequest, UpdateServicesKitRequest]
|
||||
):
|
||||
schema_class = ServicesKitSchema
|
||||
entity_not_found_msg = "Набор услуг не найден"
|
||||
entity_deleted_msg = "Набор услуг успешно удален"
|
||||
entity_updated_msg = "Набор услуг успешно обновлен"
|
||||
entity_created_msg = "Набор услуг успешно создан"
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.repository = ServicesKitRepository(session)
|
||||
|
||||
async def is_soft_delete(self, service: ServicesKitSchema) -> bool:
|
||||
return False
|
||||
@ -2,3 +2,4 @@ from .board import BoardRepository as BoardRepository
|
||||
from .deal import DealRepository as DealRepository
|
||||
from .project import ProjectRepository as ProjectRepository
|
||||
from .status import StatusRepository as StatusRepository
|
||||
from .built_in_module import BuiltInModuleRepository as BuiltInModuleRepository
|
||||
|
||||
@ -3,6 +3,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
|
||||
class BaseRepository:
|
||||
session: AsyncSession
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
|
||||
|
||||
18
repositories/built_in_module.py
Normal file
18
repositories/built_in_module.py
Normal file
@ -0,0 +1,18 @@
|
||||
from models import Board, BuiltInModule
|
||||
from repositories.mixins import *
|
||||
|
||||
|
||||
class BuiltInModuleRepository(
|
||||
BaseRepository,
|
||||
RepGetAllMixin[BuiltInModule],
|
||||
):
|
||||
entity_class = BuiltInModule
|
||||
|
||||
def _process_get_all_stmt_with_args(self, stmt: Select, *args) -> Select:
|
||||
project_id = args[0]
|
||||
return stmt.where(Board.project_id == project_id).order_by(Board.lexorank)
|
||||
|
||||
async def get_by_ids(self, ids: list[int]) -> list[BuiltInModule]:
|
||||
stmt = select(BuiltInModule).where(BuiltInModule.id.in_(ids))
|
||||
built_in_modules = await self.session.scalars(stmt)
|
||||
return built_in_modules.all()
|
||||
@ -65,7 +65,7 @@ class RepUpdateMixin(Generic[EntityType, UpdateSchemaType], RepBaseMixin[EntityT
|
||||
return model
|
||||
|
||||
async def update(self, entity: EntityType, data: UpdateSchemaType) -> EntityType:
|
||||
pass
|
||||
return await self._apply_update_data_to_model(entity, data, True)
|
||||
|
||||
|
||||
class RepGetByIdMixin(Generic[EntityType], RepBaseMixin[EntityType]):
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from models.project import Project
|
||||
from repositories.built_in_module import BuiltInModuleRepository
|
||||
from repositories.mixins import *
|
||||
from schemas.project import CreateProjectSchema, UpdateProjectSchema
|
||||
|
||||
@ -17,4 +18,10 @@ class ProjectRepository(
|
||||
return stmt.options(selectinload(Project.boards))
|
||||
|
||||
async def update(self, project: Project, data: UpdateProjectSchema) -> Project:
|
||||
if data.built_in_modules is not None:
|
||||
built_in_modules = data.built_in_modules
|
||||
data.built_in_modules = await BuiltInModuleRepository(self.session).get_by_ids(
|
||||
[module.id for module in built_in_modules]
|
||||
)
|
||||
|
||||
return await self._apply_update_data_to_model(project, data, True)
|
||||
|
||||
@ -4,12 +4,10 @@ from backend.dependecies import SessionDependency
|
||||
from schemas.board import *
|
||||
from services import BoardService
|
||||
|
||||
board_router = APIRouter(
|
||||
tags=["board"],
|
||||
)
|
||||
router = APIRouter(tags=["board"])
|
||||
|
||||
|
||||
@board_router.get(
|
||||
@router.get(
|
||||
"/{projectId}",
|
||||
response_model=GetBoardsResponse,
|
||||
operation_id="get_boards",
|
||||
@ -21,7 +19,7 @@ async def get_boards(
|
||||
return await BoardService(session).get_all(project_id)
|
||||
|
||||
|
||||
@board_router.post(
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=CreateBoardResponse,
|
||||
operation_id="create_board",
|
||||
@ -33,7 +31,7 @@ async def create_board(
|
||||
return await BoardService(session).create(request)
|
||||
|
||||
|
||||
@board_router.patch(
|
||||
@router.patch(
|
||||
"/{pk}",
|
||||
response_model=UpdateBoardResponse,
|
||||
operation_id="update_board",
|
||||
@ -46,7 +44,7 @@ async def update_board(
|
||||
return await BoardService(session).update(pk, request)
|
||||
|
||||
|
||||
@board_router.delete(
|
||||
@router.delete(
|
||||
"/{pk}",
|
||||
response_model=DeleteBoardResponse,
|
||||
operation_id="delete_board",
|
||||
|
||||
@ -8,12 +8,10 @@ from backend.dependecies import (
|
||||
from schemas.deal import *
|
||||
from services import DealService
|
||||
|
||||
deal_router = APIRouter(
|
||||
tags=["deal"],
|
||||
)
|
||||
router = APIRouter(tags=["deal"])
|
||||
|
||||
|
||||
@deal_router.get(
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=GetDealsResponse,
|
||||
operation_id="get_deals",
|
||||
@ -39,7 +37,7 @@ async def get_deals(
|
||||
)
|
||||
|
||||
|
||||
@deal_router.post(
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=CreateDealResponse,
|
||||
operation_id="create_deal",
|
||||
@ -51,7 +49,7 @@ async def create_deal(
|
||||
return await DealService(session).create(request)
|
||||
|
||||
|
||||
@deal_router.patch(
|
||||
@router.patch(
|
||||
"/{pk}",
|
||||
response_model=UpdateDealResponse,
|
||||
operation_id="update_deal",
|
||||
@ -64,7 +62,7 @@ async def update_deal(
|
||||
return await DealService(session).update(pk, request)
|
||||
|
||||
|
||||
@deal_router.delete(
|
||||
@router.delete(
|
||||
"/{pk}",
|
||||
response_model=DeleteDealResponse,
|
||||
operation_id="delete_deal",
|
||||
|
||||
18
routers/module.py
Normal file
18
routers/module.py
Normal file
@ -0,0 +1,18 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from backend.dependecies import SessionDependency
|
||||
from schemas.module import GetAllBuiltInModulesResponse
|
||||
from services.built_in_module import BuiltInModuleService
|
||||
|
||||
router = APIRouter(tags=["modules"])
|
||||
|
||||
|
||||
@router.get(
|
||||
"/built-in/",
|
||||
response_model=GetAllBuiltInModulesResponse,
|
||||
operation_id="get_built_in_modules",
|
||||
)
|
||||
async def get_built_in_modules(
|
||||
session: SessionDependency,
|
||||
):
|
||||
return await BuiltInModuleService(session).get_all()
|
||||
@ -4,12 +4,10 @@ from backend.dependecies import SessionDependency
|
||||
from schemas.project import *
|
||||
from services import ProjectService
|
||||
|
||||
project_router = APIRouter(
|
||||
tags=["project"],
|
||||
)
|
||||
router = APIRouter(tags=["project"])
|
||||
|
||||
|
||||
@project_router.get(
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=GetProjectsResponse,
|
||||
operation_id="get_projects",
|
||||
@ -20,7 +18,7 @@ async def get_projects(
|
||||
return await ProjectService(session).get_all()
|
||||
|
||||
|
||||
@project_router.post(
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=CreateProjectResponse,
|
||||
operation_id="create_project",
|
||||
@ -32,7 +30,7 @@ async def create_project(
|
||||
return await ProjectService(session).create(request)
|
||||
|
||||
|
||||
@project_router.patch(
|
||||
@router.patch(
|
||||
"/{pk}",
|
||||
response_model=UpdateProjectResponse,
|
||||
operation_id="update_project",
|
||||
@ -45,7 +43,7 @@ async def update_project(
|
||||
return await ProjectService(session).update(pk, request)
|
||||
|
||||
|
||||
@project_router.delete(
|
||||
@router.delete(
|
||||
"/{pk}",
|
||||
response_model=DeleteProjectResponse,
|
||||
operation_id="delete_project",
|
||||
|
||||
@ -4,12 +4,10 @@ from backend.dependecies import SessionDependency
|
||||
from schemas.status import *
|
||||
from services import StatusService
|
||||
|
||||
status_router = APIRouter(
|
||||
tags=["status"],
|
||||
)
|
||||
router = APIRouter(tags=["status"])
|
||||
|
||||
|
||||
@status_router.get(
|
||||
@router.get(
|
||||
"/{boardId}",
|
||||
response_model=GetStatusesResponse,
|
||||
operation_id="get_statuses",
|
||||
@ -21,7 +19,7 @@ async def get_statuses(
|
||||
return await StatusService(session).get_all(board_id)
|
||||
|
||||
|
||||
@status_router.post(
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=CreateStatusResponse,
|
||||
operation_id="create_status",
|
||||
@ -33,7 +31,7 @@ async def create_status(
|
||||
return await StatusService(session).create(request)
|
||||
|
||||
|
||||
@status_router.patch(
|
||||
@router.patch(
|
||||
"/{pk}",
|
||||
response_model=UpdateStatusResponse,
|
||||
operation_id="update_status",
|
||||
@ -46,7 +44,7 @@ async def update_status(
|
||||
return await StatusService(session).update(pk, request)
|
||||
|
||||
|
||||
@status_router.delete(
|
||||
@router.delete(
|
||||
"/{pk}",
|
||||
response_model=DeleteStatusResponse,
|
||||
operation_id="delete_status",
|
||||
|
||||
23
schemas/module.py
Normal file
23
schemas/module.py
Normal file
@ -0,0 +1,23 @@
|
||||
from schemas.base import BaseSchema
|
||||
|
||||
# region Entity
|
||||
|
||||
|
||||
class BuiltInModuleSchema(BaseSchema):
|
||||
id: int
|
||||
key: str
|
||||
label: str
|
||||
icon_name: str
|
||||
description: str
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
# region Response
|
||||
|
||||
|
||||
class GetAllBuiltInModulesResponse(BaseSchema):
|
||||
items: list[BuiltInModuleSchema]
|
||||
|
||||
|
||||
# endregion
|
||||
@ -1,6 +1,7 @@
|
||||
from typing import Optional
|
||||
|
||||
from schemas.base import BaseSchema, BaseResponse
|
||||
from schemas.module import BuiltInModuleSchema
|
||||
|
||||
|
||||
# region Entity
|
||||
@ -9,6 +10,7 @@ from schemas.base import BaseSchema, BaseResponse
|
||||
class ProjectSchema(BaseSchema):
|
||||
id: int
|
||||
name: str
|
||||
built_in_modules: list[BuiltInModuleSchema]
|
||||
|
||||
|
||||
class CreateProjectSchema(BaseSchema):
|
||||
@ -17,6 +19,7 @@ class CreateProjectSchema(BaseSchema):
|
||||
|
||||
class UpdateProjectSchema(BaseSchema):
|
||||
name: Optional[str] = None
|
||||
built_in_modules: list[BuiltInModuleSchema] = None
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
11
services/built_in_module.py
Normal file
11
services/built_in_module.py
Normal file
@ -0,0 +1,11 @@
|
||||
from models import BuiltInModule
|
||||
from repositories import BuiltInModuleRepository
|
||||
from schemas.module import BuiltInModuleSchema
|
||||
from services.mixins import *
|
||||
|
||||
|
||||
class BuiltInModuleService(ServiceGetAllMixin[BuiltInModule, BuiltInModuleSchema]):
|
||||
schema_class = BuiltInModuleSchema
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.repository = BuiltInModuleRepository(session)
|
||||
51
utils/auto_include_routers.py
Normal file
51
utils/auto_include_routers.py
Normal file
@ -0,0 +1,51 @@
|
||||
import importlib
|
||||
import pkgutil
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
|
||||
from fastapi import FastAPI, APIRouter
|
||||
|
||||
|
||||
def auto_include_routers(app: FastAPI, package: ModuleType, full_path: bool = False):
|
||||
"""
|
||||
Automatically discover and include FastAPI routers from a given package.
|
||||
|
||||
:param FastAPI app: FastAPI application.
|
||||
:param ModuleType package: Imported package.
|
||||
:param bool full_path: If True, prefix is built from full path. If False, prefix is only the last filename.
|
||||
"""
|
||||
|
||||
package_path = Path(package.__file__).parent
|
||||
base_pkg_name = package.__name__.replace("_", "-")
|
||||
|
||||
for _, name, _ in pkgutil.walk_packages(package.__path__, package.__name__ + "."):
|
||||
module = importlib.import_module(name)
|
||||
|
||||
# Try to get `router` from the module
|
||||
router = getattr(module, "router", None)
|
||||
if not isinstance(router, APIRouter):
|
||||
continue
|
||||
|
||||
# Build API prefix
|
||||
file_path = Path(module.__file__)
|
||||
relative_path = file_path.relative_to(package_path)
|
||||
|
||||
parts: list[str] = list(relative_path.parts)
|
||||
|
||||
# Remove "routers" folder(s) from prefix parts
|
||||
parts = [p for p in parts if p != "routers"]
|
||||
|
||||
# Drop extension from filename
|
||||
parts[-1] = parts[-1].replace(".py", "")
|
||||
|
||||
if full_path:
|
||||
# Use full path
|
||||
prefix_parts = [p.replace("_", "-") for p in parts if p != "__init__"]
|
||||
prefix_parts.insert(0, base_pkg_name)
|
||||
else:
|
||||
# Only use last file name
|
||||
prefix_parts = [parts[-1].replace("_", "-")]
|
||||
|
||||
prefix = "/" + "/".join(prefix_parts)
|
||||
|
||||
app.include_router(router, prefix=prefix)
|
||||
2
utils/exceptions.py
Normal file
2
utils/exceptions.py
Normal file
@ -0,0 +1,2 @@
|
||||
class ObjectNotFoundException(Exception):
|
||||
pass
|
||||
Reference in New Issue
Block a user