From 111c51a07295b2842fb8f3b980c614b8a2979a2b Mon Sep 17 00:00:00 2001 From: bartool Date: Mon, 25 Nov 2024 19:11:28 +0000 Subject: [PATCH] odoo_hours setup and ready --- .gitignore | 3 +- app/bartool_ovh/settings.py | 18 ++- app/bartool_ovh/urls.py | 4 +- app/homepage/templates/homepage.html | 2 +- app/odoo_hours/templates/calendar.html | 104 +++++++++++++++ app/odoo_hours/templates/hours_form.html | 54 ++++++++ app/odoo_hours/text_conv.py | 157 +++++++++++++++++++++++ app/odoo_hours/views.py | 22 +++- 8 files changed, 353 insertions(+), 11 deletions(-) create mode 100644 app/odoo_hours/templates/calendar.html create mode 100644 app/odoo_hours/templates/hours_form.html create mode 100644 app/odoo_hours/text_conv.py diff --git a/.gitignore b/.gitignore index 01773fd..d12fb43 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .venv __pycache__ db.sqlite3 -env.* \ No newline at end of file +env.* +.env \ No newline at end of file diff --git a/app/bartool_ovh/settings.py b/app/bartool_ovh/settings.py index 3d7f5f1..bb800c4 100644 --- a/app/bartool_ovh/settings.py +++ b/app/bartool_ovh/settings.py @@ -12,6 +12,9 @@ https://docs.djangoproject.com/en/5.1/ref/settings/ from pathlib import Path import os +from dotenv import load_dotenv + +load_dotenv() from django.core.management.commands.runserver import Command as runserver runserver.default_port = "7100" @@ -25,15 +28,15 @@ BASE_DIR = Path(__file__).resolve().parent.parent # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-snqnsca90!r=^iu6yyhpxy^+mjm%7dvrg(lb!6fr3(!9yh(c30' -# SECRET_KEY = os.environ.get("SECRET_KEY") +# SECRET_KEY = 'django-insecure-snqnsca90!r=^iu6yyhpxy^+mjm%7dvrg(lb!6fr3(!9yh(c30' +SECRET_KEY = os.getenv("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True -# DEBUG = bool(os.environ.get("DEBUG", default=0)) +# DEBUG = True +DEBUG = bool(os.getenv("DEBUG", default=0)) -ALLOWED_HOSTS = ['192.168.1.146'] -# ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ") +# ALLOWED_HOSTS = ['192.168.1.146'] +ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS").split(" ") # Application definition @@ -46,7 +49,8 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', - 'homepage' + 'homepage', + 'odoo_hours' ] MIDDLEWARE = [ diff --git a/app/bartool_ovh/urls.py b/app/bartool_ovh/urls.py index f61c5f2..cad2f3a 100644 --- a/app/bartool_ovh/urls.py +++ b/app/bartool_ovh/urls.py @@ -18,8 +18,10 @@ from django.contrib import admin from django.urls import path from homepage import views as homepage +from odoo_hours import views as odoo_hours urlpatterns = [ path('admin/', admin.site.urls), - path('', homepage.index, name="homepage") + path('', homepage.index, name="homepage"), + path("odoo-hours/", odoo_hours.index, name="odoo-hours"), ] diff --git a/app/homepage/templates/homepage.html b/app/homepage/templates/homepage.html index 8d5732c..0f7fea6 100644 --- a/app/homepage/templates/homepage.html +++ b/app/homepage/templates/homepage.html @@ -23,7 +23,7 @@
Odoo godziny

Oblicz godziny pracy na podstawie tabeli odoo.

- ENTER + ENTER diff --git a/app/odoo_hours/templates/calendar.html b/app/odoo_hours/templates/calendar.html new file mode 100644 index 0000000..b88cf93 --- /dev/null +++ b/app/odoo_hours/templates/calendar.html @@ -0,0 +1,104 @@ + + + + + + Kalendarz pracy + + + + +
+

Kalendarz pracy

