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:
|
||||
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.mixins import IdMixin, SoftDeleteMixin
|
||||
from modules.clients.models import Client
|
||||
from modules.fulfillment_base.models import (
|
||||
ProductBarcode,
|
||||
BarcodeTemplate,
|
||||
@ -28,6 +29,9 @@ class Product(BaseModel, IdMixin, SoftDeleteMixin):
|
||||
default="", comment="Дополнительная информация"
|
||||
)
|
||||
|
||||
client_id: Mapped[int] = mapped_column(ForeignKey("clients.id"))
|
||||
client: Mapped["Client"] = relationship(back_populates="products")
|
||||
|
||||
images: Mapped[list["ProductImage"]] = relationship(
|
||||
"ProductImage",
|
||||
back_populates="product",
|
||||
|
||||
Reference in New Issue
Block a user