367 lines
17 KiB
Python
367 lines
17 KiB
Python
import models.secondary
|
||
from typing import Union
|
||
import models.deal
|
||
from fastapi import HTTPException
|
||
from sqlalchemy import select, func
|
||
from sqlalchemy.orm import joinedload, selectinload
|
||
|
||
from models import User, Service, Client
|
||
from models.deal import *
|
||
from schemas.client import ClientDetailsSchema
|
||
from schemas.deal import *
|
||
from services.base import BaseService
|
||
from services.client import ClientService
|
||
|
||
|
||
class DealService(BaseService):
|
||
|
||
# region Deal
|
||
async def _get_deal_by_id(self, deal_id) -> Union[Deal, None]:
|
||
return await self.session.get(Deal, deal_id)
|
||
|
||
async def change_status(self, deal: Deal,
|
||
status: DealStatus,
|
||
user: User,
|
||
deadline: datetime.datetime = None) -> DealStatusHistory:
|
||
deadline = deadline
|
||
status_change = DealStatusHistory(
|
||
deal_id=deal.id,
|
||
user_id=user.id,
|
||
changed_at=datetime.datetime.now(),
|
||
from_status=deal.current_status,
|
||
to_status=status,
|
||
next_status_deadline=deadline
|
||
)
|
||
self.session.add(status_change)
|
||
await self.session.flush()
|
||
return status_change
|
||
|
||
async def create(self, request: DealCreateRequest, user: User) -> DealCreateResponse:
|
||
deal = Deal(
|
||
name=request.name,
|
||
created_at=datetime.datetime.now(),
|
||
current_status=DealStatus.CREATED
|
||
)
|
||
self.session.add(deal)
|
||
await self.session.flush()
|
||
|
||
# Append status history
|
||
await self.change_status(deal, DealStatus.AWAITING_ACCEPTANCE, user)
|
||
|
||
await self.session.commit()
|
||
return DealCreateResponse(ok=True)
|
||
|
||
async def quick_create(self, request: DealQuickCreateRequest, user: User) -> DealQuickCreateResponse:
|
||
client_service = ClientService(self.session)
|
||
client = await client_service.get_by_name(request.client_name)
|
||
if not client:
|
||
client = await client_service.create_client_raw(
|
||
user,
|
||
request.client_name,
|
||
ClientDetailsSchema(address=request.client_address))
|
||
await client_service.update_details(user, client, ClientDetailsSchema(address=request.client_address))
|
||
deal = Deal(
|
||
name=request.name,
|
||
created_at=datetime.datetime.now(),
|
||
client_id=client.id,
|
||
current_status=DealStatus.CREATED
|
||
)
|
||
self.session.add(deal)
|
||
await self.session.flush()
|
||
await self.change_status(deal, DealStatus.AWAITING_ACCEPTANCE, user, deadline=request.acceptance_date)
|
||
await self.session.commit()
|
||
return DealQuickCreateResponse(deal_id=deal.id)
|
||
|
||
async def change_status_manual(self, request: DealChangeStatusRequest, user: User) -> DealChangeStatusResponse:
|
||
# Changing current status
|
||
deal = await self._get_deal_by_id(request.deal_id)
|
||
if not deal:
|
||
return DealChangeStatusResponse(ok=False)
|
||
await self.change_status(deal, DealStatus(request.new_status), user)
|
||
await self.session.commit()
|
||
return DealChangeStatusResponse(ok=True)
|
||
|
||
async def get_summary(self) -> DealSummaryResponse:
|
||
service_subquery = (
|
||
select(
|
||
models.secondary.DealService.deal_id,
|
||
func.sum(models.secondary.DealService.quantity * Service.price).label('total_price')
|
||
)
|
||
.join(Service)
|
||
.group_by(models.secondary.DealService.deal_id)
|
||
.subquery()
|
||
)
|
||
q = (select(
|
||
Deal,
|
||
func.coalesce(service_subquery.c.total_price, 0)
|
||
)
|
||
.options(selectinload(Deal.status_history),
|
||
joinedload(Deal.client))
|
||
.outerjoin(service_subquery, Deal.id == service_subquery.c.deal_id)
|
||
.where(Deal.is_deleted == False,
|
||
Deal.is_completed == False))
|
||
deals_query = await self.session.execute(q)
|
||
summaries = []
|
||
for deal, total_price in deals_query.all():
|
||
deal: Deal
|
||
last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at)
|
||
summaries.append(
|
||
DealSummary(
|
||
id=deal.id,
|
||
client_name=deal.client.name,
|
||
name=deal.name,
|
||
changed_at=last_status.changed_at,
|
||
status=last_status.to_status,
|
||
total_price=total_price
|
||
)
|
||
)
|
||
return DealSummaryResponse(summaries=summaries)
|
||
|
||
async def get_all(self) -> DealGetAllResponse:
|
||
deals_query = await self.session.scalars(select(Deal).options(joinedload(Deal.client)))
|
||
deals = deals_query.all()
|
||
result = []
|
||
for deal in deals:
|
||
result.append(DealSchema.model_validate(deal))
|
||
return DealGetAllResponse(deals=result)
|
||
|
||
async def get_by_id(self, deal_id: int) -> DealSchema:
|
||
deal = await self.session.scalar(
|
||
select(Deal)
|
||
.options(
|
||
joinedload(Deal.client).joinedload(Client.details),
|
||
selectinload(Deal.services)
|
||
.joinedload(models.secondary.DealService.service)
|
||
.joinedload(Service.category),
|
||
selectinload(Deal.products)
|
||
.joinedload(models.secondary.DealProduct.product)
|
||
.joinedload(models.Product.client),
|
||
selectinload(Deal.products)
|
||
.joinedload(models.secondary.DealProduct.product)
|
||
.joinedload(models.Product.barcodes),
|
||
selectinload(Deal.status_history)
|
||
.joinedload(DealStatusHistory.user),
|
||
selectinload(Deal.status_history)
|
||
.noload(DealStatusHistory.deal)
|
||
)
|
||
.where(Deal.id == deal_id)
|
||
)
|
||
if not deal:
|
||
raise HTTPException(status_code=404, detail="Сделка не найдена")
|
||
return DealSchema.model_validate(deal)
|
||
|
||
async def update_general_info(self, request: DealUpdateGeneralInfoRequest) -> DealUpdateGeneralInfoResponse:
|
||
try:
|
||
deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id))
|
||
if not deal:
|
||
raise HTTPException(status_code=404, detail="Сделка не найдена")
|
||
deal.name = request.data.name
|
||
deal.is_deleted = request.data.is_deleted
|
||
deal.is_completed = request.data.is_completed
|
||
await self.session.commit()
|
||
return DealUpdateGeneralInfoResponse(ok=True, message='Данные о сделке успешно обновлены')
|
||
except Exception as e:
|
||
await self.session.rollback()
|
||
return DealUpdateGeneralInfoResponse(ok=False, message=str(e))
|
||
|
||
# endregion
|
||
|
||
# region Deal services
|
||
async def add_services(self, request: DealAddServicesRequest):
|
||
# TODO refactor
|
||
deal: Deal = await self.session.scalar(
|
||
select(Deal)
|
||
.options(selectinload(Deal.services))
|
||
.where(Deal.id == request.deal_id)
|
||
)
|
||
if not deal:
|
||
raise HTTPException(status_code=404, detail="Deal is not found")
|
||
|
||
services_ids = [service.id for service in request.services]
|
||
existing_service_ids = {service.service_id for service in deal.services}
|
||
request_services_dict = {service.id: service.quantity for service in request.services}
|
||
|
||
services_query = await self.session.scalars(select(Service).where(Service.id.in_(services_ids)))
|
||
services = services_query.all()
|
||
if len(services) != len(services_ids):
|
||
raise HTTPException(status_code=404, detail="Some of services is not found")
|
||
|
||
# Adding quantity
|
||
for deal_service in deal.services:
|
||
deal_service: models.secondary.DealService
|
||
if deal_service.service_id not in services_ids:
|
||
continue
|
||
deal_service.quantity += request_services_dict[deal_service.service_id]
|
||
|
||
# Adding new services
|
||
for service in services:
|
||
if service.id in existing_service_ids:
|
||
continue
|
||
quantity = request_services_dict[service.id]
|
||
deal.services.append(
|
||
models.secondary.DealService(
|
||
service_id=service.id,
|
||
deal_id=deal.id,
|
||
quantity=quantity
|
||
)
|
||
)
|
||
await self.session.commit()
|
||
return DealAddServicesResponse(ok=True, message='Услуги успешно добавлены')
|
||
|
||
async def update_service_quantity(self,
|
||
request: DealUpdateServiceQuantityRequest) -> DealUpdateServiceQuantityResponse:
|
||
try:
|
||
deal_service = await self.session.scalar(
|
||
select(models.secondary.DealService)
|
||
.where(models.secondary.DealService.deal_id == request.deal_id,
|
||
models.secondary.DealService.service_id == request.service_id)
|
||
)
|
||
if not deal_service:
|
||
raise HTTPException(status_code=404, detail="Сделка не найдена")
|
||
deal_service.quantity = request.quantity
|
||
await self.session.commit()
|
||
return DealUpdateServiceQuantityResponse(ok=True, message='Количество успешно обновлено')
|
||
except Exception as e:
|
||
await self.session.rollback()
|
||
return DealUpdateServiceQuantityResponse(ok=False, message=str(e))
|
||
|
||
async def add_service(self, request: DealAddServiceRequest) -> DealAddServiceResponse:
|
||
try:
|
||
deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id))
|
||
if not deal:
|
||
raise HTTPException(status_code=404, detail="Сделка не найдена")
|
||
service = await self.session.scalar(select(Service).where(Service.id == request.service_id))
|
||
if not service:
|
||
raise HTTPException(status_code=404, detail="Услуга не найдена")
|
||
# Preventing duplicates
|
||
deal_service = await self.session.scalar(
|
||
select(models.secondary.DealService)
|
||
.where(models.secondary.DealService.deal_id == request.deal_id,
|
||
models.secondary.DealService.service_id == request.service_id)
|
||
)
|
||
if deal_service:
|
||
raise HTTPException(status_code=400, detail="Услуга уже добавлена")
|
||
deal_service = models.secondary.DealService(
|
||
deal_id=request.deal_id,
|
||
service_id=request.service_id,
|
||
quantity=request.quantity
|
||
)
|
||
self.session.add(deal_service)
|
||
await self.session.commit()
|
||
return DealAddServiceResponse(ok=True, message='Услуга успешно добавлена')
|
||
except Exception as e:
|
||
await self.session.rollback()
|
||
return DealAddServiceResponse(ok=False, message=str(e))
|
||
|
||
async def delete_service(self, request: DealDeleteServiceRequest) -> DealDeleteServiceResponse:
|
||
try:
|
||
deal_service = await self.session.scalar(
|
||
select(models.secondary.DealService)
|
||
.where(models.secondary.DealService.deal_id == request.deal_id,
|
||
models.secondary.DealService.service_id == request.service_id)
|
||
)
|
||
if not deal_service:
|
||
raise HTTPException(status_code=404, detail="Сделка не найдена")
|
||
await self.session.delete(deal_service)
|
||
await self.session.commit()
|
||
return DealDeleteServiceResponse(ok=True, message='Услуга успешно удалена')
|
||
except Exception as e:
|
||
await self.session.rollback()
|
||
return DealDeleteServiceResponse(ok=False, message=str(e))
|
||
|
||
async def delete_services(self, request: DealDeleteServicesRequest) -> DealDeleteServicesResponse:
|
||
try:
|
||
deal_services = await self.session.scalars(
|
||
select(models.secondary.DealService)
|
||
.where(models.secondary.DealService.deal_id == request.deal_id,
|
||
models.secondary.DealService.service_id.in_(request.service_ids))
|
||
)
|
||
for deal_service in deal_services:
|
||
await self.session.delete(deal_service)
|
||
await self.session.commit()
|
||
return DealDeleteServicesResponse(ok=True, message='Услуги успешно удалены')
|
||
except Exception as e:
|
||
await self.session.rollback()
|
||
return DealDeleteServicesResponse(ok=False, message=str(e))
|
||
|
||
# endregion
|
||
|
||
# region Deal products
|
||
async def update_product_quantity(self,
|
||
request: DealUpdateProductQuantityRequest) -> DealUpdateProductQuantityResponse:
|
||
try:
|
||
# check if there is no deal or no product with different exceptions
|
||
deal_product = await self.session.scalar(
|
||
select(models.secondary.DealProduct)
|
||
.where(models.secondary.DealProduct.deal_id == request.deal_id,
|
||
models.secondary.DealProduct.product_id == request.product_id)
|
||
)
|
||
if not deal_product:
|
||
raise HTTPException(status_code=404, detail="Сделка или товар не найдена")
|
||
deal_product.quantity = request.quantity
|
||
await self.session.commit()
|
||
return DealUpdateProductQuantityResponse(ok=True, message='Количество успешно обновлено')
|
||
except Exception as e:
|
||
await self.session.rollback()
|
||
return DealUpdateProductQuantityResponse(ok=False, message=str(e))
|
||
|
||
async def add_product(self, request: DealAddProductRequest) -> DealAddProductResponse:
|
||
try:
|
||
deal = await self.session.scalar(select(Deal).where(Deal.id == request.deal_id))
|
||
if not deal:
|
||
raise HTTPException(status_code=404, detail="Сделка не найдена")
|
||
product = await self.session.scalar(select(models.Product).where(models.Product.id == request.product_id))
|
||
if not product:
|
||
raise HTTPException(status_code=404, detail="Товар не найден")
|
||
# Preventing duplicates
|
||
deal_product = await self.session.scalar(
|
||
select(models.secondary.DealProduct)
|
||
.where(models.secondary.DealProduct.deal_id == request.deal_id,
|
||
models.secondary.DealProduct.product_id == request.product_id)
|
||
)
|
||
if deal_product:
|
||
raise HTTPException(status_code=400, detail="Товар уже добавлен")
|
||
deal_product = models.secondary.DealProduct(
|
||
deal_id=request.deal_id,
|
||
product_id=request.product_id,
|
||
quantity=request.quantity
|
||
)
|
||
self.session.add(deal_product)
|
||
await self.session.commit()
|
||
return DealAddProductResponse(ok=True, message='Товар успешно добавлен')
|
||
except Exception as e:
|
||
await self.session.rollback()
|
||
return DealAddProductResponse(ok=False, message=str(e))
|
||
|
||
async def delete_product(self, request: DealDeleteProductRequest) -> DealDeleteProductResponse:
|
||
try:
|
||
deal_product = await self.session.scalar(
|
||
select(models.secondary.DealProduct)
|
||
.where(models.secondary.DealProduct.deal_id == request.deal_id,
|
||
models.secondary.DealProduct.product_id == request.product_id)
|
||
)
|
||
if not deal_product:
|
||
raise HTTPException(status_code=404, detail="Сделка не найдена")
|
||
await self.session.delete(deal_product)
|
||
await self.session.commit()
|
||
return DealDeleteProductResponse(ok=True, message='Товар успешно удален')
|
||
except Exception as e:
|
||
await self.session.rollback()
|
||
return DealDeleteProductResponse(ok=False, message=str(e))
|
||
|
||
async def delete_products(self, request: DealDeleteProductsRequest) -> DealDeleteProductsResponse:
|
||
try:
|
||
deal_products = await self.session.scalars(
|
||
select(models.secondary.DealProduct)
|
||
.where(models.secondary.DealProduct.deal_id == request.deal_id,
|
||
models.secondary.DealProduct.product_id.in_(request.product_ids))
|
||
)
|
||
for deal_product in deal_products:
|
||
await self.session.delete(deal_product)
|
||
await self.session.commit()
|
||
return DealDeleteProductsResponse(ok=True, message='Товары успешно удалены')
|
||
except Exception as e:
|
||
await self.session.rollback()
|
||
return DealDeleteProductsResponse(ok=False, message=str(e))
|
||
# endregion
|