+
+ {% for _ in empty_days %} +
+ {% comment %} +
+
X
+
+
+ {% endcomment %} +
+ {% endfor %} {% for day in days %} +
+
+ {% if day.weekday == 5 %} +
+
{{ day.day}}
+
+ {% elif day.weekday == 6 %} +
+
{{ day.day}}
+
+ {% elif day.is_holiday %} +
+
{{ day.day}}
+
+ {% else %} +
+
{{ day.day}}
+
+ {% endif %} +
+ {% if day.is_holiday == False and day.weekday < 5 %} +
+ + {{ day.daily_diffrance}} + +
+
+

{{ day.daily_actual}}

+
+
+
+ Expected: + {{day.total_expected}} + Actual: + {{day.total_actual}} +
+
+ {% endif %} +
+ {% if '-' in day.total_diffrance %} + + {% else %} + + {% endif %} +
+
+ {% endfor %} +
+
+ + diff --git a/app/odoo_hours/templates/hours_form.html b/app/odoo_hours/templates/hours_form.html new file mode 100644 index 0000000..a94de4a --- /dev/null +++ b/app/odoo_hours/templates/hours_form.html @@ -0,0 +1,54 @@ + + + + + + + Odoo Hours + + +
+
+
+

Przetwarzanie tekstu

+
+ {% csrf_token %} +
+ + +
Pole tekstowe nie może być puste.
+
+
+ +
+
+ + {% if error %} +
+

Error:

+

{{ error }}

