feat: client endpoints for clients page
This commit is contained in:
@ -23,6 +23,14 @@ class CreatedAtMixin:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LastModifiedAtMixin:
|
||||||
|
last_modified_at: Mapped[datetime] = mapped_column(
|
||||||
|
DateTime(timezone=True),
|
||||||
|
default=lambda: datetime.now(timezone.utc),
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PriceMixin:
|
class PriceMixin:
|
||||||
price: Mapped[float] = mapped_column(Numeric(12, 2), comment="Стоимость")
|
price: Mapped[float] = mapped_column(Numeric(12, 2), comment="Стоимость")
|
||||||
|
|
||||||
|
|||||||
0
modules/clients/__init__.py
Normal file
0
modules/clients/__init__.py
Normal file
1
modules/clients/models/__init__.py
Normal file
1
modules/clients/models/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .client import Client as Client, ClientDetails as ClientDetails
|
||||||
51
modules/clients/models/client.py
Normal file
51
modules/clients/models/client.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
from sqlalchemy import ForeignKey
|
||||||
|
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||||
|
|
||||||
|
from models.base import BaseModel
|
||||||
|
from models.mixins import IdMixin, CreatedAtMixin, SoftDeleteMixin, LastModifiedAtMixin
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from models import Product
|
||||||
|
|
||||||
|
|
||||||
|
class Client(BaseModel, IdMixin, CreatedAtMixin, SoftDeleteMixin):
|
||||||
|
__tablename__ = "clients"
|
||||||
|
|
||||||
|
name: Mapped[str] = mapped_column(unique=True, comment="Название клиента")
|
||||||
|
|
||||||
|
company_name: Mapped[str] = mapped_column(
|
||||||
|
nullable=False,
|
||||||
|
comment="Название компании",
|
||||||
|
)
|
||||||
|
|
||||||
|
products: Mapped[list["Product"]] = relationship(
|
||||||
|
back_populates="client", lazy="noload"
|
||||||
|
)
|
||||||
|
|
||||||
|
details: Mapped["ClientDetails"] = relationship(
|
||||||
|
uselist=False,
|
||||||
|
back_populates="client",
|
||||||
|
cascade="all, delete",
|
||||||
|
lazy="joined",
|
||||||
|
)
|
||||||
|
|
||||||
|
comment: Mapped[Optional[str]] = mapped_column(comment="Комментарий")
|
||||||
|
|
||||||
|
|
||||||
|
class ClientDetails(BaseModel, IdMixin, LastModifiedAtMixin):
|
||||||
|
__tablename__ = "client_details"
|
||||||
|
|
||||||
|
client_id: Mapped[int] = mapped_column(
|
||||||
|
ForeignKey("clients.id"), unique=True, comment="ID клиента"
|
||||||
|
)
|
||||||
|
client: Mapped[Client] = relationship(
|
||||||
|
back_populates="details", cascade="all, delete", uselist=False
|
||||||
|
)
|
||||||
|
|
||||||
|
telegram: Mapped[Optional[str]] = mapped_column()
|
||||||
|
phone_number: Mapped[Optional[str]] = mapped_column()
|
||||||
|
inn: Mapped[Optional[str]] = mapped_column()
|
||||||
|
email: Mapped[Optional[str]] = mapped_column()
|
||||||
1
modules/clients/repositories/__init__.py
Normal file
1
modules/clients/repositories/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .client import ClientRepository as ClientRepository
|
||||||
47
modules/clients/repositories/client.py
Normal file
47
modules/clients/repositories/client.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
from datetime import timezone, datetime
|
||||||
|
|
||||||
|
from sqlalchemy import update
|
||||||
|
|
||||||
|
from modules.clients.models import Client, ClientDetails
|
||||||
|
from modules.clients.schemas.client import UpdateClientSchema, CreateClientSchema
|
||||||
|
from repositories.mixins import *
|
||||||
|
|
||||||
|
|
||||||
|
class ClientRepository(
|
||||||
|
BaseRepository,
|
||||||
|
RepGetAllMixin[Client],
|
||||||
|
RepDeleteMixin[Client],
|
||||||
|
RepUpdateMixin[Client, UpdateClientSchema],
|
||||||
|
RepGetByIdMixin[Client],
|
||||||
|
):
|
||||||
|
entity_class = Client
|
||||||
|
entity_not_found_msg = "Клиент не найден"
|
||||||
|
|
||||||
|
def _process_get_all_stmt(self, stmt: Select) -> Select:
|
||||||
|
return stmt.where(Client.is_deleted.is_(False)).order_by(Client.created_at)
|
||||||
|
|
||||||
|
async def create(self, data: CreateClientSchema) -> int:
|
||||||
|
details = ClientDetails(**data.details.model_dump())
|
||||||
|
data_dict = data.model_dump()
|
||||||
|
data_dict["details"] = details
|
||||||
|
|
||||||
|
client = Client(**data_dict)
|
||||||
|
self.session.add(client)
|
||||||
|
await self.session.commit()
|
||||||
|
await self.session.refresh(client)
|
||||||
|
return client.id
|
||||||
|
|
||||||
|
async def update(self, client: Client, data: UpdateClientSchema) -> Client:
|
||||||
|
if data.details is not None:
|
||||||
|
stmt = (
|
||||||
|
update(ClientDetails)
|
||||||
|
.where(ClientDetails.client_id == client.id)
|
||||||
|
.values(
|
||||||
|
**data.details.model_dump(),
|
||||||
|
last_modified_at=datetime.now(timezone.utc),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await self.session.execute(stmt)
|
||||||
|
del data.details
|
||||||
|
|
||||||
|
return await self._apply_update_data_to_model(client, data, True)
|
||||||
0
modules/clients/routers/__init__.py
Normal file
0
modules/clients/routers/__init__.py
Normal file
55
modules/clients/routers/client.py
Normal file
55
modules/clients/routers/client.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
from fastapi import APIRouter, Path
|
||||||
|
|
||||||
|
from backend.dependecies import SessionDependency
|
||||||
|
from modules.clients.schemas.client import *
|
||||||
|
from modules.clients.services import ClientService
|
||||||
|
|
||||||
|
router = APIRouter(tags=["client"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/",
|
||||||
|
response_model=GetClientsResponse,
|
||||||
|
operation_id="get_clients",
|
||||||
|
)
|
||||||
|
async def get_clients(
|
||||||
|
session: SessionDependency,
|
||||||
|
):
|
||||||
|
return await ClientService(session).get_all()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/",
|
||||||
|
response_model=CreateClientResponse,
|
||||||
|
operation_id="create_client",
|
||||||
|
)
|
||||||
|
async def create_client(
|
||||||
|
session: SessionDependency,
|
||||||
|
request: CreateClientRequest,
|
||||||
|
):
|
||||||
|
return await ClientService(session).create(request)
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch(
|
||||||
|
"/{pk}",
|
||||||
|
response_model=UpdateClientResponse,
|
||||||
|
operation_id="update_client",
|
||||||
|
)
|
||||||
|
async def update_client(
|
||||||
|
session: SessionDependency,
|
||||||
|
request: UpdateClientRequest,
|
||||||
|
pk: int = Path(),
|
||||||
|
):
|
||||||
|
return await ClientService(session).update(pk, request)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/{pk}",
|
||||||
|
response_model=DeleteClientResponse,
|
||||||
|
operation_id="delete_client",
|
||||||
|
)
|
||||||
|
async def delete_product(
|
||||||
|
session: SessionDependency,
|
||||||
|
pk: int = Path(),
|
||||||
|
):
|
||||||
|
return await ClientService(session).delete(pk)
|
||||||
74
modules/clients/schemas/client.py
Normal file
74
modules/clients/schemas/client.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from schemas.base import BaseSchema, BaseResponse
|
||||||
|
|
||||||
|
|
||||||
|
# region Entities
|
||||||
|
|
||||||
|
|
||||||
|
class ClientDetailsSchema(BaseSchema):
|
||||||
|
telegram: str
|
||||||
|
phone_number: str
|
||||||
|
inn: str
|
||||||
|
email: str
|
||||||
|
|
||||||
|
|
||||||
|
class CreateClientSchema(BaseSchema):
|
||||||
|
name: str
|
||||||
|
company_name: str
|
||||||
|
comment: Optional[str] = ""
|
||||||
|
details: ClientDetailsSchema
|
||||||
|
|
||||||
|
|
||||||
|
class ClientSchema(CreateClientSchema):
|
||||||
|
id: int
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateClientSchema(BaseSchema):
|
||||||
|
name: Optional[str] = None
|
||||||
|
company_name: Optional[str] = None
|
||||||
|
comment: Optional[str] = None
|
||||||
|
details: Optional[ClientDetailsSchema] = None
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region Requests
|
||||||
|
|
||||||
|
|
||||||
|
class ClientUpdateDetailsRequest(BaseSchema):
|
||||||
|
client_id: int
|
||||||
|
details: ClientDetailsSchema
|
||||||
|
|
||||||
|
|
||||||
|
class CreateClientRequest(BaseSchema):
|
||||||
|
entity: CreateClientSchema
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateClientRequest(BaseSchema):
|
||||||
|
entity: UpdateClientSchema
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
|
||||||
|
# region Responses
|
||||||
|
|
||||||
|
|
||||||
|
class GetClientsResponse(BaseSchema):
|
||||||
|
items: list[ClientSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateClientResponse(BaseResponse):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CreateClientResponse(BaseResponse):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteClientResponse(BaseResponse):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
1
modules/clients/services/__init__.py
Normal file
1
modules/clients/services/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .client import ClientService as ClientService
|
||||||
26
modules/clients/services/client.py
Normal file
26
modules/clients/services/client.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from modules.clients.models import Client
|
||||||
|
from modules.clients.repositories import ClientRepository
|
||||||
|
from modules.clients.schemas.client import (
|
||||||
|
ClientSchema,
|
||||||
|
CreateClientRequest,
|
||||||
|
UpdateClientRequest,
|
||||||
|
)
|
||||||
|
from services.mixins import *
|
||||||
|
|
||||||
|
|
||||||
|
class ClientService(
|
||||||
|
ServiceGetAllMixin[Client, ClientSchema],
|
||||||
|
ServiceCreateMixin[Client, CreateClientRequest, ClientSchema],
|
||||||
|
ServiceUpdateMixin[Client, UpdateClientRequest],
|
||||||
|
ServiceDeleteMixin[Client],
|
||||||
|
):
|
||||||
|
schema_class = ClientSchema
|
||||||
|
entity_deleted_msg = "Клиент успешно удален"
|
||||||
|
entity_updated_msg = "Клиент успешно обновлен"
|
||||||
|
entity_created_msg = "Клиент успешно создан"
|
||||||
|
|
||||||
|
def __init__(self, session: AsyncSession):
|
||||||
|
self.repository = ClientRepository(session)
|
||||||
|
|
||||||
|
async def is_soft_delete(self, client: ClientSchema) -> bool:
|
||||||
|
return True
|
||||||
@ -5,6 +5,7 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|||||||
|
|
||||||
from models.base import BaseModel
|
from models.base import BaseModel
|
||||||
from models.mixins import IdMixin, SoftDeleteMixin
|
from models.mixins import IdMixin, SoftDeleteMixin
|
||||||
|
from modules.clients.models import Client
|
||||||
from modules.fulfillment_base.models import (
|
from modules.fulfillment_base.models import (
|
||||||
ProductBarcode,
|
ProductBarcode,
|
||||||
BarcodeTemplate,
|
BarcodeTemplate,
|
||||||
@ -28,6 +29,9 @@ class Product(BaseModel, IdMixin, SoftDeleteMixin):
|
|||||||
default="", comment="Дополнительная информация"
|
default="", comment="Дополнительная информация"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
client_id: Mapped[int] = mapped_column(ForeignKey("clients.id"))
|
||||||
|
client: Mapped["Client"] = relationship(back_populates="products")
|
||||||
|
|
||||||
images: Mapped[list["ProductImage"]] = relationship(
|
images: Mapped[list["ProductImage"]] = relationship(
|
||||||
"ProductImage",
|
"ProductImage",
|
||||||
back_populates="product",
|
back_populates="product",
|
||||||
|
|||||||
Reference in New Issue
Block a user