200 lines
6.3 KiB
Python
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,
|
|
}
|