115 lines
4.1 KiB
Python

from collections import defaultdict
from django.utils import timezone
from commerce.models import ExternalProject, ProjectPayment
from finance.analytics.models import ReportingEntry
from finance.analytics.sales import ZERO, bounds, bucket, finish, months
def payment_queryset(start, end, currency, source=None, project=None):
lower, upper = bounds(start, end)
qs = ProjectPayment.objects.filter(
currency=currency,
occurred_at__gte=lower,
occurred_at__lt=upper,
applicable=True,
missing_from_source=False,
).select_related("project", "source")
if source:
qs = qs.filter(source=source)
if project is not None:
qs = qs.filter(project_id=None if project == "unassigned" else project.pk)
return qs
def journal_queryset(start, end, currency, source, project):
entries = ReportingEntry.objects.filter(
date__range=(start, end),
currency=currency,
voided_at__isnull=True,
kind__in=["cash_out", "overhead", "purchase"],
).select_related("project")
if source:
entries = entries.filter(project__source=source)
if project is not None:
entries = entries.filter(
project_id=None if project == "unassigned" else project.pk
)
return entries
def projects_report(start, end, currency, sales, source=None, project=None):
rows = {}
def row_for(key, name):
if key not in rows:
rows[key] = {
**finish(bucket()),
"id": key,
"name": name,
"paid": ZERO,
"payment_count": 0,
"manual_paid": ZERO,
"overhead": ZERO,
"purchases": ZERO,
"archived": False,
}
return rows[key]
catalog = ExternalProject.objects.all()
if source:
catalog = catalog.filter(source=source)
if project is not None:
catalog = (
catalog.none() if project == "unassigned" else catalog.filter(pk=project.pk)
)
for p in catalog:
row_for(f"project:{p.pk}", p.name)["archived"] = p.archived
for sale_row in sales["projects"]:
row_for(sale_row["id"], sale_row["name"]).update(sale_row)
monthly = {
m: {"id": m, "name": m, "paid": ZERO, "manual_paid": ZERO}
for m in months(start, end)
}
categories = defaultdict(lambda: {"paid": ZERO, "payment_count": 0})
for p in payment_queryset(start, end, currency, source, project).iterator(
chunk_size=500
):
row = row_for(
f"project:{p.project_id}" if p.project_id else "unassigned",
p.project.name if p.project else "Без проекта",
)
row["paid"] += p.amount
row["payment_count"] += 1
monthly[timezone.localtime(p.occurred_at).strftime("%Y-%m")]["paid"] += p.amount
category = categories[p.expense_category or "Статья не указана"]
category["paid"] += p.amount
category["payment_count"] += 1
for entry in journal_queryset(start, end, currency, source, project):
row = row_for(
f"project:{entry.project_id}" if entry.project_id else "unassigned",
entry.project.name if entry.project else "Без проекта",
)
field = {
"cash_out": "manual_paid",
"overhead": "overhead",
"purchase": "purchases",
}[entry.kind]
row[field] += entry.amount
if entry.kind == "cash_out":
monthly[entry.date.strftime("%Y-%m")]["manual_paid"] += entry.amount
summary = {
field: sum((r[field] for r in rows.values()), ZERO)
for field in ["paid", "manual_paid", "overhead", "purchases", "payment_count"]
}
summary["unassigned_paid"] = rows.get("unassigned", {}).get("paid", ZERO)
summary["project_count"] = catalog.count()
return {
"summary": summary,
"rows": sorted(rows.values(), key=lambda r: (-r["paid"], r["name"])),
"monthly": list(monthly.values()),
"categories": [
{"id": k, "name": k, **v} for k, v in sorted(categories.items())
],
}