feat: additional expenses
This commit is contained in:
2
main.py
2
main.py
@@ -47,7 +47,7 @@ routers_list = [
|
|||||||
routers.task_router,
|
routers.task_router,
|
||||||
routers.statistics_router,
|
routers.statistics_router,
|
||||||
routers.work_shifts_router,
|
routers.work_shifts_router,
|
||||||
|
routers.expense_router,
|
||||||
]
|
]
|
||||||
for router in routers_list:
|
for router in routers_list:
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ from .payroll import *
|
|||||||
from .billing import *
|
from .billing import *
|
||||||
from .marketplace_products import *
|
from .marketplace_products import *
|
||||||
from .deal_group import *
|
from .deal_group import *
|
||||||
|
from .expense import *
|
||||||
|
|
||||||
configure_mappers()
|
configure_mappers()
|
||||||
|
|||||||
24
models/expense.py
Normal file
24
models/expense.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from datetime import datetime, date
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from sqlalchemy import ForeignKey
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
|
from models import BaseModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from models import User
|
||||||
|
|
||||||
|
|
||||||
|
class Expense(BaseModel):
|
||||||
|
__tablename__ = 'expenses'
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
created_at: Mapped[datetime] = mapped_column(nullable=False)
|
||||||
|
spent_date: Mapped[date] = mapped_column(nullable=False)
|
||||||
|
name: Mapped[str] = mapped_column()
|
||||||
|
comment: Mapped[str] = mapped_column()
|
||||||
|
amount: Mapped[float] = mapped_column()
|
||||||
|
|
||||||
|
created_by_user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
|
||||||
|
created_by_user: Mapped["User"] = relationship(foreign_keys=[created_by_user_id])
|
||||||
@@ -14,4 +14,5 @@ from .time_tracking import time_tracking_router
|
|||||||
from .billing import billing_router
|
from .billing import billing_router
|
||||||
from .task import task_router
|
from .task import task_router
|
||||||
from .work_shifts import work_shifts_router
|
from .work_shifts import work_shifts_router
|
||||||
from .statistics import statistics_router
|
from .statistics import statistics_router
|
||||||
|
from .expense import expense_router
|
||||||
49
routers/expense.py
Normal file
49
routers/expense.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
|
from backend.dependecies import SessionDependency, CurrentUserDependency, PaginationDependency
|
||||||
|
from schemas.expense import GetAllExpensesResponse, UpdateExpenseResponse, UpdateExpenseRequest, DeleteExpenseResponse
|
||||||
|
from services.auth import authorized_user
|
||||||
|
from services.expenses import ExpensesService
|
||||||
|
|
||||||
|
expense_router = APIRouter(
|
||||||
|
prefix="/expense",
|
||||||
|
tags=["expense"],
|
||||||
|
dependencies=[Depends(authorized_user)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@expense_router.get(
|
||||||
|
'/get-all',
|
||||||
|
operation_id='get_all_expenses',
|
||||||
|
response_model=GetAllExpensesResponse,
|
||||||
|
)
|
||||||
|
async def get_all(
|
||||||
|
session: SessionDependency,
|
||||||
|
pagination: PaginationDependency,
|
||||||
|
):
|
||||||
|
return await ExpensesService(session).get_all(pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@expense_router.post(
|
||||||
|
'/update',
|
||||||
|
operation_id='update_expense',
|
||||||
|
response_model=UpdateExpenseResponse,
|
||||||
|
)
|
||||||
|
async def update_expense(
|
||||||
|
session: SessionDependency,
|
||||||
|
request: UpdateExpenseRequest,
|
||||||
|
user: CurrentUserDependency,
|
||||||
|
):
|
||||||
|
return await ExpensesService(session).update_expense(user, request)
|
||||||
|
|
||||||
|
|
||||||
|
@expense_router.delete(
|
||||||
|
'/delete/{expense_id}',
|
||||||
|
operation_id='delete_expense',
|
||||||
|
response_model=DeleteExpenseResponse,
|
||||||
|
)
|
||||||
|
async def delete_expense(
|
||||||
|
session: SessionDependency,
|
||||||
|
expense_id: int,
|
||||||
|
):
|
||||||
|
return await ExpensesService(session).delete_expense(expense_id)
|
||||||
@@ -7,7 +7,7 @@ from backend.session import get_session
|
|||||||
from schemas.statistics import GetProfitChartDataRequest, GetProfitChartDataResponse, GetProfitTableDataResponse, \
|
from schemas.statistics import GetProfitChartDataRequest, GetProfitChartDataResponse, GetProfitTableDataResponse, \
|
||||||
GetProfitTableDataRequest
|
GetProfitTableDataRequest
|
||||||
from services.auth import authorized_user
|
from services.auth import authorized_user
|
||||||
from services.statistics import StatisticsService
|
from services.statistics import ProfitStatisticsService
|
||||||
|
|
||||||
statistics_router = APIRouter(
|
statistics_router = APIRouter(
|
||||||
prefix="/statistics",
|
prefix="/statistics",
|
||||||
@@ -25,7 +25,7 @@ async def get_profit_chart_data(
|
|||||||
session: Annotated[AsyncSession, Depends(get_session)],
|
session: Annotated[AsyncSession, Depends(get_session)],
|
||||||
request: GetProfitChartDataRequest
|
request: GetProfitChartDataRequest
|
||||||
):
|
):
|
||||||
return await StatisticsService(session).get_profit_chart_data(request)
|
return await ProfitStatisticsService(session).get_profit_chart_data(request)
|
||||||
|
|
||||||
|
|
||||||
@statistics_router.post(
|
@statistics_router.post(
|
||||||
@@ -37,4 +37,4 @@ async def get_profit_table_data(
|
|||||||
session: Annotated[AsyncSession, Depends(get_session)],
|
session: Annotated[AsyncSession, Depends(get_session)],
|
||||||
request: GetProfitTableDataRequest
|
request: GetProfitTableDataRequest
|
||||||
):
|
):
|
||||||
return await StatisticsService(session).get_profit_table_data(request)
|
return await ProfitStatisticsService(session).get_profit_table_data(request)
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ class PaginationSchema(BaseSchema):
|
|||||||
|
|
||||||
|
|
||||||
class PaginationInfoSchema(BaseSchema):
|
class PaginationInfoSchema(BaseSchema):
|
||||||
total_pages: int
|
total_pages: int = 0
|
||||||
total_items: int
|
total_items: int = 0
|
||||||
|
|
||||||
|
|
||||||
class BaseEnumSchema(BaseSchema):
|
class BaseEnumSchema(BaseSchema):
|
||||||
|
|||||||
52
schemas/expense.py
Normal file
52
schemas/expense.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from schemas.base import OkMessageSchema, BaseSchema, PaginationInfoSchema
|
||||||
|
from schemas.user import UserSchema
|
||||||
|
|
||||||
|
|
||||||
|
# region Entities
|
||||||
|
|
||||||
|
class ExpenseSchemaBase(BaseSchema):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
comment: str
|
||||||
|
amount: float
|
||||||
|
created_by_user: UserSchema
|
||||||
|
spent_date: datetime.date
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateExpenseSchema(BaseSchema):
|
||||||
|
id: Optional[int] = None
|
||||||
|
name: str
|
||||||
|
comment: Optional[str] = ""
|
||||||
|
amount: float
|
||||||
|
spent_date: datetime.date
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region Requests
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateExpenseRequest(BaseSchema):
|
||||||
|
expense: UpdateExpenseSchema
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region Responses
|
||||||
|
|
||||||
|
class GetAllExpensesResponse(BaseSchema):
|
||||||
|
expenses: list[ExpenseSchemaBase]
|
||||||
|
pagination_info: PaginationInfoSchema
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateExpenseResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteExpenseResponse(OkMessageSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# endregion
|
||||||
85
services/expenses.py
Normal file
85
services/expenses.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import math
|
||||||
|
from fastapi import HTTPException
|
||||||
|
from sqlalchemy import delete, select, func
|
||||||
|
from fastapi import status
|
||||||
|
|
||||||
|
from models import User
|
||||||
|
from models.expense import Expense
|
||||||
|
from schemas.base import PaginationSchema, PaginationInfoSchema
|
||||||
|
from schemas.expense import UpdateExpenseResponse, UpdateExpenseRequest, DeleteExpenseResponse, GetAllExpensesResponse
|
||||||
|
from services.base import BaseService
|
||||||
|
from utils.dependecies import is_valid_pagination
|
||||||
|
|
||||||
|
|
||||||
|
class ExpensesService(BaseService):
|
||||||
|
async def get_all(self, pagination: PaginationSchema) -> GetAllExpensesResponse:
|
||||||
|
if not is_valid_pagination(pagination):
|
||||||
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid pagination')
|
||||||
|
page = max(0, pagination.page - 1)
|
||||||
|
|
||||||
|
stmt = (
|
||||||
|
select(Expense)
|
||||||
|
.order_by(Expense.spent_date.desc())
|
||||||
|
.offset(page * pagination.items_per_page)
|
||||||
|
.limit(pagination.items_per_page)
|
||||||
|
)
|
||||||
|
|
||||||
|
total_records = await self.session.scalar(select(func.count()).select_from(Expense))
|
||||||
|
if not total_records:
|
||||||
|
return GetAllExpensesResponse(
|
||||||
|
expenses=[],
|
||||||
|
pagination_info=PaginationInfoSchema()
|
||||||
|
)
|
||||||
|
total_items = total_records
|
||||||
|
total_pages = math.ceil(total_records / pagination.items_per_page)
|
||||||
|
|
||||||
|
expenses = await self.session.execute(stmt)
|
||||||
|
expenses = expenses.scalars().all()
|
||||||
|
response = GetAllExpensesResponse(
|
||||||
|
expenses=expenses,
|
||||||
|
pagination_info=PaginationInfoSchema(
|
||||||
|
total_items=total_items,
|
||||||
|
total_pages=total_pages
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def get_by_id(self, expense_id) -> Optional[Expense]:
|
||||||
|
expense = await self.session.get(Expense, expense_id)
|
||||||
|
return expense
|
||||||
|
|
||||||
|
async def update_expense(self, user: User, request: UpdateExpenseRequest) -> UpdateExpenseResponse:
|
||||||
|
expense = await self.get_by_id(request.expense.id)
|
||||||
|
|
||||||
|
if not expense:
|
||||||
|
expense = Expense(
|
||||||
|
created_at=datetime.now(),
|
||||||
|
name=request.expense.name,
|
||||||
|
comment=request.expense.comment,
|
||||||
|
amount=request.expense.amount,
|
||||||
|
spent_date=request.expense.spent_date,
|
||||||
|
created_by_user_id=user.id,
|
||||||
|
)
|
||||||
|
self.session.add(expense)
|
||||||
|
await self.session.commit()
|
||||||
|
return UpdateExpenseResponse(ok=True, message='Запись о расходах успешно создана')
|
||||||
|
|
||||||
|
expense.name = request.expense.name
|
||||||
|
expense.amount = request.expense.amount
|
||||||
|
expense.comment = request.expense.comment
|
||||||
|
expense.spent_date = request.expense.spent_date
|
||||||
|
self.session.add(expense)
|
||||||
|
await self.session.commit()
|
||||||
|
return UpdateExpenseResponse(ok=True, message='Запись о расходах успешно изменена')
|
||||||
|
|
||||||
|
async def delete_expense(self, expense_id) -> DeleteExpenseResponse:
|
||||||
|
stmt = (
|
||||||
|
delete(Expense)
|
||||||
|
.where(Expense.id == expense_id)
|
||||||
|
)
|
||||||
|
await self.session.execute(stmt)
|
||||||
|
await self.session.commit()
|
||||||
|
return DeleteExpenseResponse(ok=True, message='Запись о расходах успешно удалена')
|
||||||
@@ -149,10 +149,7 @@ class PayrollService(BaseService):
|
|||||||
if not total_records:
|
if not total_records:
|
||||||
return GetPaymentRecordsResponse(
|
return GetPaymentRecordsResponse(
|
||||||
payment_records=[],
|
payment_records=[],
|
||||||
pagination_info=PaginationInfoSchema(
|
pagination_info=PaginationInfoSchema()
|
||||||
total_pages=0,
|
|
||||||
total_items=0
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
total_items = total_records
|
total_items = total_records
|
||||||
total_pages = math.ceil(total_records / pagination.items_per_page)
|
total_pages = math.ceil(total_records / pagination.items_per_page)
|
||||||
|
|||||||
1
services/statistics/__init__.py
Normal file
1
services/statistics/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .profit_statistics import ProfitStatisticsService
|
||||||
19
services/statistics/common.py
Normal file
19
services/statistics/common.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from sqlalchemy import select, func, literal, text, cast, CTE
|
||||||
|
from sqlalchemy.dialects.postgresql import TIMESTAMP
|
||||||
|
|
||||||
|
|
||||||
|
def generate_date_range(date_from: date, date_to: date, additional_columns: list[str]) -> CTE:
|
||||||
|
cols = [literal(0).label(col_label) for col_label in additional_columns]
|
||||||
|
return select(
|
||||||
|
cast(
|
||||||
|
func.generate_series(
|
||||||
|
date_from,
|
||||||
|
date_to,
|
||||||
|
text("'1 day'")
|
||||||
|
),
|
||||||
|
TIMESTAMP(timezone=False)
|
||||||
|
).label("date"),
|
||||||
|
*cols,
|
||||||
|
).cte()
|
||||||
60
services/statistics/expenses_statistics.py
Normal file
60
services/statistics/expenses_statistics.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
from datetime import date
|
||||||
|
from sqlalchemy import select, func, Subquery, cast
|
||||||
|
from sqlalchemy.dialects.postgresql import TIMESTAMP
|
||||||
|
from models import PaymentRecord, Expense
|
||||||
|
from services.base import BaseService
|
||||||
|
from services.statistics.common import generate_date_range
|
||||||
|
|
||||||
|
|
||||||
|
class ExpensesStatisticsService(BaseService):
|
||||||
|
date_from: date
|
||||||
|
date_to: date
|
||||||
|
|
||||||
|
def _get_expenses_sub(self, model, date_column, amount_column) -> Subquery:
|
||||||
|
all_dates = generate_date_range(self.date_from, self.date_to, ["expenses"])
|
||||||
|
|
||||||
|
expenses = (
|
||||||
|
select(
|
||||||
|
func.sum(getattr(model, amount_column)).label("expenses"),
|
||||||
|
cast(getattr(model, date_column), TIMESTAMP(timezone=False)).label("date"),
|
||||||
|
)
|
||||||
|
.group_by("date")
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
|
||||||
|
expenses_with_gaps_filled = (
|
||||||
|
select(
|
||||||
|
all_dates.c.date,
|
||||||
|
(all_dates.c.expenses + func.coalesce(expenses.c.expenses, 0)).label("expenses"),
|
||||||
|
)
|
||||||
|
.join(expenses, all_dates.c.date == expenses.c.date, isouter=True)
|
||||||
|
.order_by(all_dates.c.date)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
return expenses_with_gaps_filled
|
||||||
|
|
||||||
|
def _apply_expenses(self, deals_by_dates: Subquery, expenses_subquery: Subquery):
|
||||||
|
return (
|
||||||
|
select(
|
||||||
|
deals_by_dates.c.date,
|
||||||
|
deals_by_dates.c.deals_count,
|
||||||
|
deals_by_dates.c.revenue,
|
||||||
|
(func.coalesce(deals_by_dates.c.profit, 0) - func.coalesce(expenses_subquery.c.expenses, 0)).label(
|
||||||
|
"profit"),
|
||||||
|
(deals_by_dates.c.expenses + expenses_subquery.c.expenses).label("expenses"),
|
||||||
|
)
|
||||||
|
.join(expenses_subquery, expenses_subquery.c.date == deals_by_dates.c.date)
|
||||||
|
)
|
||||||
|
|
||||||
|
def apply_expenses(self, date_from: date, date_to: date, deals_by_dates: Subquery):
|
||||||
|
self.date_from, self.date_to = date_from, date_to
|
||||||
|
|
||||||
|
# Apply salary expenses
|
||||||
|
salary_expenses = self._get_expenses_sub(PaymentRecord, "start_date", "amount")
|
||||||
|
deals_by_dates = self._apply_expenses(deals_by_dates, salary_expenses)
|
||||||
|
|
||||||
|
# Apply additional expenses
|
||||||
|
additional_expenses = self._get_expenses_sub(Expense, "spent_date", "amount")
|
||||||
|
deals_by_dates = self._apply_expenses(deals_by_dates, additional_expenses)
|
||||||
|
|
||||||
|
return deals_by_dates
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
|
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from sqlalchemy import select, and_, union_all, func, Subquery, literal, text
|
from sqlalchemy import select, and_, union_all, func, Subquery, literal
|
||||||
|
|
||||||
from enums.profit_table_group_by import ProfitTableGroupBy
|
from enums.profit_table_group_by import ProfitTableGroupBy
|
||||||
from models import DealService, Deal, DealStatusHistory, DealProductService, DealProduct, Service, Client, \
|
from models import DealService, Deal, DealStatusHistory, DealProductService, DealProduct, Service, Client, \
|
||||||
ShippingWarehouse, BaseMarketplace, User, PaymentRecord
|
ShippingWarehouse, BaseMarketplace, User
|
||||||
from schemas.statistics import GetProfitChartDataResponse, GetProfitChartDataRequest, ProfitChartDataItem, \
|
from schemas.statistics import GetProfitChartDataResponse, GetProfitChartDataRequest, ProfitChartDataItem, \
|
||||||
GetProfitTableDataResponse, GetProfitTableDataRequest, ProfitTableDataItem, CommonProfitFilters
|
GetProfitTableDataResponse, GetProfitTableDataRequest, ProfitTableDataItem, CommonProfitFilters
|
||||||
from services.base import BaseService
|
from services.base import BaseService
|
||||||
|
from services.statistics.common import generate_date_range
|
||||||
|
from services.statistics.expenses_statistics import ExpensesStatisticsService
|
||||||
|
|
||||||
|
|
||||||
class StatisticsService(BaseService):
|
class ProfitStatisticsService(BaseService):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_sub_deals_created_at(date_from: datetime.date, date_to: datetime.date):
|
def _get_sub_deals_created_at(date_from: datetime.date, date_to: datetime.date):
|
||||||
return (
|
return (
|
||||||
@@ -46,7 +48,7 @@ class StatisticsService(BaseService):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_filtered_sub_status_history(date_from: datetime.date, date_to: datetime.date):
|
def _get_filtered_sub_status_history(date_from: datetime.date, date_to: datetime.date):
|
||||||
sub_status_history = StatisticsService._get_sub_status_history()
|
sub_status_history = ProfitStatisticsService._get_sub_status_history()
|
||||||
return (
|
return (
|
||||||
select(sub_status_history)
|
select(sub_status_history)
|
||||||
.where(sub_status_history.c.date.between(date_from, date_to))
|
.where(sub_status_history.c.date.between(date_from, date_to))
|
||||||
@@ -55,8 +57,8 @@ class StatisticsService(BaseService):
|
|||||||
|
|
||||||
def _get_deals_dates(self, deal_status_id: int):
|
def _get_deals_dates(self, deal_status_id: int):
|
||||||
if deal_status_id == -1:
|
if deal_status_id == -1:
|
||||||
return StatisticsService._get_sub_deals_created_at(self.date_from, self.date_to)
|
return ProfitStatisticsService._get_sub_deals_created_at(self.date_from, self.date_to)
|
||||||
return StatisticsService._get_filtered_sub_status_history(self.date_from, self.date_to)
|
return ProfitStatisticsService._get_filtered_sub_status_history(self.date_from, self.date_to)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _to_schema(rows, is_chart: bool = True) -> (
|
def _to_schema(rows, is_chart: bool = True) -> (
|
||||||
@@ -161,7 +163,7 @@ class StatisticsService(BaseService):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _group_by_date(self, stmt):
|
def _group_by_date(self, stmt):
|
||||||
all_dates = self._generate_date_range(["deals_count", "revenue", "profit", "expenses"])
|
all_dates = generate_date_range(self.date_from, self.date_to, ["deals_count", "revenue", "profit", "expenses"])
|
||||||
deals = (
|
deals = (
|
||||||
select(
|
select(
|
||||||
stmt.c.date,
|
stmt.c.date,
|
||||||
@@ -276,60 +278,6 @@ class StatisticsService(BaseService):
|
|||||||
.group_by(managers.c.id, "grouped_value")
|
.group_by(managers.c.id, "grouped_value")
|
||||||
)
|
)
|
||||||
|
|
||||||
def _generate_date_range(self, additional_columns: list[str]):
|
|
||||||
cols = [literal(0).label(col_label) for col_label in additional_columns]
|
|
||||||
date_from = self.date_from + timedelta(days=1)
|
|
||||||
return select(
|
|
||||||
func.date_trunc(
|
|
||||||
"day",
|
|
||||||
func.generate_series(
|
|
||||||
date_from,
|
|
||||||
self.date_to,
|
|
||||||
text("'1 day'")
|
|
||||||
),
|
|
||||||
).label("date"),
|
|
||||||
*cols,
|
|
||||||
).cte()
|
|
||||||
|
|
||||||
def _get_expenses_sub(self) -> Subquery:
|
|
||||||
all_dates = self._generate_date_range(["expenses"])
|
|
||||||
payment_records = (
|
|
||||||
select(
|
|
||||||
func.sum(PaymentRecord.amount).label("expenses"),
|
|
||||||
func.date_trunc(
|
|
||||||
"day",
|
|
||||||
PaymentRecord.start_date,
|
|
||||||
).label("date"),
|
|
||||||
)
|
|
||||||
.where(PaymentRecord.start_date.between(self.date_from, self.date_to))
|
|
||||||
.group_by("date")
|
|
||||||
.subquery()
|
|
||||||
)
|
|
||||||
payments_with_filled_gaps = (
|
|
||||||
select(
|
|
||||||
all_dates.c.date,
|
|
||||||
(all_dates.c.expenses + func.coalesce(payment_records.c.expenses, 0)).label("expenses"),
|
|
||||||
)
|
|
||||||
.join(payment_records, all_dates.c.date == payment_records.c.date, isouter=True)
|
|
||||||
.order_by(all_dates.c.date)
|
|
||||||
.subquery()
|
|
||||||
)
|
|
||||||
return payments_with_filled_gaps
|
|
||||||
|
|
||||||
def _apply_expenses(self, deals_by_dates: Subquery):
|
|
||||||
expenses_by_dates = self._get_expenses_sub()
|
|
||||||
|
|
||||||
return (
|
|
||||||
select(
|
|
||||||
deals_by_dates.c.date,
|
|
||||||
deals_by_dates.c.deals_count,
|
|
||||||
deals_by_dates.c.revenue,
|
|
||||||
(func.coalesce(deals_by_dates.c.profit, 0) - func.coalesce(expenses_by_dates.c.expenses, 0)).label("profit"),
|
|
||||||
expenses_by_dates.c.expenses,
|
|
||||||
)
|
|
||||||
.join(expenses_by_dates, expenses_by_dates.c.date == deals_by_dates.c.date)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _get_data_rows_grouped_by_date(
|
async def _get_data_rows_grouped_by_date(
|
||||||
self,
|
self,
|
||||||
stmt_deal_services,
|
stmt_deal_services,
|
||||||
@@ -344,7 +292,12 @@ class StatisticsService(BaseService):
|
|||||||
sub_grouped_by_deals = self._group_by_deals(sub_union)
|
sub_grouped_by_deals = self._group_by_deals(sub_union)
|
||||||
sub_deals_grouped_by_date = self._group_by_date(sub_grouped_by_deals).subquery()
|
sub_deals_grouped_by_date = self._group_by_date(sub_grouped_by_deals).subquery()
|
||||||
|
|
||||||
stmt_deals_applied_expenses = self._apply_expenses(sub_deals_grouped_by_date)
|
expenses_statistics_service = ExpensesStatisticsService(self.session)
|
||||||
|
stmt_deals_applied_expenses = expenses_statistics_service.apply_expenses(
|
||||||
|
self.date_from,
|
||||||
|
self.date_to,
|
||||||
|
sub_deals_grouped_by_date
|
||||||
|
)
|
||||||
|
|
||||||
result = await self.session.execute(stmt_deals_applied_expenses)
|
result = await self.session.execute(stmt_deals_applied_expenses)
|
||||||
rows = result.all()
|
rows = result.all()
|
||||||
@@ -352,7 +305,6 @@ class StatisticsService(BaseService):
|
|||||||
|
|
||||||
async def _get_data_grouped_by_date(self, request: CommonProfitFilters, is_chart: bool = True):
|
async def _get_data_grouped_by_date(self, request: CommonProfitFilters, is_chart: bool = True):
|
||||||
self.date_from, self.date_to = request.date_range
|
self.date_from, self.date_to = request.date_range
|
||||||
self.date_to += timedelta(days=1)
|
|
||||||
|
|
||||||
sub_deals_dates = self._get_deals_dates(request.deal_status_id)
|
sub_deals_dates = self._get_deals_dates(request.deal_status_id)
|
||||||
|
|
||||||
@@ -393,7 +345,6 @@ class StatisticsService(BaseService):
|
|||||||
|
|
||||||
def _get_common_table_grouped(self, request: GetProfitTableDataRequest):
|
def _get_common_table_grouped(self, request: GetProfitTableDataRequest):
|
||||||
self.date_from, self.date_to = request.date_range
|
self.date_from, self.date_to = request.date_range
|
||||||
self.date_to += timedelta(days=1)
|
|
||||||
|
|
||||||
sub_deals_dates = self._get_deals_dates(request.deal_status_id)
|
sub_deals_dates = self._get_deals_dates(request.deal_status_id)
|
||||||
|
|
||||||
@@ -433,7 +384,6 @@ class StatisticsService(BaseService):
|
|||||||
|
|
||||||
async def _get_table_grouped_by_statuses(self, request: GetProfitTableDataRequest) -> GetProfitTableDataResponse:
|
async def _get_table_grouped_by_statuses(self, request: GetProfitTableDataRequest) -> GetProfitTableDataResponse:
|
||||||
date_from, date_to = request.date_range
|
date_from, date_to = request.date_range
|
||||||
date_to += timedelta(days=1)
|
|
||||||
|
|
||||||
sub_deals_dates = self._get_filtered_sub_status_history(date_from, date_to)
|
sub_deals_dates = self._get_filtered_sub_status_history(date_from, date_to)
|
||||||
|
|
||||||
5
test.py
5
test.py
@@ -5,15 +5,14 @@ from sqlalchemy import select
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from backend.session import session_maker
|
from backend.session import session_maker
|
||||||
from schemas.statistics import GetProfitChartDataRequest
|
from services.statistics.profit_statistics import ProfitStatisticsService
|
||||||
from services.statistics import StatisticsService
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
session: AsyncSession = session_maker()
|
session: AsyncSession = session_maker()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
service = StatisticsService(session)
|
service = ProfitStatisticsService(session)
|
||||||
service.date_from = datetime.date(2020, 1, 20)
|
service.date_from = datetime.date(2020, 1, 20)
|
||||||
service.date_to = datetime.date(2020, 2, 10)
|
service.date_to = datetime.date(2020, 2, 10)
|
||||||
stmt = service._generate_date_range(["expenses"])
|
stmt = service._generate_date_range(["expenses"])
|
||||||
|
|||||||
Reference in New Issue
Block a user