feat: income

This commit is contained in:
2024-12-12 20:23:19 +04:00
parent daa34b2808
commit e5712224e1
16 changed files with 574 additions and 463 deletions

View File

@@ -3,24 +3,24 @@ from datetime import date
from sqlalchemy import select, func, Subquery, cast, CTE
from sqlalchemy.dialects.postgresql import TIMESTAMP
from models import PaymentRecord, Expense, expenses_expense_tags
from models import PaymentRecord
from schemas.statistics import CommonProfitFilters
from services.base import BaseService
from services.statistics.common import generate_date_range
class ExpensesStatisticsService(BaseService):
class PaymentStatisticsService(BaseService):
date_from: date
date_to: date
@staticmethod
def _fill_date_gaps(expenses: Subquery, all_dates: CTE) -> Subquery:
def _fill_date_gaps(payments: Subquery, all_dates: CTE) -> Subquery:
return (
select(
all_dates.c.date,
(all_dates.c.expenses + func.coalesce(expenses.c.expenses, 0)).label("expenses"),
(all_dates.c.expenses + func.coalesce(payments.c.expenses, 0)).label("expenses"),
)
.join(expenses, all_dates.c.date == expenses.c.date, isouter=True)
.join(payments, all_dates.c.date == payments.c.date, isouter=True)
.order_by(all_dates.c.date)
.subquery()
)
@@ -40,58 +40,24 @@ class ExpensesStatisticsService(BaseService):
expenses_with_filled_gaps = self._fill_date_gaps(expenses, all_dates)
return expenses_with_filled_gaps
def _get_additional_expenses_sub(self, tag_id: int) -> Subquery:
all_dates = generate_date_range(self.date_from, self.date_to, ["expenses"])
expenses = (
select(Expense)
)
if tag_id != -1:
expenses = (
expenses
.join(expenses_expense_tags)
.where(expenses_expense_tags.c.expense_tag_id == tag_id)
)
expenses = expenses.subquery()
expenses = (
select(
func.sum(expenses.c.amount).label("expenses"),
cast(expenses.c.spent_date, TIMESTAMP(timezone=False)).label("date"),
)
.where(expenses.c.spent_date.between(self.date_from, self.date_to))
.group_by("date")
.subquery()
)
expenses_with_filled_gaps = self._fill_date_gaps(expenses, all_dates)
return expenses_with_filled_gaps
@staticmethod
def _apply_expenses(deals_by_dates: Subquery, expenses_subquery: Subquery):
def _apply_payments(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"),
(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, filters: CommonProfitFilters, deals_by_dates: Subquery):
def apply_payments(self, filters: CommonProfitFilters, deals_by_dates: Subquery):
self.date_from, self.date_to = filters.date_range
# Apply salary expenses
salary_expenses = self._get_payment_records_sub()
deals_by_dates = self._apply_expenses(deals_by_dates, salary_expenses)
# Apply additional expenses
additional_expenses = self._get_additional_expenses_sub(filters.tag_id)
deals_by_dates = self._apply_expenses(deals_by_dates, additional_expenses)
deals_by_dates = self._apply_payments(deals_by_dates, salary_expenses)
return deals_by_dates

View File

@@ -10,7 +10,8 @@ from schemas.statistics import GetProfitChartDataResponse, GetProfitChartDataReq
GetProfitTableDataResponse, GetProfitTableDataRequest, ProfitTableDataItem, CommonProfitFilters
from services.base import BaseService
from services.statistics.common import generate_date_range
from services.statistics.expenses_statistics import ExpensesStatisticsService
from services.statistics.expenses_statistics import PaymentStatisticsService
from services.statistics.transactions_statistics import TransactionsStatisticsService
class ProfitStatisticsService(BaseService):
@@ -302,13 +303,18 @@ class ProfitStatisticsService(BaseService):
sub_grouped_by_deals = self._group_by_deals(sub_union)
sub_deals_grouped_by_date = self._group_by_date(sub_grouped_by_deals).subquery()
expenses_statistics_service = ExpensesStatisticsService(self.session)
stmt_deals_applied_expenses = expenses_statistics_service.apply_expenses(
expenses_statistics_service = PaymentStatisticsService(self.session)
stmt_deals_applied_expenses = expenses_statistics_service.apply_payments(
self.filters,
sub_deals_grouped_by_date
)
transactions_statistics_service = TransactionsStatisticsService(self.session)
stmt_deals_applied_transactions = transactions_statistics_service.apply_transactions(
self.filters,
stmt_deals_applied_expenses.subquery()
)
result = await self.session.execute(stmt_deals_applied_expenses)
result = await self.session.execute(stmt_deals_applied_transactions)
rows = result.all()
return rows

View File

@@ -0,0 +1,93 @@
from datetime import date
from sqlalchemy import select, func, Subquery, cast, CTE, case, or_, and_
from sqlalchemy.dialects.postgresql import TIMESTAMP
from models import Transaction, transactions_transaction_tags, TransactionTag
from schemas.statistics import CommonProfitFilters
from services.base import BaseService
from services.statistics.common import generate_date_range
class TransactionsStatisticsService(BaseService):
date_from: date
date_to: date
@staticmethod
def _fill_date_gaps(expenses: Subquery, all_dates: CTE) -> Subquery:
return (
select(
all_dates.c.date,
(all_dates.c.expenses + func.coalesce(expenses.c.expenses, 0)).label("expenses"),
(all_dates.c.revenue + func.coalesce(expenses.c.revenue, 0)).label("revenue"),
)
.join(expenses, all_dates.c.date == expenses.c.date, isouter=True)
.order_by(all_dates.c.date)
.subquery()
)
def _get_additional_transactions_sub(self, income_tag_id: int, expense_tag_id: int) -> Subquery:
all_dates = generate_date_range(self.date_from, self.date_to, ["expenses", "revenue"])
filtered_tags_sub = (
select(TransactionTag)
.where(
or_(
and_(TransactionTag.is_income == True,
or_(income_tag_id == -1, TransactionTag.id == income_tag_id)),
and_(TransactionTag.is_income == False,
or_(expense_tag_id == -1, TransactionTag.id == expense_tag_id)),
)
)
.subquery()
)
transaction_ids = (
select(transactions_transaction_tags.c.transaction_id)
.join(filtered_tags_sub, filtered_tags_sub.c.id == transactions_transaction_tags.c.transaction_tag_id)
.group_by(transactions_transaction_tags.c.transaction_id)
.subquery()
)
transactions = (
select(Transaction)
.join(transaction_ids, transaction_ids.c.transaction_id == Transaction.id)
.subquery()
)
transactions = (
select(
func.sum(case((transactions.c.is_income, transactions.c.amount), else_=0)).label("revenue"),
func.sum(case((~transactions.c.is_income, transactions.c.amount), else_=0)).label("expenses"),
cast(transactions.c.spent_date, TIMESTAMP(timezone=False)).label("date"),
)
.where(transactions.c.spent_date.between(self.date_from, self.date_to))
.group_by("date")
.subquery()
)
expenses_with_filled_gaps = self._fill_date_gaps(transactions, all_dates)
return expenses_with_filled_gaps
@staticmethod
def _apply_transactions(deals_by_dates: Subquery, transactions: Subquery):
return (
select(
deals_by_dates.c.date,
deals_by_dates.c.deals_count,
(deals_by_dates.c.revenue + transactions.c.revenue).label("revenue"),
(func.coalesce(deals_by_dates.c.profit, 0) - func.coalesce(transactions.c.expenses, 0) + func.coalesce(
transactions.c.revenue, 0))
.label("profit"),
(deals_by_dates.c.expenses + transactions.c.expenses).label("expenses"),
)
.join(transactions, transactions.c.date == deals_by_dates.c.date)
)
def apply_transactions(self, filters: CommonProfitFilters, deals_by_dates: Subquery):
self.date_from, self.date_to = filters.date_range
additional_expenses = self._get_additional_transactions_sub(filters.income_tag_id, filters.expense_tag_id)
deals_by_dates = self._apply_transactions(deals_by_dates, additional_expenses)
return deals_by_dates