From 3105eeb0b7eedf45b67ef05d10a3c082742c2a48 Mon Sep 17 00:00:00 2001 From: Denislav Davidov Date: Tue, 18 Feb 2025 11:48:15 +0000 Subject: [PATCH] initial commit adding service function fixing sort order adding error handleing fixing error handler adding basic tests --- payroll/api.py | 46 +++++++++++++++++++ ...ayperiods_notes_vacancypayperiods_notes.py | 23 ++++++++++ payroll/models.py | 1 + payroll/services/payroll.py | 24 +++++++++- payroll/tests/services/test_payroll.py | 30 ++++++++++++ payroll/urls.py | 12 ++++- 6 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 payroll/migrations/0018_employeepayperiods_notes_vacancypayperiods_notes.py diff --git a/payroll/api.py b/payroll/api.py index 7a102809..5107f69e 100644 --- a/payroll/api.py +++ b/payroll/api.py @@ -1,6 +1,7 @@ import json import waffle +from django.core.exceptions import ValidationError from django.http import JsonResponse from config import flags @@ -12,12 +13,14 @@ class EditPayrollApiView(EditPayrollBaseView): def get(self, request, *args, **kwargs): + employees = list( payroll_service.get_employee_data( self.cost_centre, self.financial_year, ) ) + vacancies = list( payroll_service.get_vacancies_data( self.cost_centre, @@ -86,3 +89,46 @@ def post(self, request, *args, **kwargs): ) return JsonResponse({}) + + +class EmployeeNotesApi(EditPayrollBaseView): + def post(self, request, *args, **kwargs): + try: + data = json.loads(request.body) + if not data: + return JsonResponse({"error": "Missing request body"}, status=400) + notes = data.get("notes") + employee_no = data.get("employee_no") + + if not notes or not employee_no: + return JsonResponse( + {"error": "Both 'notes' and 'employee_no' are required"}, status=400 + ) + employee_data = payroll_service.get_employee_data( + self.cost_centre, + self.financial_year, + ) + employee = next( + ( + item + for item in employee_data + if str(item["employee_no"]) == employee_no + ), + None, + ) + if employee: + payroll_service.update_employee_notes( + notes, + employee_no, + self.cost_centre, + self.financial_year, + ) + return JsonResponse({}, status=204) + except json.JSONDecodeError: + return JsonResponse({"error": "Invalid JSON format"}, status=400) + except ValidationError: + return JsonResponse({"error": "Invalid data provided"}, status=400) + except Exception: + return JsonResponse( + {"error": "An error occurred while processing the request"}, status=500 + ) diff --git a/payroll/migrations/0018_employeepayperiods_notes_vacancypayperiods_notes.py b/payroll/migrations/0018_employeepayperiods_notes_vacancypayperiods_notes.py new file mode 100644 index 00000000..5f876616 --- /dev/null +++ b/payroll/migrations/0018_employeepayperiods_notes_vacancypayperiods_notes.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.5 on 2025-02-17 11:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("payroll", "0017_remove_payelementtype_group_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="employeepayperiods", + name="notes", + field=models.TextField(default=""), + ), + migrations.AddField( + model_name="vacancypayperiods", + name="notes", + field=models.TextField(default=""), + ), + ] diff --git a/payroll/models.py b/payroll/models.py index 5828ec82..545e95f0 100644 --- a/payroll/models.py +++ b/payroll/models.py @@ -74,6 +74,7 @@ class Meta: period_10 = models.BooleanField(default=True) period_11 = models.BooleanField(default=True) period_12 = models.BooleanField(default=True) + notes = models.TextField(default="") @property def periods(self) -> list[bool]: diff --git a/payroll/services/payroll.py b/payroll/services/payroll.py index a5716e4e..d7213cd5 100644 --- a/payroll/services/payroll.py +++ b/payroll/services/payroll.py @@ -282,6 +282,7 @@ def get_employee_data( basic_pay=obj.basic_pay, # `first` is OK as there should only be one `pay_periods` with the filters. pay_periods=obj.pay_periods.first().periods, + notes=obj.pay_periods.first().notes, ) @@ -320,6 +321,23 @@ def update_employee_data( pay_periods.save() +def update_employee_notes( + notes: str, + employee_no: str, + cost_centre: CostCentre, + financial_year: FinancialYear, +) -> None: + if not employee_no: + raise ValueError("employee_no is empty") + pay_period = EmployeePayPeriods.objects.get( + employee__employee_no=employee_no, + employee__cost_centre=cost_centre, + year=financial_year, + ) + pay_period.notes = notes + pay_period.save() + + class Vacancies(TypedDict): id: int grade: str @@ -331,6 +349,7 @@ class Vacancies(TypedDict): hiring_manager: str hr_ref: str pay_periods: list[bool] + notes: str def get_vacancies_data( @@ -352,7 +371,7 @@ def get_vacancies_data( ) for obj in qs: budget_type = obj.programme_code.budget_type - + pay_periods = (obj.pay_periods.first(),) yield Vacancies( id=obj.pk, grade=obj.grade.pk, @@ -364,7 +383,8 @@ def get_vacancies_data( hiring_manager=obj.hiring_manager, hr_ref=obj.hr_ref, # `first` is OK as there should only be one `pay_periods` with the filters. - pay_periods=obj.pay_periods.first().periods, + pay_periods=pay_periods.periods, + notes=pay_periods.notes, ) diff --git a/payroll/tests/services/test_payroll.py b/payroll/tests/services/test_payroll.py index df0f869a..50abf61b 100644 --- a/payroll/tests/services/test_payroll.py +++ b/payroll/tests/services/test_payroll.py @@ -1,5 +1,6 @@ from random import randrange from statistics import mean +import json import pytest from pytest_django.asserts import assertNumQueries @@ -315,3 +316,32 @@ def test_update_all_employee_pay_periods(db): # then there are 2 pay periods assert EmployeePayPeriods.objects.count() == 2 + +def update_notes_success(self, db, client): + url = "http://localhost:8000/payroll/api/888813/2024/employees/notes" + response = client.post( + url, + data=json.dumps( + { + "notes": "some notes", + "employee_no": "150892", + } + ), + content_type="application/json", + ) + + assert response.status_code == 200 + +def update_notes_fail(self, db, client): + url = "http://localhost:8000/payroll/api/888813/2024/employees/notes" + response = client.post( + url, + data=json.dumps( + { + "notes": "some notes" + } + ), + content_type="application/json", + ) + + assert response.status_code == 400 diff --git a/payroll/urls.py b/payroll/urls.py index ad52b648..b7977244 100644 --- a/payroll/urls.py +++ b/payroll/urls.py @@ -1,7 +1,7 @@ from django.urls import path from forecast.views.edit_select_cost_centre import ChooseCostCentreView -from payroll.api import EditPayrollApiView, PayModifiersApiView +from payroll.api import EditPayrollApiView, EmployeeNotesApi, PayModifiersApiView from . import views @@ -19,6 +19,16 @@ EditPayrollApiView.as_view(), name="api", ), + path( + "api///vacancies/notes", + EmployeeNotesApi.as_view(), + name="vacancy_notes", + ), + path( + "api///employees/notes", + EmployeeNotesApi.as_view(), + name="employee_notes", + ), path( "api///pay_modifiers/", PayModifiersApiView.as_view(),