from time import time from typing import Optional from fastapi import APIRouter from pydantic import BaseModel from app import mongo from app.mongo import deals_collection, transactions_collection from app.utils.response_util import response router = APIRouter() class ProfitParams(BaseModel): baseMarketplaceKey: str boardId: int clientId: int dateRange: list dealStatusId: int expenseTagId: int incomeTagId: int isCompletedOnly: bool managerId: int projectId: int groupTableBy: Optional[int] = None def get_item_date(item: dict): if item.get("completedAt"): return item["completedAt"].split("T")[0] if item.get("createdAt"): return item["createdAt"].split("T")[0] if item.get("spentDate"): return item["spentDate"] async def group_key(item: dict, group_by: int) -> Optional[str]: if group_by == 0: return get_item_date(item) elif group_by == 1: return item["clientName"] elif group_by == 2: project = await mongo.projects_collection.find_one({"id": item["board"]["projectId"]}) return project["name"] if project else None elif group_by == 3: board = await mongo.boards_collection.find_one({"id": item["board"]["id"]}) return board["name"] if board else None elif group_by == 4: return item["statusId"] elif group_by == 5: return item["shippingWarehouse"] elif group_by == 6: return item["baseMarketplace"]["name"] elif group_by == 7: manager = await mongo.users_collection.find_one({"id": item.get("managerId")}) return manager["name"] if manager else None return None async def get_profit_data(params: ProfitParams): start_datetime = f'{params.dateRange[0]}T00:00:00' end_datetime = f'{params.dateRange[1]}T23:59:59.999' deals_query = { "$and": [ { "$or": [ {"isDeleted": False}, {"isDeleted": {"$exists": False}} ] }, { "$or": [ {"completedAt": {"$gte": start_datetime, "$lte": end_datetime}}, {"$and": [ {"completedAt": None}, {"createdAt": {"$gte": start_datetime, "$lte": end_datetime}} ]} ] } ] } if params.dealStatusId != -1: deals_query["statusId"] = params.dealStatusId elif params.boardId != -1: deals_query["board.id"] = params.boardId elif params.projectId != -1: deals_query["project.id"] = params.projectId if params.isCompletedOnly: deals_query["isCompleted"] = True client = None if params.clientId != -1: client = await mongo.clients_collection.find_one({"id": params.clientId}, {"_id": False}) deals = [] for deal in await deals_collection.find(deals_query, {"_id": False}).to_list(None): if params.baseMarketplaceKey != "all" and deal["baseMarketplace"]["key"] != params.baseMarketplaceKey: continue if client and deal["clientName"] != client["name"]: continue deals.append(deal) transactions_query = { "spentDate": {"$gte": params.dateRange[0], "$lte": params.dateRange[1]}, } filter_income_tag = params.incomeTagId != -1 filter_expense_tag = params.expenseTagId != -1 transactions = await transactions_collection.find(transactions_query, {"_id": False}).to_list(None) if filter_income_tag or filter_expense_tag: def tag_matches(item, tag_id): return any(tag.get("id") == tag_id for tag in item.get("tags", [])) filtered_transactions = [] for transaction in transactions: if transaction["isIncome"] and filter_income_tag: if tag_matches(transaction, params.incomeTagId): filtered_transactions.append(transaction) elif not transaction["isIncome"] and filter_expense_tag: if tag_matches(transaction, params.expenseTagId): filtered_transactions.append(transaction) elif not filter_income_tag and not filter_expense_tag: filtered_transactions.append(transaction) transactions = filtered_transactions return deals, transactions @router.post("/get-profit-chart-data", tags=["Profit"]) async def get_profit_chart_data(params: ProfitParams): start_time = time() deals, transactions = await get_profit_data(params) deals_total_prices = await mongo.get_deals_total_prices(*deals) grouped = {} for deal in deals: date = get_item_date(deal) if date not in grouped: grouped[date] = { "date": date, "revenue": 0, "expenses": 0, "profit": 0, "dealsCount": 0 } grouped[date]["dealsCount"] += 1 if deal.get("isCompleted") or deal.get("completedAt"): total_price = deals_total_prices.get(deal["id"]) if total_price: grouped[date]["revenue"] += total_price grouped[date]["profit"] += total_price for transaction in transactions: date = transaction["spentDate"] if date not in grouped: grouped[date] = { "date": date, "revenue": 0, "expenses": 0, "profit": 0, "dealsCount": 0 } if transaction["isIncome"]: grouped[date]["revenue"] += transaction["amount"] grouped[date]["profit"] += transaction["amount"] else: grouped[date]["expenses"] += transaction["amount"] grouped[date]["profit"] -= transaction["amount"] sorted_grouped_values = [value for _, value in sorted(grouped.items())] return response({"data": sorted_grouped_values}, start_time=start_time) @router.post("/get-profit-table-data", tags=["Profit"]) async def get_profit_table_data(params: ProfitParams): start_time = time() deals, transactions = await get_profit_data(params) deals_total_prices = await mongo.get_deals_total_prices(*deals) grouped = {} for deal in deals: key = await group_key(deal, params.groupTableBy) if not key: continue if key not in grouped: grouped[key] = { "groupedValue": key, "revenue": 0, "expenses": 0, "profit": 0, "dealsCount": 0 } grouped[key]["dealsCount"] += 1 if deal.get("isCompleted") or deal.get("completedAt"): total_price = deals_total_prices.get(deal["id"]) if total_price: grouped[key]["revenue"] += total_price grouped[key]["profit"] += total_price if params.groupTableBy == 0: for transaction in transactions: key = await group_key(transaction, params.groupTableBy) if not key: continue if key not in grouped: grouped[key] = { "groupedValue": key, "revenue": 0, "expenses": 0, "profit": 0, "dealsCount": 0 } if transaction["isIncome"]: grouped[key]["revenue"] += transaction["amount"] grouped[key]["profit"] += transaction["amount"] else: grouped[key]["expenses"] += transaction["amount"] grouped[key]["profit"] -= transaction["amount"] sorted_grouped_values = [value for _, value in sorted(grouped.items())] return response({"data": sorted_grouped_values}, start_time=start_time)