From 53473e0bbc02cfb439903fd995c3f5954263cc47 Mon Sep 17 00:00:00 2001 From: Heibert Date: Tue, 5 Nov 2024 14:53:01 -0500 Subject: [PATCH] Vacation production version (fix) --- INSIGHTSAPI/payslip/templates/payslip.html | 2 +- ...22_alter_vacationrequest_sat_is_working.py | 18 ++ INSIGHTSAPI/vacation/models.py | 6 +- INSIGHTSAPI/vacation/serializers.py | 28 ++- .../vacation/templates/vacation_request.html | 141 ++++++----- .../vacation/templates/vacation_response.html | 226 ++++++++---------- INSIGHTSAPI/vacation/tests.py | 48 ++-- INSIGHTSAPI/vacation/utils.py | 9 +- 8 files changed, 257 insertions(+), 221 deletions(-) create mode 100644 INSIGHTSAPI/vacation/migrations/0022_alter_vacationrequest_sat_is_working.py diff --git a/INSIGHTSAPI/payslip/templates/payslip.html b/INSIGHTSAPI/payslip/templates/payslip.html index a42dc3e..f3560cd 100644 --- a/INSIGHTSAPI/payslip/templates/payslip.html +++ b/INSIGHTSAPI/payslip/templates/payslip.html @@ -82,7 +82,7 @@ alt="logo-cyc" src="data:image/png;base64,{{ logo }}" width="200px" - /> + /> diff --git a/INSIGHTSAPI/vacation/migrations/0022_alter_vacationrequest_sat_is_working.py b/INSIGHTSAPI/vacation/migrations/0022_alter_vacationrequest_sat_is_working.py new file mode 100644 index 0000000..60c6971 --- /dev/null +++ b/INSIGHTSAPI/vacation/migrations/0022_alter_vacationrequest_sat_is_working.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.8 on 2024-11-05 11:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('vacation', '0021_vacationrequest_user_job_position'), + ] + + operations = [ + migrations.AlterField( + model_name='vacationrequest', + name='sat_is_working', + field=models.BooleanField(), + ), + ] diff --git a/INSIGHTSAPI/vacation/models.py b/INSIGHTSAPI/vacation/models.py index 0a1f29d..a8906f1 100644 --- a/INSIGHTSAPI/vacation/models.py +++ b/INSIGHTSAPI/vacation/models.py @@ -9,7 +9,7 @@ from notifications.utils import create_notification from users.models import User -from vacation.utils import get_return_date +from vacation.utils import get_return_date, get_working_days class VacationRequest(models.Model): @@ -20,7 +20,7 @@ class VacationRequest(models.Model): ) start_date = models.DateField() end_date = models.DateField() - sat_is_working = models.BooleanField(default=True) + sat_is_working = models.BooleanField() boss_is_approved = models.BooleanField(null=True, blank=True) boss_approved_at = models.DateTimeField(null=True, blank=True) manager_is_approved = models.BooleanField(null=True, blank=True) @@ -58,7 +58,7 @@ class Meta: @property def duration(self): """Return the duration of the vacation request.""" - return (self.end_date - self.start_date).days + return get_working_days(self.start_date, self.end_date, self.sat_is_working) @property def return_date(self): diff --git a/INSIGHTSAPI/vacation/serializers.py b/INSIGHTSAPI/vacation/serializers.py index d553894..c598a63 100644 --- a/INSIGHTSAPI/vacation/serializers.py +++ b/INSIGHTSAPI/vacation/serializers.py @@ -5,6 +5,8 @@ from rest_framework import serializers +from hierarchy.models import JobPosition + from .models import VacationRequest from .utils import get_working_days, is_working_day @@ -32,14 +34,19 @@ class Meta: "hr_approved_at", "payroll_is_approved", "payroll_approved_at", + "sat_is_working", "status", "comment", + "user_job_position", ] read_only_fields = [ + "boss_approved_at", "manager_approved_at", "hr_approved_at", "payroll_approved_at", "created_at", + "user", + "user_job_position", ] def to_representation(self, instance): @@ -50,6 +57,7 @@ def to_representation(self, instance): data.pop("manager_approved_at") data.pop("hr_approved_at") data.pop("payroll_approved_at") + data.pop("user_job_position") return data def validate(self, attrs): @@ -59,32 +67,32 @@ def validate(self, attrs): # Creation created_at = datetime.now() request = self.context["request"] - if request.data.get("mon_to_sat") is None: + if request.data.get("sat_is_working") is None: raise serializers.ValidationError( "Debes especificar si trabajas los sábados." ) else: try: - mon_to_sat = bool(strtobool(request.data["mon_to_sat"])) + sat_is_working = bool(strtobool(request.data["sat_is_working"])) except ValueError: raise serializers.ValidationError( "Debes especificar si trabajas los sábados o no." ) - if not is_working_day(attrs["start_date"], mon_to_sat): + if not is_working_day(attrs["start_date"], sat_is_working): raise serializers.ValidationError( "No puedes iniciar tus vacaciones un día no laboral." ) - if not is_working_day(attrs["end_date"], mon_to_sat): + if not is_working_day(attrs["end_date"], sat_is_working): raise serializers.ValidationError( "No puedes terminar tus vacaciones un día no laboral." ) - if request.data["mon_to_sat"] == True: + if request.data["sat_is_working"] == True: if attrs["start_date"].weekday() == 5: raise serializers.ValidationError( "No puedes iniciar tus vacaciones un sábado." ) if ( - get_working_days(attrs["start_date"], attrs["end_date"], mon_to_sat) + get_working_days(attrs["start_date"], attrs["end_date"], sat_is_working) > 15 ): raise serializers.ValidationError( @@ -126,11 +134,17 @@ def validate(self, attrs): def create(self, validated_data): """Create the vacation request.""" - # Remove the is_approved fields from the validated data + # Remove the is_approved fields from the validated data (security check) validated_data.pop("boss_is_approved", None) validated_data.pop("manager_is_approved", None) validated_data.pop("hr_is_approved", None) validated_data.pop("payroll_is_approved", None) + # Add the user job position to the validated data + job_position = JobPosition.objects.get( + id=validated_data["user"].job_position_id + ) + validated_data["user_job_position"] = job_position + # Create the vacation request vacation_request = super().create(validated_data) return vacation_request diff --git a/INSIGHTSAPI/vacation/templates/vacation_request.html b/INSIGHTSAPI/vacation/templates/vacation_request.html index b351acd..8fa1909 100644 --- a/INSIGHTSAPI/vacation/templates/vacation_request.html +++ b/INSIGHTSAPI/vacation/templates/vacation_request.html @@ -1,67 +1,94 @@ - - - - Solicitud de Vacaciones - - - -
-

