"""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, }