200 lines
6.3 KiB
Python

"""Read models used by the Finance Admin API."""
from decimal import Decimal
from django.contrib.contenttypes.models import ContentType
from django.db.models import Count, Max
from communications.models import Message
from finance.enums import (
ApprovalStatus,
InputSource,
PayrollComponentKind,
PayrollPeriodStatus,
)
from finance.models import (
PayrollCellCommentThread,
PayrollInputLine,
PayrollStatement,
)
from ..admin_serializers import (
PayrollCellCommentSerializer,
PayrollComponentSerializer,
PayrollEmployeeMiniSerializer,
PayrollUserRefSerializer,
)
def _cell_comment_summaries(period):
threads = list(
PayrollCellCommentThread.objects.filter(period=period).values(
"id",
"subject_employee_id",
"column_id",
)
)
if not threads:
return []
thread_by_id = {thread["id"]: thread for thread in threads}
content_type = ContentType.objects.get_for_model(PayrollCellCommentThread)
totals = (
Message.objects.filter(
chat__type="comments",
chat__context_content_type=content_type,
chat__context_object_id__in=thread_by_id,
is_deleted=False,
)
.values("chat__context_object_id")
.annotate(count=Count("id"), latest_at=Max("created_at"))
)
result = []
for total in totals:
thread = thread_by_id[int(total["chat__context_object_id"])]
result.append(
{
"employee_id": thread["subject_employee_id"],
"column_id": thread["column_id"],
"count": total["count"],
"latest_at": total["latest_at"],
}
)
return result
def _serialize_cell_comment(message, request):
return PayrollCellCommentSerializer(
{
"id": message.id,
"author": message.author,
"text": message.content,
"created_at": message.created_at,
},
context={"request": request},
).data
def _carryover_summary(period):
statements = (
PayrollStatement.objects.filter(
run=period.current_run,
)
if period.current_run_id
else PayrollStatement.objects.none()
)
return {
"outgoing": statements.exclude(closing_balance=0).count(),
"incoming": statements.exclude(opening_balance=0).count(),
"pending": 0,
}
def _automatic_balance_summaries(rows):
summaries = []
for row in rows:
balance = Decimal(row["closing_balance"])
if balance == 0:
continue
summaries.append(
{
"employee_id": row["employee"]["id"],
"count": 1,
"company_owes": str(max(balance, Decimal("0.00"))),
"employee_owes": str(max(-balance, Decimal("0.00"))),
"pending": 0,
}
)
return summaries
def _carryover_ledger(period):
lines = (
PayrollInputLine.objects.filter(
period__currency=period.currency,
period__date_to__lte=period.date_to,
component_code__in=("CORRECTION_CREDIT", "CORRECTION_DEBIT"),
)
.exclude(
source=InputSource.API,
source_ref__startswith="payroll-carryover:",
)
.select_related(
"period",
"employee",
"employee__position",
"created_by",
)
.prefetch_related("employee__departments_links__department")
.order_by("-period__date_to", "-created_at", "-id")
)
entries = []
balances_by_employee = {}
included_count = 0
excluded_count = 0
for line in lines:
current_period_line = line.period_id == period.pk
active_status = line.status == ApprovalStatus.APPROVED or (
current_period_line
and period.status != PayrollPeriodStatus.CLOSED
and line.status == ApprovalStatus.DRAFT
)
counts_in_balance = bool(line.include_in_carryover and active_status)
raw_contribution = (
line.amount
if line.component.kind == PayrollComponentKind.ADJUSTMENT_DEBIT
else -line.amount
)
contribution = raw_contribution if counts_in_balance else Decimal("0.00")
balances_by_employee[line.employee_id] = (
balances_by_employee.get(line.employee_id, Decimal("0.00")) + contribution
)
if line.include_in_carryover:
included_count += 1
else:
excluded_count += 1
entries.append(
{
"id": line.pk,
"period": {
"id": line.period_id,
"code": line.period.code,
"name": line.period.name,
"date_from": line.period.date_from,
"date_to": line.period.date_to,
"currency": line.period.currency,
},
"employee": PayrollEmployeeMiniSerializer(line.employee).data,
"component": PayrollComponentSerializer(line.component).data,
"amount": str(line.amount),
"raw_contribution": str(raw_contribution),
"contribution": str(contribution),
"reason": line.reason,
"status": line.status,
"status_label": line.get_status_display(),
"include_in_carryover": line.include_in_carryover,
"counts_in_balance": counts_in_balance,
"created_by": PayrollUserRefSerializer(line.created_by).data,
"created_at": line.created_at,
}
)
company_owes = sum(
(max(balance, Decimal("0.00")) for balance in balances_by_employee.values()),
Decimal("0.00"),
)
employee_owes = sum(
(max(-balance, Decimal("0.00")) for balance in balances_by_employee.values()),
Decimal("0.00"),
)
return {
"period_id": period.pk,
"currency": period.currency,
"summary": {
"included_count": included_count,
"excluded_count": excluded_count,
"company_owes": str(company_owes),
"employee_owes": str(employee_owes),
"balance": str(company_owes - employee_owes),
},
"entries": entries,
}