Fecha de envío de la solicitud: {{ fecha_solicitud }}

-
-
-

Estimado(a) {{ nombre_destinatario }},

+ + + + Solicitud de Vacaciones + + + + + Logo de la empresa C&C SERVICES S.A.S + +
+

{{ vacation.created_at|date }}

+
+
+

+ Por la presente yo, {{ vacation.user }}, identificado(a) con la cédula de ciudadanía + {{ vacation.user.cedula }}, y desempeñando + el cargo de {{ vacation.user_job_position }} en la empresa, me permito solicitar {{ vacation.duration }} días de vacaciones. +

+ +

+ Los días solicitados serán a partir del {{ vacation.start_date }} hasta el {{ vacation.end_date }}, y retomaré mis labores el + {{ vacation.return_date }}. +

+ +

Notifico con antelación mi solicitud para que se realicen los ajustes o acuerdos necesarios para el normal + funcionamiento de las actividades en mi ausencia.

+ +

Agradezco de antemano su pronta respuesta.

-

Atentamente:

-
-

{{ nombre_solicitante }}

-

C.C {{ numero_cedula }}

+

Atentamente,

+

{{ vacation.user }}

+

C.C {{ vacation.user.cedula }}

- - +
+ + + \ No newline at end of file diff --git a/INSIGHTSAPI/vacation/templates/vacation_response.html b/INSIGHTSAPI/vacation/templates/vacation_response.html index aa6b93e..1bd75b4 100644 --- a/INSIGHTSAPI/vacation/templates/vacation_response.html +++ b/INSIGHTSAPI/vacation/templates/vacation_response.html @@ -1,143 +1,107 @@ - + - - - - Respuesta a Solicitud de Vacaciones - + - .date { - color: #7f8c8d; - font-style: italic; - text-align: right; - margin-bottom: 20px; - } + + Logo de la empresa C&C SERVICES S.A.S - .highlight { - color: #3498db; - font-weight: bold; - } +
+

+ Bogotá D.C.; {% if vacation.payroll_approved_at %} {{ vacation.payroll_approved_at|date:"d \d\e F \d\e Y" }} {% elif vacation.hr_approved_at %} + {{vacation.hr_approved_at|date:"d \d\e F \d\e Y" }} {% elif vacation.manager_approved_at %} {{vacation.manager_approved_at|date:"d \d\e F \d\e Y" }} {% elif + vacation.boss_approved_at %} {{ vacation.boss_approved_at|date:"d \d\e F \d\e Y" }} {% else %} No approval date available {% endif %} +

+
- .signature { - margin-top: 40px; - } +
+

Señor/a:

+

{{vacation.user}}

+

{{vacation.user_job_position}} - Bogotá

+

De. Gestión Humana

+

+ REF: aprobación de vacaciones mes de {{ vacation.start_date|date:"F" }} de {{ vacation.start_date|date:"Y" }} +

- .footer { - margin-top: 30px; - text-align: center; - } + {% if vacation.status == 'APROBADA' %} +

+ Me permito informarle que en respuesta a su comunicación recibida el día {{ vacation.created_at|date }}, usted podrá disfrutar de sus vacaciones + a partir del día {{ vacation.start_date }}, retomando actividades laborales el día {{ vacation.return_date }}. +

+

Espero disfrute con agrado sus merecidas vacaciones.

+ {% else %} +

+ Lamentamos informarle que su solicitud de vacaciones recibida el día {{ vacation.created_at|date }} no ha sido aprobada debido al siguiente motivo: +

+

{{ vacation.comment }}.

+ {% endif %} - {% comment %} @media print { - html, body { - height: auto; /* Reset height */ - display: block; /* Reset the flexbox layout */ - padding: 0; /* Adjust padding as necessary */ - } - .card { - margin: 0; - padding: 20px; - width: 100%; /* Ensure the card takes up the full width */ - height: auto; /* Reset height for better PDF rendering */ - background-color: #ffffff; - } - } {% endcomment %} - - - - - -
-
-

Respuesta a Solicitud de Vacaciones

-
-
-
- - - - Solicitud Aprobada -
-

{{ current_date }}

-

Estimado(a) {{ vacation.user }},

-

- Nos complace informarle que su solicitud de vacaciones realizada el {{ vacation.created_at|date }} - ha sido aprobada. Los detalles de su periodo vacacional son los siguientes: -

-
    -
  • Fecha de inicio: {{ vacation.start_date }}
  • -
  • Fecha de finalización: {{ vacation.end_date }}
  • -
  • Fecha de reincorporación: {{ vacation.return_date }}
  • -
  • Dias utilizados: {{ vacation.duration }}
  • -
-

- Por favor, asegúrese de completar todas las tareas pendientes y de informar a su equipo sobre su ausencia. Si tiene alguna pregunta o necesita realizar algún cambio, no dude en contactarnos. -

-

- Le deseamos unas excelentes vacaciones. -

-
-

- JEANNETH PINZON MARTINEZ
- Gerente Gestión Humana
- CYC SERVICES S.A.S.
-

-
-
-
- - \ No newline at end of file +
+

+ JEANNETH PINZON MARTINEZ
+ Gerente Gestión Humana
+ C&C SERVICES S.A.S.
+

+
+
+ + diff --git a/INSIGHTSAPI/vacation/tests.py b/INSIGHTSAPI/vacation/tests.py index e926bcf..095481b 100644 --- a/INSIGHTSAPI/vacation/tests.py +++ b/INSIGHTSAPI/vacation/tests.py @@ -23,10 +23,10 @@ class WorkingDayTestCase(TestCase): def test_is_working_day(self): """Test the is_working_day function.""" - self.assertTrue(is_working_day("2024-01-02")) - self.assertFalse(is_working_day("2024-01-01")) - self.assertTrue(is_working_day("2024-01-05")) - self.assertTrue(is_working_day("2024-01-06")) + self.assertTrue(is_working_day("2024-01-02", True)) + self.assertFalse(is_working_day("2024-01-01", True)) + self.assertTrue(is_working_day("2024-01-05", True)) + self.assertTrue(is_working_day("2024-01-06", True)) self.assertFalse(is_working_day("2024-01-06", False)) def test_get_working_days_no_sat(self): @@ -36,6 +36,7 @@ def test_get_working_days_no_sat(self): # The 8th is a holiday self.assertEqual(get_working_days("2024-01-01", "2024-01-09", False), 5) self.assertEqual(get_working_days("2024-01-01", "2024-01-23", False), 15) + self.assertEqual(get_working_days("2024-12-09", "2024-12-27", False), 14) def test_get_working_days_sat(self): """Test the get_working_days function with Saturdays.""" @@ -74,22 +75,33 @@ def setUp(self): "start_date": "2024-01-02", "end_date": "2024-01-18", "user": self.test_user, + "user_job_position": self.test_user.job_position, + "sat_is_working": True, } def test_vacation_create(self): """Test creating a vacation endpoint.""" self.vacation_request["hr_is_approved"] = True # This is just a check - self.vacation_request["mon_to_sat"] = False + self.vacation_request["sat_is_working"] = False response = self.client.post( reverse("vacation-list"), self.vacation_request, ) self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.data) self.assertEqual(response.data["hr_is_approved"], None) - - def test_vacation_create_no_mon_to_sat(self): - """Test creating a vacation without mon_to_sat.""" + self.assertEqual(response.data["status"], "PENDIENTE") + self.assertEqual(response.data["user_id"], self.user.id) + self.assertEqual(response.data.get("user_job_position"), None) + self.assertEqual(response.data["start_date"], "2024-01-02") + self.assertEqual(response.data["end_date"], "2024-01-18") + vacation = VacationRequest.objects.get(pk=response.data["id"]) + self.assertEqual(vacation.user_job_position, self.user.job_position) + self.assertEqual(vacation.sat_is_working, False) + self.assertEqual(vacation.duration, 12) + + def test_vacation_create_no_sat_is_working(self): + """Test creating a vacation without sat_is_working.""" response = self.client.post( reverse("vacation-list"), self.vacation_request, @@ -106,7 +118,7 @@ def test_vacation_create_no_mon_to_sat(self): def test_vacation_create_same_month(self): """Test creating a vacation that spans two months.""" super().setUp() - self.vacation_request["mon_to_sat"] = False + self.vacation_request["sat_is_working"] = False self.vacation_request["start_date"] = "2024-07-22" response = self.client.post( reverse("vacation-list"), @@ -204,7 +216,7 @@ def test_vacation_retrieve(self): def test_vacation_create_end_before_start(self): """Test creating a vacation with the end date before the start date.""" self.vacation_request["end_date"] = "2021-01-04" - self.vacation_request["mon_to_sat"] = False + self.vacation_request["sat_is_working"] = False response = self.client.post( reverse("vacation-list"), self.vacation_request, @@ -414,7 +426,7 @@ def test_vacation_payroll_approve_no_payroll(self): def test_validate_vacation_request_after_20th(self): """Test the validation of a vacation request after the 20th.""" super().setUp() - self.vacation_request["mon_to_sat"] = False + self.vacation_request["sat_is_working"] = False self.vacation_request["start_date"] = "2024-08-12" response = self.client.post( reverse("vacation-list"), @@ -430,7 +442,7 @@ def test_validate_vacation_request_after_20th(self): def test_validate_vacation_request_not_working_day(self): """Test the validation of a vacation request on a non-working day.""" - self.vacation_request["mon_to_sat"] = False + self.vacation_request["sat_is_working"] = False self.vacation_request["start_date"] = "2024-01-01" response = self.client.post( reverse("vacation-list"), @@ -446,7 +458,7 @@ def test_validate_vacation_request_not_working_day(self): def test_validate_vacation_request_not_working_day_sat(self): """Test the validation of a vacation request on a Saturday.""" - self.vacation_request["mon_to_sat"] = False + self.vacation_request["sat_is_working"] = False self.vacation_request["start_date"] = "2024-05-04" response = self.client.post( reverse("vacation-list"), @@ -463,7 +475,7 @@ def test_validate_vacation_request_not_working_day_sat(self): def test_validate_vacation_request_not_working_day_sat_working(self): """Test the validation of a vacation request on a Saturday with working Saturdays.""" - self.vacation_request["mon_to_sat"] = True + self.vacation_request["sat_is_working"] = True self.vacation_request["start_date"] = "2024-05-04" self.vacation_request["end_date"] = "2024-05-06" response = self.client.post( @@ -474,7 +486,7 @@ def test_validate_vacation_request_not_working_day_sat_working(self): def test_validate_vacation_request_not_working_day_end(self): """Test the validation of a vacation request on a non-working day.""" - self.vacation_request["mon_to_sat"] = False + self.vacation_request["sat_is_working"] = False self.vacation_request["end_date"] = "2024-01-01" response = self.client.post( reverse("vacation-list"), @@ -490,7 +502,7 @@ def test_validate_vacation_request_not_working_day_end(self): def test_validate_vacation_request_not_working_day_sat_end(self): """Test the validation of a vacation request on a Saturday.""" - self.vacation_request["mon_to_sat"] = False + self.vacation_request["sat_is_working"] = False self.vacation_request["end_date"] = "2024-05-04" response = self.client.post( reverse("vacation-list"), @@ -507,7 +519,7 @@ def test_validate_vacation_request_not_working_day_sat_end(self): def test_validate_vacation_request_not_working_day_sat_working_end(self): """Test the validation of a vacation request on a Saturday with working Saturdays.""" - self.vacation_request["mon_to_sat"] = True + self.vacation_request["sat_is_working"] = True self.vacation_request["start_date"] = "2024-05-03" self.vacation_request["end_date"] = "2024-05-04" response = self.client.post( @@ -518,7 +530,7 @@ def test_validate_vacation_request_not_working_day_sat_working_end(self): def test_validate_vacation_request_more_than_15_days(self): """Test the validation of a vacation request with more than 15 days.""" - self.vacation_request["mon_to_sat"] = False + self.vacation_request["sat_is_working"] = False self.vacation_request["end_date"] = "2024-01-24" response = self.client.post( reverse("vacation-list"), diff --git a/INSIGHTSAPI/vacation/utils.py b/INSIGHTSAPI/vacation/utils.py index 1de2d2e..33684f6 100644 --- a/INSIGHTSAPI/vacation/utils.py +++ b/INSIGHTSAPI/vacation/utils.py @@ -1,11 +1,12 @@ """Utility functions for the vacation app.""" -import holidays from datetime import datetime, timedelta from distutils.util import strtobool +import holidays + -def is_working_day(date, sat_is_working=True): +def is_working_day(date, sat_is_working): """Check if a date is a working day.""" if isinstance(sat_is_working, str): sat_is_working = bool(strtobool(sat_is_working)) @@ -21,7 +22,7 @@ def is_working_day(date, sat_is_working=True): return True -def get_working_days(start_date, end_date, sat_is_working=True): +def get_working_days(start_date, end_date, sat_is_working: bool): """Get the number of working days between two dates.""" working_days = 0 if isinstance(sat_is_working, str): @@ -36,7 +37,7 @@ def get_working_days(start_date, end_date, sat_is_working=True): return working_days -def get_return_date(end_date, sat_is_working=True) -> datetime: +def get_return_date(end_date, sat_is_working) -> datetime: """Get the return date of a vacation request.""" if isinstance(sat_is_working, str): sat_is_working = bool(strtobool(sat_is_working))