feat: projects and boards
This commit is contained in:
244
services/deal.py
244
services/deal.py
@@ -1,13 +1,14 @@
|
||||
from collections import defaultdict
|
||||
|
||||
import lexorank
|
||||
from attr import dataclass
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import select, func, update, delete, insert, and_
|
||||
from sqlalchemy.orm import joinedload, selectinload
|
||||
from starlette import status
|
||||
|
||||
from models import User, Service, Client, DealProductService, deal_relations, DealStatusHistory, Product, DealProduct
|
||||
import models.deal
|
||||
import models.secondary
|
||||
from models import User, Service, Client, DealProductService, deal_relations, GroupBillRequest
|
||||
from models.deal import *
|
||||
from models.deal_group import DealGroup
|
||||
from models.shipping import ShippingProduct
|
||||
@@ -16,9 +17,9 @@ from schemas.deal import *
|
||||
from services.auth import AuthService
|
||||
from services.base import BaseService
|
||||
from services.client import ClientService
|
||||
from services.deal_group import DealGroupService
|
||||
from services.service import ServiceService
|
||||
from services.shipping_warehouse import ShippingWarehouseService
|
||||
from services import deal_group
|
||||
|
||||
|
||||
class DealService(BaseService):
|
||||
@@ -36,9 +37,13 @@ class DealService(BaseService):
|
||||
async def _get_deal_by_id(self, deal_id) -> Union[Deal, None]:
|
||||
return await self.session.get(Deal, deal_id)
|
||||
|
||||
async def _get_rank_for_deal(self, deal_status: DealStatus) -> str:
|
||||
async def _get_rank_for_deal(self, status_id: int) -> str:
|
||||
deal_query = await self.session.execute(
|
||||
select(Deal).where(Deal.current_status == deal_status).order_by(Deal.lexorank.desc()).limit(1))
|
||||
select(Deal)
|
||||
.where(Deal.current_status_id == status_id)
|
||||
.order_by(Deal.lexorank.desc())
|
||||
.limit(1)
|
||||
)
|
||||
deal = deal_query.scalar_one_or_none()
|
||||
if not deal:
|
||||
prev = lexorank.middle(lexorank.Bucket.BUCEKT_0)
|
||||
@@ -46,59 +51,46 @@ class DealService(BaseService):
|
||||
return str(lexorank.parse(deal.lexorank).next())
|
||||
|
||||
async def change_status(self, deal: Deal,
|
||||
status: DealStatus,
|
||||
user: User,
|
||||
status_id: int,
|
||||
user: models.User,
|
||||
deadline: datetime.datetime = None,
|
||||
rank=None,
|
||||
comment: str = ''):
|
||||
if not deal.current_status == status:
|
||||
if not deal.current_status_id == status_id:
|
||||
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,
|
||||
from_status_id=deal.current_status_id,
|
||||
to_status_id=status_id,
|
||||
next_status_deadline=deadline,
|
||||
comment=comment
|
||||
)
|
||||
self.session.add(status_change)
|
||||
deal.current_status = status
|
||||
deal.current_status_id = status_id
|
||||
if not rank:
|
||||
rank = await self._get_rank_for_deal(status)
|
||||
rank = await self._get_rank_for_deal(status_id)
|
||||
if rank:
|
||||
deal.lexorank = rank
|
||||
await self.session.flush()
|
||||
|
||||
async def create(self, request: DealCreateRequest, user: User) -> DealCreateResponse:
|
||||
rank = self._get_rank_for_deal(DealStatus.CREATED)
|
||||
deal = Deal(
|
||||
name=request.name,
|
||||
created_at=datetime.datetime.now(),
|
||||
current_status=DealStatus.CREATED,
|
||||
lexorank=rank
|
||||
)
|
||||
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 delete(self, request: DealDeleteRequest) -> DealDeleteResponse:
|
||||
deal = await self._get_deal_by_id(request.deal_id)
|
||||
if not deal:
|
||||
return DealDeleteResponse(ok=False, message="Сделка не найдена")
|
||||
if deal.group:
|
||||
await DealGroupService(self.session).delete_group(deal.group.id)
|
||||
await deal_group.DealGroupService(self.session).delete_group(deal.group.id)
|
||||
else:
|
||||
deal.is_deleted = True
|
||||
await self.session.commit()
|
||||
return DealDeleteResponse(ok=True, message="Сделка успешно удалена")
|
||||
|
||||
async def quick_create(self, request: DealQuickCreateRequest, user: User) -> DealQuickCreateResponse:
|
||||
async def quick_create(self, request: DealQuickCreateRequest, user: models.User) -> DealQuickCreateResponse:
|
||||
deal_status = await self.session.get(DealStatus, request.status_id)
|
||||
if not deal_status:
|
||||
raise HTTPException(status_code=400, detail="Указан некорректный статус")
|
||||
|
||||
client_service = ClientService(self.session)
|
||||
client = await client_service.get_by_name(request.client_name)
|
||||
|
||||
@@ -108,28 +100,25 @@ class DealService(BaseService):
|
||||
request.client_name,
|
||||
ClientDetailsSchema()
|
||||
)
|
||||
|
||||
shipping_warehouse_service = ShippingWarehouseService(self.session)
|
||||
shipping_warehouse = await shipping_warehouse_service.get_by_name(name=request.shipping_warehouse)
|
||||
if not shipping_warehouse:
|
||||
shipping_warehouse = await shipping_warehouse_service.create_by_name(name=request.shipping_warehouse)
|
||||
|
||||
rank = await self._get_rank_for_deal(DealStatus.CREATED)
|
||||
rank = await self._get_rank_for_deal(request.status_id)
|
||||
deal = Deal(
|
||||
name=request.name,
|
||||
created_at=datetime.datetime.now(),
|
||||
client_id=client.id,
|
||||
current_status=DealStatus.CREATED,
|
||||
current_status_id=request.status_id,
|
||||
board_id=deal_status.board_id,
|
||||
lexorank=rank,
|
||||
shipping_warehouse_id=shipping_warehouse.id,
|
||||
base_marketplace_key=request.base_marketplace.key
|
||||
)
|
||||
self.session.add(deal)
|
||||
await self.session.flush()
|
||||
await self.change_status(deal,
|
||||
DealStatus.AWAITING_ACCEPTANCE,
|
||||
user,
|
||||
deadline=request.acceptance_date,
|
||||
comment=request.comment)
|
||||
# add category if specified
|
||||
if request.category:
|
||||
deal_category = DealPriceCategory(
|
||||
@@ -141,12 +130,12 @@ class DealService(BaseService):
|
||||
await self.session.commit()
|
||||
return DealQuickCreateResponse(deal_id=deal.id)
|
||||
|
||||
async def change_status_manual(self, request: DealChangeStatusRequest, user: User) -> DealChangeStatusResponse:
|
||||
async def change_status_manual(self, request: DealChangeStatusRequest, user: models.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.change_status(deal, request.new_status, user)
|
||||
await self.session.commit()
|
||||
return DealChangeStatusResponse(ok=True)
|
||||
|
||||
@@ -199,7 +188,7 @@ class DealService(BaseService):
|
||||
Deal,
|
||||
func.coalesce(price_subquery.c.total_price, 0),
|
||||
func.row_number().over(
|
||||
partition_by=Deal.current_status,
|
||||
partition_by=Deal.current_status_id,
|
||||
order_by=Deal.lexorank
|
||||
).label('rank'),
|
||||
func.coalesce(products_quantity_subquery.c.total_quantity, 0)
|
||||
@@ -208,7 +197,9 @@ class DealService(BaseService):
|
||||
selectinload(Deal.status_history),
|
||||
joinedload(Deal.client),
|
||||
joinedload(Deal.shipping_warehouse),
|
||||
joinedload(Deal.bill_request)
|
||||
joinedload(Deal.bill_request),
|
||||
joinedload(Deal.status),
|
||||
joinedload(Deal.board),
|
||||
)
|
||||
.outerjoin(
|
||||
price_subquery, Deal.id == price_subquery.c.deal_id,
|
||||
@@ -231,8 +222,6 @@ class DealService(BaseService):
|
||||
summaries = []
|
||||
for deal, total_price, rank, products_count in deals_query.all():
|
||||
deal: Deal
|
||||
last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at)
|
||||
deadline = last_status.next_status_deadline
|
||||
base_marketplace = None
|
||||
if deal.base_marketplace:
|
||||
base_marketplace = BaseMarketplaceSchema.model_validate(deal.base_marketplace)
|
||||
@@ -242,9 +231,8 @@ class DealService(BaseService):
|
||||
id=deal.id,
|
||||
client_name=deal.client.name,
|
||||
name=deal.name,
|
||||
changed_at=last_status.changed_at,
|
||||
deadline=deadline,
|
||||
status=last_status.to_status,
|
||||
status=deal.status,
|
||||
board=deal.board,
|
||||
total_price=total_price,
|
||||
rank=rank,
|
||||
base_marketplace=base_marketplace,
|
||||
@@ -308,6 +296,9 @@ class DealService(BaseService):
|
||||
deal = await self.session.scalar(
|
||||
select(Deal)
|
||||
.options(
|
||||
joinedload(Deal.status),
|
||||
joinedload(Deal.board)
|
||||
.joinedload(Board.project),
|
||||
joinedload(Deal.shipping_warehouse),
|
||||
joinedload(Deal.client)
|
||||
.joinedload(Client.details),
|
||||
@@ -350,7 +341,7 @@ class DealService(BaseService):
|
||||
raise HTTPException(status_code=404, detail="Сделка не найдена")
|
||||
return DealSchema.model_validate(deal)
|
||||
|
||||
async def update_general_info(self, request: DealUpdateGeneralInfoRequest) -> DealUpdateGeneralInfoResponse:
|
||||
async def update_general_info(self, request: DealUpdateGeneralInfoRequest, user: User) -> DealUpdateGeneralInfoResponse:
|
||||
try:
|
||||
deal: Deal = await self.session.scalar(
|
||||
select(Deal)
|
||||
@@ -369,6 +360,15 @@ class DealService(BaseService):
|
||||
deal.delivery_date = request.data.delivery_date
|
||||
deal.receiving_slot_date = request.data.receiving_slot_date
|
||||
|
||||
if deal.board_id != request.data.board.id or deal.current_status_id != request.data.status.id:
|
||||
if deal.group:
|
||||
for deal in deal.group.deals:
|
||||
deal.board_id = request.data.board.id
|
||||
await self.change_status(deal, request.data.status.id, user)
|
||||
else:
|
||||
deal.board_id = request.data.board.id
|
||||
await self.change_status(deal, request.data.status.id, user)
|
||||
|
||||
if deal.group:
|
||||
for deal in deal.group.deals:
|
||||
deal.is_accounted = request.data.is_accounted
|
||||
@@ -402,7 +402,7 @@ class DealService(BaseService):
|
||||
stmt = (
|
||||
select(Deal)
|
||||
.where(
|
||||
Deal.current_status == request.status,
|
||||
Deal.current_status_id == request.status_id,
|
||||
Deal.id != request.deal_id,
|
||||
Deal.is_deleted == False,
|
||||
Deal.is_completed == False
|
||||
@@ -433,7 +433,7 @@ class DealService(BaseService):
|
||||
else:
|
||||
new_rank = lexorank.middle(lexorank.Bucket.BUCEKT_0)
|
||||
|
||||
await self.change_status(deal, request.status, user,
|
||||
await self.change_status(deal, request.status_id, user,
|
||||
deadline=request.deadline,
|
||||
comment=request.comment,
|
||||
rank=str(new_rank))
|
||||
@@ -1079,12 +1079,9 @@ class DealService(BaseService):
|
||||
return DealCompleteResponse(ok=False, message="Сделка не найдена")
|
||||
|
||||
if deal.group:
|
||||
deals = await DealGroupService(self.session).complete_group(deal.group.id)
|
||||
for completed_deal in deals:
|
||||
await self.change_status(completed_deal, DealStatus.COMPLETED, user)
|
||||
await deal_group.DealGroupService(self.session).complete_group(deal.group.id)
|
||||
else:
|
||||
deal.is_completed = True
|
||||
await self.change_status(deal, DealStatus.COMPLETED, user)
|
||||
await self.session.commit()
|
||||
|
||||
return DealCompleteResponse(ok=True, message="Сделка успешно завершена")
|
||||
@@ -1234,14 +1231,15 @@ class DealService(BaseService):
|
||||
async def _create_deal_from_excel(
|
||||
self,
|
||||
client: Client,
|
||||
deal_status: DealStatus,
|
||||
breakdown: CityBreakdownFromExcelSchema,
|
||||
user: User,
|
||||
) -> Deal:
|
||||
rank = await self._get_rank_for_deal(DealStatus.CREATED)
|
||||
rank = await self._get_rank_for_deal(deal_status.id)
|
||||
deal = Deal(
|
||||
name=f"{client.name} - {breakdown.base_marketplace.key.upper()} - {breakdown.shipping_warehouse.name}",
|
||||
created_at=datetime.datetime.now(),
|
||||
current_status=DealStatus.CREATED,
|
||||
current_status_id=deal_status.id,
|
||||
lexorank=rank,
|
||||
client_id=client.id,
|
||||
base_marketplace_key=breakdown.base_marketplace.key,
|
||||
@@ -1250,19 +1248,8 @@ class DealService(BaseService):
|
||||
|
||||
self.session.add(deal)
|
||||
await self.session.flush()
|
||||
await self.change_status(deal, DealStatus.AWAITING_ACCEPTANCE, user)
|
||||
|
||||
return deal
|
||||
|
||||
async def _create_group(self) -> DealGroup:
|
||||
group = models.DealGroup(
|
||||
name='',
|
||||
lexorank=lexorank.middle(lexorank.Bucket.BUCEKT_0).__str__(),
|
||||
)
|
||||
self.session.add(group)
|
||||
await self.session.flush()
|
||||
return group
|
||||
|
||||
async def _get_or_create_warehouse(
|
||||
self,
|
||||
shipping_warehouse: OptionalShippingWarehouseSchema,
|
||||
@@ -1288,8 +1275,12 @@ class DealService(BaseService):
|
||||
if not client:
|
||||
return CreateDealsFromExcelResponse(ok=False, message=f"Клиент с ID {request.client_id} не найден")
|
||||
|
||||
deal_status: Optional[DealStatus] = await self.session.get(DealStatus, request.status_id)
|
||||
if not deal_status:
|
||||
return CreateDealsFromExcelResponse(ok=False, message=f"Статус с ID {request.status_id} не найден")
|
||||
|
||||
deals_dict: dict[str, Deal] = {}
|
||||
group = await self._create_group()
|
||||
group = await deal_group.DealGroupService(self.session).create_group_model()
|
||||
|
||||
for product_data in request.products:
|
||||
for breakdown in product_data.cities_breakdown:
|
||||
@@ -1298,7 +1289,7 @@ class DealService(BaseService):
|
||||
key = f"{breakdown.shipping_warehouse.id} - {breakdown.base_marketplace.key}"
|
||||
deal = deals_dict.get(key)
|
||||
if not deal:
|
||||
deal = await self._create_deal_from_excel(client, breakdown, user)
|
||||
deal = await self._create_deal_from_excel(client, deal_status, breakdown, user)
|
||||
deals_dict[key] = deal
|
||||
|
||||
insert_stmt = insert(deal_relations).values({
|
||||
@@ -1317,121 +1308,6 @@ class DealService(BaseService):
|
||||
await self.session.commit()
|
||||
return CreateDealsFromExcelResponse(ok=True, message="Сделки успешно созданы")
|
||||
|
||||
async def add_to_group(self, user: User, request: DealAddToGroupRequest) -> DealAddToGroupResponse:
|
||||
try:
|
||||
group_bill_request = await self.session.get(GroupBillRequest, request.group_id)
|
||||
if group_bill_request:
|
||||
raise Exception("Нельзя добавить сделку, так как на группу выставлен счёт.")
|
||||
|
||||
# changing status if needed
|
||||
deal_id = await self.session.scalar(
|
||||
select(deal_relations.c.deal_id)
|
||||
.where(deal_relations.c.group_id == request.group_id)
|
||||
)
|
||||
group_deal_status = await self.session.scalar(
|
||||
select(Deal.current_status)
|
||||
.where(Deal.id == deal_id)
|
||||
)
|
||||
request_deal = await self.session.scalar(
|
||||
select(Deal).where(Deal.id == request.deal_id)
|
||||
)
|
||||
if group_deal_status != request_deal.current_status:
|
||||
await self.change_status(request_deal, group_deal_status, user)
|
||||
insert_stmt = insert(deal_relations).values({
|
||||
'deal_id': request.deal_id,
|
||||
'group_id': request.group_id
|
||||
})
|
||||
await self.session.execute(insert_stmt)
|
||||
await self.session.commit()
|
||||
|
||||
return DealAddToGroupResponse(ok=True, message="Сделка успешно добавлена в группу")
|
||||
except Exception as e:
|
||||
await self.session.rollback()
|
||||
return DealAddToGroupResponse(ok=False, message=str(e))
|
||||
|
||||
async def create_group(self, user: User, request: DealCreateGroupRequest) -> DealCreateGroupResponse:
|
||||
try:
|
||||
# getting lexorank for grop
|
||||
|
||||
group = await self._create_group()
|
||||
|
||||
for deal_id in [request.dragging_deal_id, request.hovered_deal_id]:
|
||||
insert_stmt = insert(deal_relations).values({
|
||||
'deal_id': deal_id,
|
||||
'group_id': group.id
|
||||
})
|
||||
await self.session.execute(insert_stmt)
|
||||
# changing status if needed on draggable deal
|
||||
dragging_deal = await self.session.scalar(
|
||||
select(Deal).where(Deal.id == request.dragging_deal_id)
|
||||
)
|
||||
dropped_deal = await self.session.scalar(
|
||||
select(Deal).where(Deal.id == request.hovered_deal_id)
|
||||
)
|
||||
if dragging_deal.current_status != dropped_deal.current_status:
|
||||
await self.change_status(dragging_deal, DealStatus(dropped_deal.current_status), user)
|
||||
await self.session.commit()
|
||||
return DealCreateGroupResponse(ok=True, message="Группа успешно создана")
|
||||
except Exception as e:
|
||||
return DealCreateGroupResponse(ok=False, message=str(e))
|
||||
|
||||
async def remove_from_group(self, request: DealRemoveFromGroupRequest) -> DealRemoveFromGroupResponse:
|
||||
try:
|
||||
delete_stmt = (
|
||||
delete(deal_relations)
|
||||
.where(
|
||||
deal_relations.c.deal_id == request.deal_id,
|
||||
)
|
||||
)
|
||||
await self.session.execute(delete_stmt)
|
||||
await self.session.commit()
|
||||
return DealRemoveFromGroupResponse(ok=True, message="Сделка успешно удалена из группы")
|
||||
except Exception as e:
|
||||
await self.session.rollback()
|
||||
return DealRemoveFromGroupResponse(ok=False, message=str(e))
|
||||
|
||||
async def update_group(self, request: DealGroupUpdateRequest) -> DealGroupUpdateResponse:
|
||||
try:
|
||||
group = await self.session.scalar(
|
||||
select(models.DealGroup).where(models.DealGroup.id == request.data.id)
|
||||
)
|
||||
if not group:
|
||||
return DealGroupUpdateResponse(ok=False, message="Группа не найдена")
|
||||
# update by dictionary
|
||||
request_dict = request.data.dict()
|
||||
update_stmt = (
|
||||
update(
|
||||
models.DealGroup
|
||||
)
|
||||
.where(models.DealGroup.id == request.data.id)
|
||||
.values(**request_dict)
|
||||
)
|
||||
await self.session.execute(update_stmt)
|
||||
await self.session.commit()
|
||||
return DealGroupUpdateResponse(ok=True, message="Группа успешно обновлена")
|
||||
except Exception as e:
|
||||
await self.session.rollback()
|
||||
return DealGroupUpdateResponse(ok=False, message=str(e))
|
||||
|
||||
async def change_group_status(self, user: User,
|
||||
request: DealGroupChangeStatusRequest) -> DealGroupChangeStatusResponse:
|
||||
try:
|
||||
# getting all deals in group
|
||||
deals = await self.session.scalars(
|
||||
select(deal_relations.c.deal_id)
|
||||
.where(deal_relations.c.group_id == request.group_id)
|
||||
)
|
||||
for deal_id in deals:
|
||||
deal = await self.session.scalar(
|
||||
select(Deal).where(Deal.id == deal_id)
|
||||
)
|
||||
await self.change_status(deal, DealStatus(request.new_status), user)
|
||||
await self.session.commit()
|
||||
return DealGroupChangeStatusResponse(ok=True, message="Статус группы успешно изменен")
|
||||
except Exception as e:
|
||||
await self.session.rollback()
|
||||
return DealGroupChangeStatusResponse(ok=False, message=str(e))
|
||||
|
||||
async def get_deals_grouped(self, deal: models.Deal) -> List[models.Deal]:
|
||||
if not deal.group:
|
||||
return [deal]
|
||||
|
||||
Reference in New Issue
Block a user