+
+ {% endif %} +
+
+
+ + + + diff --git a/app/odoo_hours/text_conv.py b/app/odoo_hours/text_conv.py new file mode 100644 index 0000000..80af933 --- /dev/null +++ b/app/odoo_hours/text_conv.py @@ -0,0 +1,157 @@ +import re +from datetime import datetime, timedelta +import holidays +import calendar +import json + +def timedelta_to_string(td: timedelta) -> str: + """Convert a timedelta object to a string in the format HH:MM.""" + + total_minutes = int(td.total_seconds() / 60) + hours = abs(total_minutes) // 60 + minutes = abs(total_minutes) % 60 + sign = "-" if td.total_seconds() < 0 else "" + + return f"{sign}{hours:02}:{minutes:02}" + +def process_input_txt(text: str) -> list: + """ + Process text containing work records and return a sorted list of dictionaries + containing the extracted data. + + Args: + text (str): Text containing work records. + + Returns: + list[dict]: List of dictionaries containing the fields 'enter', 'exit', and 'duration'. + """ + pattern = re.compile( + r""" + (?P\S+\s\S+)\s+ # Name + (?P\d{2}\.\d{2}\.\d{4}\s\d{2}:\d{2}:\d{2})\s+ # Start date + (?P\d{2}\.\d{2}\.\d{4}\s\d{2}:\d{2}:\d{2})\s+ # End date + (?P\d{2}:\d{2}) # Duration + """, + re.VERBOSE, + ) + + results = [] + for line in text.splitlines(): + line = line.strip() + if not line: + continue + match = pattern.match(line) + if match: + try: + start_date = datetime.strptime(match.group("start_date"), "%d.%m.%Y %H:%M:%S") + end_date = datetime.strptime(match.group("end_date"), "%d.%m.%Y %H:%M:%S") + duration = timedelta( + hours=int(match.group("duration")[:2]), minutes=int(match.group("duration")[3:]) + ) + results.append( + { + "enter": start_date, + "exit": end_date, + "duration": duration, + } + ) + except ValueError: + # Ignore invalid dates + continue + + # Sort results by start date + results.sort(key=lambda x: x["enter"]) + return results + + +def generate_monthly_summary(work_records: list, year: int, month: int) -> list: + # Polska lista dni świątecznych + pl_holidays = holidays.Poland(years=year) + + # Liczba dni w miesiącu + _, num_days = calendar.monthrange(year, month) + summary = [] + + # Oblicz godziny pracy na dzień (8h 15min = 8.25h) + daily_work_hours = timedelta(hours=8, minutes=15) + holiday_work_hours = timedelta(hours=0, minutes=15) + + # Suma godzin oczekiwanych i rzeczywistych + total_expected = timedelta(0) + total_actual = timedelta(0) + + # Tworzenie wpisów dla każdego dnia + for day in range(1, num_days + 1): + date = datetime(year, month, day) + weekday = date.weekday() # 0 = poniedziałek, ..., 6 = niedziela + is_holiday = date in pl_holidays + should_work = weekday < 5 and not is_holiday # Praca w dni robocze poza świętami + + # Aktualizuj oczekiwany czas pracy + if should_work: + total_expected += daily_work_hours + + # Znajdź godziny faktyczne dla tego dnia + daily_actual = sum( + (record["duration"] + for record in work_records + if record["enter"].date() == date.date()) , timedelta() + ) + total_actual += daily_actual # Dodanie timedelta do sumy + + # Dodaj dane do listy podsumowania + summary.append({ + "day": date.date(), + "weekday": weekday, + "is_holiday": is_holiday, + "total_expected": timedelta_to_string(total_expected), + "total_actual": timedelta_to_string(total_actual), + "total_diffrance": timedelta_to_string(total_actual - total_expected), + "daily_actual": timedelta_to_string(daily_actual), + "daily_diffrance": timedelta_to_string(daily_actual - (daily_work_hours if should_work else holiday_work_hours if daily_actual >= holiday_work_hours else timedelta(0))), + }) + + return summary + +# text3 = """ +# Worked hours + +# Attendance Reason +# Marcin Nowak 22.11.2024 07:12:30 22.11.2024 16:05:33 08:53 + +# Marcin Nowak 21.11.2024 07:25:38 21.11.2024 16:43:19 09:18 + +# Marcin Nowak 20.11.2024 07:31:46 20.11.2024 15:47:36 08:16 + +# Marcin Nowak 19.11.2024 07:10:39 19.11.2024 15:54:14 08:44 + +# Marcin Nowak 18.11.2024 07:13:11 18.11.2024 16:04:15 08:51 + +# Marcin Nowak 15.11.2024 07:06:31 15.11.2024 15:41:49 08:35 + +# Marcin Nowak 14.11.2024 06:56:23 14.11.2024 16:11:52 09:15 + +# Marcin Nowak 13.11.2024 07:12:25 13.11.2024 17:19:58 10:08 + +# Marcin Nowak 12.11.2024 07:40:57 12.11.2024 16:10:42 08:30 + +# Marcin Nowak 08.11.2024 07:01:22 08.11.2024 15:48:01 08:47 + +# Marcin Nowak 07.11.2024 07:07:53 07.11.2024 16:26:30 09:19 + +# Marcin Nowak 06.11.2024 07:11:46 06.11.2024 16:08:43 08:57 + +# Marcin Nowak 05.11.2024 07:16:33 05.11.2024 16:10:52 08:54 + +# Marcin Nowak 04.11.2024 07:28:10 04.11.2024 15:51:19 08:23 + +# """ + +# # Przetwórz dane wejściowe +# work_records = process_input_txt(text3) +# date = work_records[-1]["enter"].date() +# monthly_summary = generate_monthly_summary(work_records, date.year, date.month) + +# # Przykład wyświetlenia podsumowania +# import pprint +# pprint.pprint(monthly_summary) diff --git a/app/odoo_hours/views.py b/app/odoo_hours/views.py index 91ea44a..1665fc0 100644 --- a/app/odoo_hours/views.py +++ b/app/odoo_hours/views.py @@ -1,3 +1,23 @@ from django.shortcuts import render - +from datetime import datetime +from .text_conv import generate_monthly_summary, process_input_txt # Create your views here. + +def index(request): + if request.method == "POST": + input_text = request.POST.get("input_text", "") + if input_text: + # Przetwarzanie tekstu + try: + work_records = process_input_txt(input_text) + last_day = work_records[-1]["enter"].date() + empty_days = datetime(year=last_day.year, month=last_day.month, day=1).weekday() + monthly_summary = generate_monthly_summary(work_records, last_day.year, last_day.month) + return render(request, "calendar.html", { + "days": monthly_summary, + "empty_days": range(empty_days), + }) + except: + return render(request, "hours_form.html", {"error": "Cos poszlo nie tak!"}) + + return render(request, "hours_form.html") \ No newline at end of file