diff --git a/models/attribute.py b/models/attribute.py index 77dddbe..e32932f 100644 --- a/models/attribute.py +++ b/models/attribute.py @@ -72,9 +72,14 @@ class Attribute(BaseModel, IdMixin, SoftDeleteMixin): ) -class AttributeValue(BaseModel, IdMixin): +class AttributeValue(BaseModel): __tablename__ = "attribute_values" + id: Mapped[int] = mapped_column( + primary_key=True, + autoincrement=True, + ) + value: Mapped[Optional[dict[str, any]]] = mapped_column(JSONB) deal_id: Mapped[int] = mapped_column( diff --git a/repositories/attribute.py b/repositories/attribute.py index a3386bc..82c84b3 100644 --- a/repositories/attribute.py +++ b/repositories/attribute.py @@ -1,8 +1,15 @@ +from sqlalchemy import and_ from sqlalchemy.orm import joinedload -from models import Attribute, AttributeLabel, AttributeType +from models import ( + Attribute, + AttributeLabel, + AttributeType, + AttributeValue, + module_attribute, +) from repositories.mixins import * -from schemas.attribute import CreateAttributeSchema, UpdateAttributeSchema +from schemas.attribute import CreateAttributeSchema, UpdateAttributeSchema, UpdateDealModuleAttributeSchema from utils.exceptions import ForbiddenException @@ -71,3 +78,75 @@ class AttributeRepository( stmt = select(AttributeType).where(AttributeType.is_deleted.is_(False)) result = await self.session.execute(stmt) return list(result.scalars().all()) + + async def get_deal_module_attributes( + self, deal_id: int, module_id: int + ) -> list[tuple[Attribute, AttributeValue, AttributeLabel]]: + stmt = ( + select(Attribute, AttributeValue, AttributeLabel) + .outerjoin( + AttributeValue, + and_( + AttributeValue.attribute_id == Attribute.id, + AttributeValue.module_id == module_id, + AttributeValue.deal_id == deal_id, + ), + ) + .outerjoin( + AttributeLabel, + and_( + AttributeLabel.attribute_id == Attribute.id, + AttributeLabel.module_id == module_id, + ), + ) + .join( + module_attribute, + and_( + module_attribute.c.attribute_id == Attribute.id, + module_attribute.c.module_id == module_id, + ), + ) + ) + result = await self.session.execute(stmt) + return list(result.all()) + + async def _get_deal_attribute_values( + self, deal_id: int, module_id: int + ) -> list[AttributeValue]: + stmt = ( + select(AttributeValue) + .join(Attribute, AttributeValue.attribute_id == Attribute.id) + .where( + AttributeValue.deal_id == deal_id, + AttributeValue.module_id == module_id, + Attribute.is_deleted.is_(False), + ) + ) + result = await self.session.execute(stmt) + return list(result.scalars().all()) + + async def update_or_create_deal_attribute_values( + self, + deal_id: int, + module_id: int, + attributes: list[UpdateDealModuleAttributeSchema], + ): + old_deal_attribute_values: list[AttributeValue] = await self._get_deal_attribute_values(deal_id, module_id) + dict_old_attrs: dict[int, AttributeValue] = {obj.attribute_id: obj for obj in old_deal_attribute_values} + + for attribute in attributes: + if attribute.attribute_id in dict_old_attrs: + # update + attribute_value = dict_old_attrs[attribute.attribute_id] + attribute_value.value = attribute.value + else: + # create + attribute_value = AttributeValue( + attribute_id=attribute.attribute_id, + deal_id=deal_id, + module_id=module_id, + value=attribute.value, + ) + self.session.add(attribute_value) + + await self.session.commit() diff --git a/repositories/module.py b/repositories/module.py index 09e0369..3079653 100644 --- a/repositories/module.py +++ b/repositories/module.py @@ -46,7 +46,7 @@ class ModuleRepository( Module.is_deleted.is_(False), or_(Attribute.id.is_(None), Attribute.is_deleted.is_(False)), ) - .order_by(Attribute.id) + .order_by(Module.id, Attribute.id) ) async def get_with_attributes_as_tuples( diff --git a/routers/crm/v1/attribute.py b/routers/crm/v1/attribute.py index 01b8f5c..9600637 100644 --- a/routers/crm/v1/attribute.py +++ b/routers/crm/v1/attribute.py @@ -76,3 +76,34 @@ async def get_attribute_types( session: SessionDependency, ): return await AttributeService(session).get_attribute_types() + + +@router.get( + "/deal/{dealId}/module/{moduleId}", + response_model=GetDealModuleAttributesResponse, + operation_id="get_deal_module_attributes", +) +async def get_deal_module_attributes( + session: SessionDependency, + deal_id: int = Path(alias="dealId"), + module_id: int = Path(alias="moduleId"), +): + return await AttributeService(session).get_deal_module_attributes( + deal_id, module_id + ) + + +@router.post( + "/deal/{dealId}/module/{moduleId}", + response_model=UpdateDealModuleAttributesResponse, + operation_id="update_deal_module_attributes", +) +async def update_deal_module_attributes( + session: SessionDependency, + request: UpdateDealModuleAttributesRequest, + deal_id: int = Path(alias="dealId"), + module_id: int = Path(alias="moduleId"), +): + return await AttributeService(session).update_deal_module_attributes( + deal_id, module_id, request + ) diff --git a/schemas/attribute.py b/schemas/attribute.py index 434c340..62f1031 100644 --- a/schemas/attribute.py +++ b/schemas/attribute.py @@ -44,6 +44,25 @@ class ModuleAttributeSchema(AttributeSchema): original_label: str +class DealModuleAttributeSchema(BaseSchema): + attribute_id: int + label: str + original_label: str + value: Optional[dict[str, Any]] + type: AttributeTypeSchema + default_value: dict[str, Any] + description: str + is_applicable_to_group: bool + is_shown_on_dashboard: bool + is_highlight_if_expired: bool + is_nullable: bool + + +class UpdateDealModuleAttributeSchema(BaseSchema): + attribute_id: int + value: Optional[dict[str, Any]] + + # endregion # region Request @@ -63,6 +82,9 @@ class UpdateAttributeLabelRequest(BaseSchema): label: str +class UpdateDealModuleAttributesRequest(BaseSchema): + attributes: list[UpdateDealModuleAttributeSchema] + # endregion # region Response @@ -92,4 +114,11 @@ class GetAllAttributeTypesResponse(BaseSchema): items: list[AttributeTypeSchema] +class GetDealModuleAttributesResponse(BaseSchema): + attributes: list[DealModuleAttributeSchema] + + +class UpdateDealModuleAttributesResponse(BaseResponse): + pass + # endregion diff --git a/services/attribute.py b/services/attribute.py index 05679b0..253d1f8 100644 --- a/services/attribute.py +++ b/services/attribute.py @@ -1,4 +1,4 @@ -from models import Attribute +from models import Attribute, AttributeValue, AttributeLabel from repositories import AttributeRepository from schemas.attribute import * from services.mixins import * @@ -29,3 +29,37 @@ class AttributeService( return GetAllAttributeTypesResponse( items=[AttributeTypeSchema.model_validate(t) for t in types] ) + + async def get_deal_module_attributes( + self, deal_id: int, module_id: int + ) -> GetDealModuleAttributesResponse: + deal_attributes: list[ + tuple[Attribute, AttributeValue, AttributeLabel] + ] = await self.repository.get_deal_module_attributes(deal_id, module_id) + + attributes = [] + for attr, attr_value, attr_label in deal_attributes: + attribute = DealModuleAttributeSchema( + attribute_id=attr.id, + label=attr_label.label if attr_label else attr.label, + original_label=attr.label, + value=attr_value.value if attr_value else attr.default_value, + type=AttributeTypeSchema.model_validate(attr.type), + default_value=attr.default_value, + description=attr.description, + is_applicable_to_group=attr.is_applicable_to_group, + is_shown_on_dashboard=attr.is_shown_on_dashboard, + is_highlight_if_expired=attr.is_highlight_if_expired, + is_nullable=attr.is_nullable, + ) + attributes.append(attribute) + + return GetDealModuleAttributesResponse(attributes=attributes) + + async def update_deal_module_attributes( + self, deal_id: int, module_id: int, request: UpdateDealModuleAttributesRequest + ) -> UpdateDealModuleAttributesResponse: + await self.repository.update_or_create_deal_attribute_values( + deal_id, module_id, request.attributes + ) + return UpdateDealModuleAttributesResponse(message="Успешно сохранено")