115 lines
4.1 KiB
Python
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())
|
|
],
|
|
}
|