feat: attributes in cards and projects
This commit is contained in:
@@ -5,7 +5,7 @@ from sqlalchemy import select, and_
|
|||||||
|
|
||||||
from card_attributes.exceptions import CardAttributeException
|
from card_attributes.exceptions import CardAttributeException
|
||||||
from card_attributes.handlers.base_handler import BaseHandler
|
from card_attributes.handlers.base_handler import BaseHandler
|
||||||
from models import CardAttribute, Attribute, Card
|
from models import CardAttribute, Attribute, Card, Project
|
||||||
from .card_attributes_query_handler import CardAttributesQueryHandler
|
from .card_attributes_query_handler import CardAttributesQueryHandler
|
||||||
|
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ class CardAttributesCommandHandler(BaseHandler):
|
|||||||
self.session.add(card_attribute)
|
self.session.add(card_attribute)
|
||||||
await self.session.flush()
|
await self.session.flush()
|
||||||
|
|
||||||
async def _set_attribute_after_creation(self, card_id: int, project_attr: Attribute, attributes: Optional[dict]):
|
async def _set_attribute_after_creation(self, card_id: int, project_attr: Attribute, attributes: Optional[dict] = None):
|
||||||
if attributes and project_attr.name in attributes:
|
if attributes and project_attr.name in attributes:
|
||||||
passed_value = attributes[project_attr.name]
|
passed_value = attributes[project_attr.name]
|
||||||
return await self._create_card_attribute(card_id, project_attr.id, passed_value)
|
return await self._create_card_attribute(card_id, project_attr.id, passed_value)
|
||||||
@@ -65,7 +65,7 @@ class CardAttributesCommandHandler(BaseHandler):
|
|||||||
else:
|
else:
|
||||||
card_attribute.set_value(value)
|
card_attribute.set_value(value)
|
||||||
|
|
||||||
async def set_attr_for_each_in_group(self, group_id: int, attribute_name: str, value):
|
async def set_attr_for_each_card_in_group(self, group_id: int, attribute_name: str, value):
|
||||||
query_handler = CardAttributesQueryHandler(self.session)
|
query_handler = CardAttributesQueryHandler(self.session)
|
||||||
card_ids: list[int] = await query_handler.get_card_ids_by_group_id(group_id)
|
card_ids: list[int] = await query_handler.get_card_ids_by_group_id(group_id)
|
||||||
|
|
||||||
@@ -81,10 +81,33 @@ class CardAttributesCommandHandler(BaseHandler):
|
|||||||
try:
|
try:
|
||||||
attr = next(attr for attr in project_attrs if attr.name == attr_name)
|
attr = next(attr for attr in project_attrs if attr.name == attr_name)
|
||||||
if attr.is_applicable_to_group and card.group:
|
if attr.is_applicable_to_group and card.group:
|
||||||
await self.set_attr_for_each_in_group(card.group.id, attr_name, attr_value)
|
await self.set_attr_for_each_card_in_group(card.group.id, attr_name, attr_value)
|
||||||
else:
|
else:
|
||||||
await self._set_card_attribute(card.id, attr_name, attr_value)
|
await self._set_card_attribute(card.id, attr_name, attr_value)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
pass
|
pass
|
||||||
except CardAttributeException:
|
except CardAttributeException:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
async def set_project_attributes(self, project: Project, attribute_ids: list[int]):
|
||||||
|
query_handler = CardAttributesQueryHandler(self.session)
|
||||||
|
attributes = await query_handler.get_attributes_by_ids(attribute_ids)
|
||||||
|
|
||||||
|
attributes_to_create = []
|
||||||
|
for attribute in attributes:
|
||||||
|
project_attr = await query_handler.get_project_attr(project.id, attribute.id)
|
||||||
|
if not project_attr:
|
||||||
|
attributes_to_create.append(attribute)
|
||||||
|
|
||||||
|
async for card in query_handler.get_all_cards_for_project(project.id):
|
||||||
|
await self._add_attributes_to_card(card, attributes_to_create)
|
||||||
|
|
||||||
|
project.attributes = attributes
|
||||||
|
await self.session.commit()
|
||||||
|
|
||||||
|
async def _add_attributes_to_card(self, card: Card, attributes_to_create: list[Attribute]):
|
||||||
|
card_attribute_ids: set[int] = set((attr.attribute_id for attr in card.attributes))
|
||||||
|
|
||||||
|
for attribute in attributes_to_create:
|
||||||
|
if attribute.id not in card_attribute_ids:
|
||||||
|
await self._set_attribute_after_creation(card.id, attribute)
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select, and_
|
||||||
from sqlalchemy.orm import joinedload, selectinload
|
from sqlalchemy.ext.asyncio import AsyncResult
|
||||||
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from card_attributes.handlers.base_handler import BaseHandler
|
from card_attributes.handlers.base_handler import BaseHandler
|
||||||
from models import Attribute, project_attribute, card_relations
|
from models import Attribute, project_attribute, card_relations, Card, Project, Board
|
||||||
|
|
||||||
|
|
||||||
class CardAttributesQueryHandler(BaseHandler):
|
class CardAttributesQueryHandler(BaseHandler):
|
||||||
@@ -12,7 +13,10 @@ class CardAttributesQueryHandler(BaseHandler):
|
|||||||
stmt = (
|
stmt = (
|
||||||
select(Attribute)
|
select(Attribute)
|
||||||
.join(project_attribute, project_attribute.c.attribute_id == Attribute.id)
|
.join(project_attribute, project_attribute.c.attribute_id == Attribute.id)
|
||||||
.where(project_attribute.c.project_id == project_id)
|
.where(
|
||||||
|
project_attribute.c.project_id == project_id,
|
||||||
|
Attribute.is_deleted == False,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
attributes = (await self.session.scalars(stmt)).all()
|
attributes = (await self.session.scalars(stmt)).all()
|
||||||
return list(attributes)
|
return list(attributes)
|
||||||
@@ -23,7 +27,10 @@ class CardAttributesQueryHandler(BaseHandler):
|
|||||||
.options(
|
.options(
|
||||||
selectinload(Attribute.projects),
|
selectinload(Attribute.projects),
|
||||||
)
|
)
|
||||||
.where(Attribute.name == attr_name)
|
.where(
|
||||||
|
Attribute.name == attr_name,
|
||||||
|
Attribute.is_deleted == False,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
attribute = (await self.session.scalars(stmt)).first()
|
attribute = (await self.session.scalars(stmt)).first()
|
||||||
return attribute
|
return attribute
|
||||||
@@ -35,3 +42,37 @@ class CardAttributesQueryHandler(BaseHandler):
|
|||||||
)
|
)
|
||||||
ids = await self.session.scalars(stmt)
|
ids = await self.session.scalars(stmt)
|
||||||
return list(ids)
|
return list(ids)
|
||||||
|
|
||||||
|
async def get_all_cards_for_project(self, project_id: int) -> AsyncResult[Card]:
|
||||||
|
stmt = (
|
||||||
|
select(Card)
|
||||||
|
.join(Board)
|
||||||
|
.join(Project)
|
||||||
|
.where(Project.id == project_id)
|
||||||
|
.options(selectinload(Card.attributes))
|
||||||
|
.execution_options(yield_per=100)
|
||||||
|
)
|
||||||
|
rows: AsyncResult[tuple[Card]] = await self.session.stream(stmt)
|
||||||
|
async for row in rows:
|
||||||
|
yield row[0]
|
||||||
|
|
||||||
|
async def get_project_attr(self, project_id: int, attribute_id: int) -> project_attribute:
|
||||||
|
stmt_is_attribute_already_added = (
|
||||||
|
select(project_attribute)
|
||||||
|
.where(
|
||||||
|
and_(
|
||||||
|
project_attribute.c.project_id == project_id,
|
||||||
|
project_attribute.c.attribute_id == attribute_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
project_attribute_inst = await self.session.execute(stmt_is_attribute_already_added)
|
||||||
|
return project_attribute_inst.first()
|
||||||
|
|
||||||
|
async def get_attributes_by_ids(self, attribute_ids: list[int]) -> list[Attribute]:
|
||||||
|
stmt = (
|
||||||
|
select(Attribute)
|
||||||
|
.where(Attribute.id.in_(attribute_ids))
|
||||||
|
)
|
||||||
|
attributes = (await self.session.scalars(stmt)).all()
|
||||||
|
return list(attributes)
|
||||||
|
|||||||
1
main.py
1
main.py
@@ -30,6 +30,7 @@ app.add_middleware(
|
|||||||
)
|
)
|
||||||
|
|
||||||
routers_list = [
|
routers_list = [
|
||||||
|
routers.attribute_router,
|
||||||
routers.auth_router,
|
routers.auth_router,
|
||||||
routers.card_router,
|
routers.card_router,
|
||||||
routers.card_group_router,
|
routers.card_group_router,
|
||||||
|
|||||||
@@ -37,13 +37,15 @@ class Attribute(BaseModel):
|
|||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
label: Mapped[str] = mapped_column(nullable=False)
|
label: Mapped[str] = mapped_column(nullable=False)
|
||||||
name: Mapped[str] = mapped_column(nullable=False, unique=True)
|
name: Mapped[str] = mapped_column(nullable=False, index=True)
|
||||||
is_applicable_to_group: Mapped[bool] = mapped_column(
|
is_applicable_to_group: Mapped[bool] = mapped_column(
|
||||||
default=False,
|
default=False,
|
||||||
comment='Применять ли изменения атрибута карточки ко всем карточкам в группе',
|
comment='Применять ли изменения атрибута карточки ко всем карточкам в группе',
|
||||||
)
|
)
|
||||||
is_nullable: Mapped[bool] = mapped_column(default=False, nullable=False)
|
is_nullable: Mapped[bool] = mapped_column(default=False, nullable=False)
|
||||||
default_value: Mapped[bytes] = mapped_column(nullable=True)
|
default_value: Mapped[bytes] = mapped_column(nullable=True)
|
||||||
|
is_deleted: Mapped[bool] = mapped_column(default=False)
|
||||||
|
description: Mapped[str] = mapped_column(default="", nullable=False)
|
||||||
|
|
||||||
projects: Mapped[list['Project']] = relationship(
|
projects: Mapped[list['Project']] = relationship(
|
||||||
'Project',
|
'Project',
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class Module(BaseModel):
|
|||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
key: Mapped[str] = mapped_column(unique=True, nullable=False)
|
key: Mapped[str] = mapped_column(unique=True, nullable=False)
|
||||||
|
label: Mapped[str] = mapped_column(nullable=False)
|
||||||
is_deleted: Mapped[bool] = mapped_column(default=False)
|
is_deleted: Mapped[bool] = mapped_column(default=False)
|
||||||
|
|
||||||
projects: Mapped[list['Project']] = relationship(
|
projects: Mapped[list['Project']] = relationship(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from .attribute import attribute_router
|
||||||
from .auth import auth_router
|
from .auth import auth_router
|
||||||
from .card import card_router
|
from .card import card_router
|
||||||
from .group import card_group_router
|
from .group import card_group_router
|
||||||
|
|||||||
77
routers/attribute.py
Normal file
77
routers/attribute.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from backend.session import get_session
|
||||||
|
from schemas.attribute import *
|
||||||
|
from services.attribute import AttributeService
|
||||||
|
from services.auth import authorized_user
|
||||||
|
|
||||||
|
attribute_router = APIRouter(
|
||||||
|
prefix='/attribute',
|
||||||
|
tags=['attribute'],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@attribute_router.get(
|
||||||
|
'/',
|
||||||
|
response_model=GetAttributesResponse,
|
||||||
|
operation_id='get_all',
|
||||||
|
dependencies=[Depends(authorized_user)]
|
||||||
|
)
|
||||||
|
async def get_all(
|
||||||
|
session: Annotated[AsyncSession, Depends(get_session)]
|
||||||
|
):
|
||||||
|
return await AttributeService(session).get_all()
|
||||||
|
|
||||||
|
|
||||||
|
@attribute_router.get(
|
||||||
|
'/types',
|
||||||
|
response_model=GetAttributeTypesResponse,
|
||||||
|
operation_id='get_types',
|
||||||
|
dependencies=[Depends(authorized_user)]
|
||||||
|
)
|
||||||
|
async def get_types(
|
||||||
|
session: Annotated[AsyncSession, Depends(get_session)]
|
||||||
|
):
|
||||||
|
return await AttributeService(session).get_types()
|
||||||
|
|
||||||
|
|
||||||
|
@attribute_router.post(
|
||||||
|
'/',
|
||||||
|
response_model=CreateAttributeResponse,
|
||||||
|
operation_id='create',
|
||||||
|
dependencies=[Depends(authorized_user)]
|
||||||
|
)
|
||||||
|
async def create(
|
||||||
|
request: CreateAttributeRequest,
|
||||||
|
session: Annotated[AsyncSession, Depends(get_session)]
|
||||||
|
):
|
||||||
|
return await AttributeService(session).create(request)
|
||||||
|
|
||||||
|
|
||||||
|
@attribute_router.patch(
|
||||||
|
'/',
|
||||||
|
response_model=UpdateAttributeResponse,
|
||||||
|
operation_id='update',
|
||||||
|
dependencies=[Depends(authorized_user)]
|
||||||
|
)
|
||||||
|
async def update(
|
||||||
|
request: UpdateAttributeRequest,
|
||||||
|
session: Annotated[AsyncSession, Depends(get_session)]
|
||||||
|
):
|
||||||
|
return await AttributeService(session).update(request)
|
||||||
|
|
||||||
|
|
||||||
|
@attribute_router.delete(
|
||||||
|
'/{attribute_id}',
|
||||||
|
response_model=DeleteAttributeResponse,
|
||||||
|
operation_id='delete',
|
||||||
|
dependencies=[Depends(authorized_user)]
|
||||||
|
)
|
||||||
|
async def delete(
|
||||||
|
session: Annotated[AsyncSession, Depends(get_session)],
|
||||||
|
attribute_id: int,
|
||||||
|
):
|
||||||
|
return await AttributeService(session).delete(attribute_id)
|
||||||
@@ -63,3 +63,38 @@ async def delete_project(
|
|||||||
project_id: int,
|
project_id: int,
|
||||||
):
|
):
|
||||||
return await ProjectService(session).delete_project(project_id)
|
return await ProjectService(session).delete_project(project_id)
|
||||||
|
|
||||||
|
|
||||||
|
@project_router.get(
|
||||||
|
"/modules",
|
||||||
|
response_model=GetAllModulesResponse,
|
||||||
|
operation_id="get_all_modules",
|
||||||
|
)
|
||||||
|
async def get_all_modules(
|
||||||
|
session: Annotated[AsyncSession, Depends(get_session)],
|
||||||
|
):
|
||||||
|
return await ProjectService(session).get_all_modules()
|
||||||
|
|
||||||
|
|
||||||
|
@project_router.post(
|
||||||
|
"/modules",
|
||||||
|
response_model=UpdateModulesResponse,
|
||||||
|
operation_id="update_project_modules",
|
||||||
|
)
|
||||||
|
async def update_project_modules(
|
||||||
|
session: Annotated[AsyncSession, Depends(get_session)],
|
||||||
|
request: UpdateModulesRequest,
|
||||||
|
):
|
||||||
|
return await ProjectService(session).update_project_modules(request)
|
||||||
|
|
||||||
|
|
||||||
|
@project_router.post(
|
||||||
|
"/attributes",
|
||||||
|
response_model=UpdateAttributesResponse,
|
||||||
|
operation_id="update_project_attributes",
|
||||||
|
)
|
||||||
|
async def update_project_attributes(
|
||||||
|
session: Annotated[AsyncSession, Depends(get_session)],
|
||||||
|
request: UpdateAttributesRequest,
|
||||||
|
):
|
||||||
|
return await ProjectService(session).update_project_attributes(request)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import Optional
|
|||||||
|
|
||||||
from pydantic import field_validator
|
from pydantic import field_validator
|
||||||
|
|
||||||
from schemas.base import BaseSchema
|
from schemas.base import BaseSchema, OkMessageSchema
|
||||||
|
|
||||||
|
|
||||||
# region Entities
|
# region Entities
|
||||||
@@ -16,17 +16,25 @@ class AttributeTypeSchema(BaseSchema):
|
|||||||
is_deleted: bool
|
is_deleted: bool
|
||||||
|
|
||||||
|
|
||||||
class AttributeSchema(BaseSchema):
|
class BaseAttributeSchema(BaseSchema):
|
||||||
id: int
|
|
||||||
label: str
|
label: str
|
||||||
name: str
|
name: str
|
||||||
is_applicable_to_group: bool
|
is_applicable_to_group: bool
|
||||||
is_nullable: bool
|
is_nullable: bool
|
||||||
default_value: Optional[bool | int | float | str | date | datetime]
|
default_value: Optional[bool | int | float | str | date | datetime]
|
||||||
|
type_id: int
|
||||||
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeSchema(BaseAttributeSchema):
|
||||||
|
id: int
|
||||||
type: AttributeTypeSchema
|
type: AttributeTypeSchema
|
||||||
|
is_deleted: bool
|
||||||
|
|
||||||
@field_validator("default_value", mode="before")
|
@field_validator("default_value", mode="before")
|
||||||
def validate_default_value(cls, value: Optional[bytes]):
|
def validate_default_value(cls, value: Optional[bytes]):
|
||||||
|
if not isinstance(value, bytes):
|
||||||
|
return value
|
||||||
return pickle.loads(value) if value else None
|
return pickle.loads(value) if value else None
|
||||||
|
|
||||||
|
|
||||||
@@ -41,3 +49,37 @@ class CardAttributeSchema(BaseSchema):
|
|||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
|
# region Requests
|
||||||
|
|
||||||
|
class CreateAttributeRequest(BaseSchema):
|
||||||
|
attribute: BaseAttributeSchema
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateAttributeRequest(BaseSchema):
|
||||||
|
attribute: AttributeSchema
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region Responses
|
||||||
|
|
||||||
|
class GetAttributesResponse(BaseSchema):
|
||||||
|
attributes: list[AttributeSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class GetAttributeTypesResponse(BaseSchema):
|
||||||
|
types: list[AttributeTypeSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class CreateAttributeResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateAttributeResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteAttributeResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ class CardGeneralInfoSchema(BaseSchemaWithAttributes):
|
|||||||
board_id: int
|
board_id: int
|
||||||
status_id: int
|
status_id: int
|
||||||
is_services_profit_accounted: bool
|
is_services_profit_accounted: bool
|
||||||
|
client_id: Optional[int]
|
||||||
|
|
||||||
|
|
||||||
class OptionalShippingWarehouseSchema(BaseShippingWarehouseSchema):
|
class OptionalShippingWarehouseSchema(BaseShippingWarehouseSchema):
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
from schemas.base import BaseSchema
|
|
||||||
|
|
||||||
|
|
||||||
# region Entities
|
|
||||||
|
|
||||||
class ModuleSchema(BaseSchema):
|
|
||||||
id: int
|
|
||||||
key: str
|
|
||||||
is_deleted: bool
|
|
||||||
|
|
||||||
# endregion
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
from schemas.attribute import AttributeSchema
|
from schemas.attribute import AttributeSchema
|
||||||
from schemas.base import BaseSchema, OkMessageSchema
|
from schemas.base import BaseSchema, OkMessageSchema
|
||||||
from schemas.module import ModuleSchema
|
|
||||||
|
|
||||||
|
|
||||||
# region Entities
|
# region Entities
|
||||||
@@ -10,6 +9,13 @@ class BaseProjectSchema(BaseSchema):
|
|||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleSchema(BaseSchema):
|
||||||
|
id: int
|
||||||
|
key: str
|
||||||
|
label: str
|
||||||
|
is_deleted: bool
|
||||||
|
|
||||||
|
|
||||||
class ProjectSchema(BaseProjectSchema):
|
class ProjectSchema(BaseProjectSchema):
|
||||||
id: int
|
id: int
|
||||||
attributes: list[AttributeSchema]
|
attributes: list[AttributeSchema]
|
||||||
@@ -32,6 +38,16 @@ class UpdateProjectRequest(BaseSchema):
|
|||||||
project: ProjectSchema
|
project: ProjectSchema
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateModulesRequest(BaseSchema):
|
||||||
|
project_id: int
|
||||||
|
module_ids: list[int]
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateAttributesRequest(BaseSchema):
|
||||||
|
project_id: int
|
||||||
|
attribute_ids: list[int]
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -52,4 +68,16 @@ class UpdateProjectResponse(OkMessageSchema):
|
|||||||
class DeleteProjectResponse(OkMessageSchema):
|
class DeleteProjectResponse(OkMessageSchema):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GetAllModulesResponse(BaseSchema):
|
||||||
|
modules: list[ModuleSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateModulesResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateAttributesResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|||||||
85
services/attribute.py
Normal file
85
services/attribute.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
from sqlalchemy import select, and_
|
||||||
|
|
||||||
|
from models import Attribute, AttributeType
|
||||||
|
from schemas.attribute import *
|
||||||
|
from services.base import BaseService
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeService(BaseService):
|
||||||
|
async def get_all(self) -> GetAttributesResponse:
|
||||||
|
stmt = (
|
||||||
|
select(Attribute)
|
||||||
|
.where(Attribute.is_deleted == False)
|
||||||
|
.order_by(Attribute.label)
|
||||||
|
)
|
||||||
|
attrs = (await self.session.scalars(stmt)).all()
|
||||||
|
return GetAttributesResponse(attributes=attrs)
|
||||||
|
|
||||||
|
async def get_types(self) -> GetAttributeTypesResponse:
|
||||||
|
stmt = (
|
||||||
|
select(AttributeType)
|
||||||
|
.where(AttributeType.is_deleted == False)
|
||||||
|
)
|
||||||
|
types = (await self.session.scalars(stmt)).all()
|
||||||
|
return GetAttributeTypesResponse(types=types)
|
||||||
|
|
||||||
|
async def get_attr_by_name(self, attr_name: str) -> Optional[Attribute]:
|
||||||
|
stmt = (
|
||||||
|
select(Attribute)
|
||||||
|
.where(
|
||||||
|
and_(
|
||||||
|
Attribute.name == attr_name,
|
||||||
|
Attribute.is_deleted == False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
attribute = (await self.session.scalars(stmt)).first()
|
||||||
|
return attribute
|
||||||
|
|
||||||
|
async def create(self, request: CreateAttributeRequest) -> CreateAttributeResponse:
|
||||||
|
existing_attr = await self.get_attr_by_name(request.attribute.name)
|
||||||
|
if existing_attr:
|
||||||
|
return CreateAttributeResponse(ok=False, message="Атрибут с данным уникальным ключом уже существует")
|
||||||
|
|
||||||
|
default_value = pickle.dumps(request.attribute.default_value)
|
||||||
|
values = request.attribute.model_dump()
|
||||||
|
del values["default_value"]
|
||||||
|
|
||||||
|
attribute = Attribute(
|
||||||
|
**values,
|
||||||
|
default_value=default_value,
|
||||||
|
)
|
||||||
|
self.session.add(attribute)
|
||||||
|
await self.session.commit()
|
||||||
|
return CreateAttributeResponse(ok=True, message="Атрибут успешно создан")
|
||||||
|
|
||||||
|
async def update(self, request: UpdateAttributeRequest) -> UpdateAttributeResponse:
|
||||||
|
attribute = await self.session.get(Attribute, request.attribute.id)
|
||||||
|
if not attribute:
|
||||||
|
return UpdateAttributeResponse(ok=False, message=f"Атрибут с ID {request.attribute.id} не найден")
|
||||||
|
|
||||||
|
if attribute.name != request.attribute.name:
|
||||||
|
attr_with_same_name = await self.get_attr_by_name(request.attribute.name)
|
||||||
|
if attr_with_same_name:
|
||||||
|
return CreateAttributeResponse(ok=False, message="Атрибут с данным уникальным ключом уже существует")
|
||||||
|
|
||||||
|
default_value = pickle.dumps(request.attribute.default_value) if request.attribute.default_value else None
|
||||||
|
|
||||||
|
attribute.name = request.attribute.name
|
||||||
|
attribute.label = request.attribute.label
|
||||||
|
attribute.default_value = default_value
|
||||||
|
attribute.is_applicable_to_group = request.attribute.is_applicable_to_group
|
||||||
|
attribute.is_nullable = request.attribute.is_nullable
|
||||||
|
attribute.description = request.attribute.description
|
||||||
|
await self.session.commit()
|
||||||
|
|
||||||
|
return UpdateAttributeResponse(ok=True, message="Атрибут успешно обновлен")
|
||||||
|
|
||||||
|
async def delete(self, attribute_id: int) -> DeleteAttributeResponse:
|
||||||
|
attribute: Optional[Attribute] = await self.session.get(Attribute, attribute_id)
|
||||||
|
if not attribute:
|
||||||
|
return DeleteAttributeResponse(ok=False, message=f"Атрибут с ID {attribute_id} не найден")
|
||||||
|
|
||||||
|
attribute.is_deleted = True
|
||||||
|
await self.session.commit()
|
||||||
|
return DeleteAttributeResponse(ok=True, message=f"Атрибут успешно удален")
|
||||||
@@ -356,6 +356,7 @@ class CardsService(BaseService):
|
|||||||
card.comment = request.data.comment
|
card.comment = request.data.comment
|
||||||
card.is_deleted = request.data.is_deleted
|
card.is_deleted = request.data.is_deleted
|
||||||
card.is_completed = request.data.is_completed
|
card.is_completed = request.data.is_completed
|
||||||
|
card.client_id = request.data.client_id
|
||||||
|
|
||||||
if card.board_id != request.data.board_id or card.current_status_id != request.data.status_id:
|
if card.board_id != request.data.board_id or card.current_status_id != request.data.status_id:
|
||||||
if card.group:
|
if card.group:
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy import select, update, func, delete
|
from sqlalchemy import select, update, func, delete
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from models import Project, Board
|
from card_attributes import CardAttributesCommandHandler
|
||||||
|
from models import Project, Board, Module, Attribute
|
||||||
from schemas.project import *
|
from schemas.project import *
|
||||||
from services.base import BaseService
|
from services.base import BaseService
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ class ProjectService(BaseService):
|
|||||||
selectinload(Project.attributes),
|
selectinload(Project.attributes),
|
||||||
selectinload(Project.modules),
|
selectinload(Project.modules),
|
||||||
)
|
)
|
||||||
|
.order_by(Project.id)
|
||||||
)
|
)
|
||||||
project_data = (await self.session.execute(stmt)).all()
|
project_data = (await self.session.execute(stmt)).all()
|
||||||
|
|
||||||
@@ -81,3 +84,37 @@ class ProjectService(BaseService):
|
|||||||
await self.session.execute(stmt)
|
await self.session.execute(stmt)
|
||||||
await self.session.commit()
|
await self.session.commit()
|
||||||
return DeleteProjectResponse(ok=True, message="Проект успешно удален")
|
return DeleteProjectResponse(ok=True, message="Проект успешно удален")
|
||||||
|
|
||||||
|
async def get_all_modules(self) -> GetAllModulesResponse:
|
||||||
|
stmt = (
|
||||||
|
select(Module)
|
||||||
|
.where(Module.is_deleted == False)
|
||||||
|
)
|
||||||
|
modules = await self.session.scalars(stmt)
|
||||||
|
return GetAllModulesResponse(modules=modules.all())
|
||||||
|
|
||||||
|
async def update_project_modules(self, request: UpdateModulesRequest) -> UpdateModulesResponse:
|
||||||
|
project: Optional[Project] = await self.session.get(Project, request.project_id)
|
||||||
|
if not project:
|
||||||
|
return UpdateModulesResponse(ok=False, message=f"Проект с ID {request.project_id} не найден")
|
||||||
|
|
||||||
|
modules_stmt = (
|
||||||
|
select(Module)
|
||||||
|
.where(Module.id.in_(request.module_ids))
|
||||||
|
)
|
||||||
|
modules = (await self.session.scalars(modules_stmt)).all()
|
||||||
|
|
||||||
|
project.modules = modules
|
||||||
|
await self.session.commit()
|
||||||
|
|
||||||
|
return UpdateModulesResponse(ok=True, message="Модули успешно обновлены")
|
||||||
|
|
||||||
|
async def update_project_attributes(self, request: UpdateAttributesRequest) -> UpdateAttributesResponse:
|
||||||
|
project: Optional[Project] = await self.session.get(Project, request.project_id)
|
||||||
|
if not project:
|
||||||
|
return UpdateAttributesResponse(ok=False, message=f"Проект с ID {request.project_id} не найден")
|
||||||
|
|
||||||
|
card_attrs_handler = CardAttributesCommandHandler(self.session)
|
||||||
|
await card_attrs_handler.set_project_attributes(project, request.attribute_ids)
|
||||||
|
|
||||||
|
return UpdateAttributesResponse(ok=True, message="Атрибуты успешно обновлены")
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ class ResiduesService(BaseService):
|
|||||||
pallet = ResidualPallet(client_id=client_id, created_at=datetime.now())
|
pallet = ResidualPallet(client_id=client_id, created_at=datetime.now())
|
||||||
self.session.add(pallet)
|
self.session.add(pallet)
|
||||||
await self.session.flush()
|
await self.session.flush()
|
||||||
await self._load_receipt_boxes(receipt_pallet.boxes, pallet.id)
|
await self._load_receipt_boxes(receipt_pallet.boxes, None, pallet.id)
|
||||||
await self._load_receipt_products(receipt_pallet.products, pallet_id=pallet.id)
|
await self._load_receipt_products(receipt_pallet.products, pallet_id=pallet.id)
|
||||||
|
|
||||||
async def _load_receipt_products(
|
async def _load_receipt_products(
|
||||||
|
|||||||
Reference in New Issue
Block a user