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 import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.middleware.gzip import GZipMiddleware
|
||||||
from fastapi.responses import ORJSONResponse
|
from fastapi.responses import ORJSONResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.middleware.gzip import GZipMiddleware
|
|
||||||
from fastapi_endpoints import auto_include_routers
|
|
||||||
|
|
||||||
|
import modules
|
||||||
import routers
|
import routers
|
||||||
|
from utils.auto_include_routers import auto_include_routers
|
||||||
|
|
||||||
origins = ["http://localhost:3000"]
|
origins = ["http://localhost:3000"]
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
separate_input_output_schemas=True,
|
separate_input_output_schemas=True,
|
||||||
default_response_class=ORJSONResponse,
|
default_response_class=ORJSONResponse,
|
||||||
root_path="/api"
|
root_path="/api",
|
||||||
)
|
)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
@ -28,5 +29,6 @@ app.add_middleware(
|
|||||||
)
|
)
|
||||||
|
|
||||||
auto_include_routers(app, routers)
|
auto_include_routers(app, routers)
|
||||||
|
auto_include_routers(app, modules, True)
|
||||||
|
|
||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
from sqlalchemy.orm import configure_mappers
|
from sqlalchemy.orm import configure_mappers
|
||||||
|
|
||||||
|
from modules.fulfillment_base.models import * # noqa: F401
|
||||||
from .base import BaseModel as BaseModel
|
from .base import BaseModel as BaseModel
|
||||||
from .board import Board as Board
|
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 .deal import Deal as Deal
|
||||||
from .project import Project as Project
|
from .project import Project as Project
|
||||||
from .status import Status as Status, CardStatusHistory as CardStatusHistory
|
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 datetime import datetime, timezone
|
||||||
|
|
||||||
from sqlalchemy import DateTime
|
from sqlalchemy import DateTime, Numeric
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
|
||||||
@ -21,3 +21,11 @@ class CreatedAtMixin:
|
|||||||
default=lambda: datetime.now(timezone.utc),
|
default=lambda: datetime.now(timezone.utc),
|
||||||
nullable=False,
|
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
|
from models.mixins import SoftDeleteMixin, CreatedAtMixin, IdMixin
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from models import Board
|
from models import Board, BuiltInModule
|
||||||
|
|
||||||
|
|
||||||
class Project(BaseModel, IdMixin, SoftDeleteMixin, CreatedAtMixin):
|
class Project(BaseModel, IdMixin, SoftDeleteMixin, CreatedAtMixin):
|
||||||
@ -18,3 +18,10 @@ class Project(BaseModel, IdMixin, SoftDeleteMixin, CreatedAtMixin):
|
|||||||
back_populates="project",
|
back_populates="project",
|
||||||
lazy="noload",
|
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 .deal import DealRepository as DealRepository
|
||||||
from .project import ProjectRepository as ProjectRepository
|
from .project import ProjectRepository as ProjectRepository
|
||||||
from .status import StatusRepository as StatusRepository
|
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:
|
class BaseRepository:
|
||||||
|
session: AsyncSession
|
||||||
|
|
||||||
def __init__(self, session: AsyncSession):
|
def __init__(self, session: AsyncSession):
|
||||||
self.session = session
|
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
|
return model
|
||||||
|
|
||||||
async def update(self, entity: EntityType, data: UpdateSchemaType) -> EntityType:
|
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]):
|
class RepGetByIdMixin(Generic[EntityType], RepBaseMixin[EntityType]):
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from models.project import Project
|
from models.project import Project
|
||||||
|
from repositories.built_in_module import BuiltInModuleRepository
|
||||||
from repositories.mixins import *
|
from repositories.mixins import *
|
||||||
from schemas.project import CreateProjectSchema, UpdateProjectSchema
|
from schemas.project import CreateProjectSchema, UpdateProjectSchema
|
||||||
|
|
||||||
@ -17,4 +18,10 @@ class ProjectRepository(
|
|||||||
return stmt.options(selectinload(Project.boards))
|
return stmt.options(selectinload(Project.boards))
|
||||||
|
|
||||||
async def update(self, project: Project, data: UpdateProjectSchema) -> Project:
|
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)
|
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 schemas.board import *
|
||||||
from services import BoardService
|
from services import BoardService
|
||||||
|
|
||||||
board_router = APIRouter(
|
router = APIRouter(tags=["board"])
|
||||||
tags=["board"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@board_router.get(
|
@router.get(
|
||||||
"/{projectId}",
|
"/{projectId}",
|
||||||
response_model=GetBoardsResponse,
|
response_model=GetBoardsResponse,
|
||||||
operation_id="get_boards",
|
operation_id="get_boards",
|
||||||
@ -21,7 +19,7 @@ async def get_boards(
|
|||||||
return await BoardService(session).get_all(project_id)
|
return await BoardService(session).get_all(project_id)
|
||||||
|
|
||||||
|
|
||||||
@board_router.post(
|
@router.post(
|
||||||
"/",
|
"/",
|
||||||
response_model=CreateBoardResponse,
|
response_model=CreateBoardResponse,
|
||||||
operation_id="create_board",
|
operation_id="create_board",
|
||||||
@ -33,7 +31,7 @@ async def create_board(
|
|||||||
return await BoardService(session).create(request)
|
return await BoardService(session).create(request)
|
||||||
|
|
||||||
|
|
||||||
@board_router.patch(
|
@router.patch(
|
||||||
"/{pk}",
|
"/{pk}",
|
||||||
response_model=UpdateBoardResponse,
|
response_model=UpdateBoardResponse,
|
||||||
operation_id="update_board",
|
operation_id="update_board",
|
||||||
@ -46,7 +44,7 @@ async def update_board(
|
|||||||
return await BoardService(session).update(pk, request)
|
return await BoardService(session).update(pk, request)
|
||||||
|
|
||||||
|
|
||||||
@board_router.delete(
|
@router.delete(
|
||||||
"/{pk}",
|
"/{pk}",
|
||||||
response_model=DeleteBoardResponse,
|
response_model=DeleteBoardResponse,
|
||||||
operation_id="delete_board",
|
operation_id="delete_board",
|
||||||
|
|||||||
@ -8,12 +8,10 @@ from backend.dependecies import (
|
|||||||
from schemas.deal import *
|
from schemas.deal import *
|
||||||
from services import DealService
|
from services import DealService
|
||||||
|
|
||||||
deal_router = APIRouter(
|
router = APIRouter(tags=["deal"])
|
||||||
tags=["deal"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@deal_router.get(
|
@router.get(
|
||||||
"/",
|
"/",
|
||||||
response_model=GetDealsResponse,
|
response_model=GetDealsResponse,
|
||||||
operation_id="get_deals",
|
operation_id="get_deals",
|
||||||
@ -39,7 +37,7 @@ async def get_deals(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@deal_router.post(
|
@router.post(
|
||||||
"/",
|
"/",
|
||||||
response_model=CreateDealResponse,
|
response_model=CreateDealResponse,
|
||||||
operation_id="create_deal",
|
operation_id="create_deal",
|
||||||
@ -51,7 +49,7 @@ async def create_deal(
|
|||||||
return await DealService(session).create(request)
|
return await DealService(session).create(request)
|
||||||
|
|
||||||
|
|
||||||
@deal_router.patch(
|
@router.patch(
|
||||||
"/{pk}",
|
"/{pk}",
|
||||||
response_model=UpdateDealResponse,
|
response_model=UpdateDealResponse,
|
||||||
operation_id="update_deal",
|
operation_id="update_deal",
|
||||||
@ -64,7 +62,7 @@ async def update_deal(
|
|||||||
return await DealService(session).update(pk, request)
|
return await DealService(session).update(pk, request)
|
||||||
|
|
||||||
|
|
||||||
@deal_router.delete(
|
@router.delete(
|
||||||
"/{pk}",
|
"/{pk}",
|
||||||
response_model=DeleteDealResponse,
|
response_model=DeleteDealResponse,
|
||||||
operation_id="delete_deal",
|
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 schemas.project import *
|
||||||
from services import ProjectService
|
from services import ProjectService
|
||||||
|
|
||||||
project_router = APIRouter(
|
router = APIRouter(tags=["project"])
|
||||||
tags=["project"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@project_router.get(
|
@router.get(
|
||||||
"/",
|
"/",
|
||||||
response_model=GetProjectsResponse,
|
response_model=GetProjectsResponse,
|
||||||
operation_id="get_projects",
|
operation_id="get_projects",
|
||||||
@ -20,7 +18,7 @@ async def get_projects(
|
|||||||
return await ProjectService(session).get_all()
|
return await ProjectService(session).get_all()
|
||||||
|
|
||||||
|
|
||||||
@project_router.post(
|
@router.post(
|
||||||
"/",
|
"/",
|
||||||
response_model=CreateProjectResponse,
|
response_model=CreateProjectResponse,
|
||||||
operation_id="create_project",
|
operation_id="create_project",
|
||||||
@ -32,7 +30,7 @@ async def create_project(
|
|||||||
return await ProjectService(session).create(request)
|
return await ProjectService(session).create(request)
|
||||||
|
|
||||||
|
|
||||||
@project_router.patch(
|
@router.patch(
|
||||||
"/{pk}",
|
"/{pk}",
|
||||||
response_model=UpdateProjectResponse,
|
response_model=UpdateProjectResponse,
|
||||||
operation_id="update_project",
|
operation_id="update_project",
|
||||||
@ -45,7 +43,7 @@ async def update_project(
|
|||||||
return await ProjectService(session).update(pk, request)
|
return await ProjectService(session).update(pk, request)
|
||||||
|
|
||||||
|
|
||||||
@project_router.delete(
|
@router.delete(
|
||||||
"/{pk}",
|
"/{pk}",
|
||||||
response_model=DeleteProjectResponse,
|
response_model=DeleteProjectResponse,
|
||||||
operation_id="delete_project",
|
operation_id="delete_project",
|
||||||
|
|||||||
@ -4,12 +4,10 @@ from backend.dependecies import SessionDependency
|
|||||||
from schemas.status import *
|
from schemas.status import *
|
||||||
from services import StatusService
|
from services import StatusService
|
||||||
|
|
||||||
status_router = APIRouter(
|
router = APIRouter(tags=["status"])
|
||||||
tags=["status"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@status_router.get(
|
@router.get(
|
||||||
"/{boardId}",
|
"/{boardId}",
|
||||||
response_model=GetStatusesResponse,
|
response_model=GetStatusesResponse,
|
||||||
operation_id="get_statuses",
|
operation_id="get_statuses",
|
||||||
@ -21,7 +19,7 @@ async def get_statuses(
|
|||||||
return await StatusService(session).get_all(board_id)
|
return await StatusService(session).get_all(board_id)
|
||||||
|
|
||||||
|
|
||||||
@status_router.post(
|
@router.post(
|
||||||
"/",
|
"/",
|
||||||
response_model=CreateStatusResponse,
|
response_model=CreateStatusResponse,
|
||||||
operation_id="create_status",
|
operation_id="create_status",
|
||||||
@ -33,7 +31,7 @@ async def create_status(
|
|||||||
return await StatusService(session).create(request)
|
return await StatusService(session).create(request)
|
||||||
|
|
||||||
|
|
||||||
@status_router.patch(
|
@router.patch(
|
||||||
"/{pk}",
|
"/{pk}",
|
||||||
response_model=UpdateStatusResponse,
|
response_model=UpdateStatusResponse,
|
||||||
operation_id="update_status",
|
operation_id="update_status",
|
||||||
@ -46,7 +44,7 @@ async def update_status(
|
|||||||
return await StatusService(session).update(pk, request)
|
return await StatusService(session).update(pk, request)
|
||||||
|
|
||||||
|
|
||||||
@status_router.delete(
|
@router.delete(
|
||||||
"/{pk}",
|
"/{pk}",
|
||||||
response_model=DeleteStatusResponse,
|
response_model=DeleteStatusResponse,
|
||||||
operation_id="delete_status",
|
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 typing import Optional
|
||||||
|
|
||||||
from schemas.base import BaseSchema, BaseResponse
|
from schemas.base import BaseSchema, BaseResponse
|
||||||
|
from schemas.module import BuiltInModuleSchema
|
||||||
|
|
||||||
|
|
||||||
# region Entity
|
# region Entity
|
||||||
@ -9,6 +10,7 @@ from schemas.base import BaseSchema, BaseResponse
|
|||||||
class ProjectSchema(BaseSchema):
|
class ProjectSchema(BaseSchema):
|
||||||
id: int
|
id: int
|
||||||
name: str
|
name: str
|
||||||
|
built_in_modules: list[BuiltInModuleSchema]
|
||||||
|
|
||||||
|
|
||||||
class CreateProjectSchema(BaseSchema):
|
class CreateProjectSchema(BaseSchema):
|
||||||
@ -17,6 +19,7 @@ class CreateProjectSchema(BaseSchema):
|
|||||||
|
|
||||||
class UpdateProjectSchema(BaseSchema):
|
class UpdateProjectSchema(BaseSchema):
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
|
built_in_modules: list[BuiltInModuleSchema] = None
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# 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