fix(msp): Performance-Chart — Abwesenheiten aus itsdave_hr statt hrms-Attendance

get_attendance_list fragte den DocType `Attendance` ab, den es seit dem
v16-Stand ohne hrms nicht mehr gibt. Der DoesNotExistError riss die
gesamte Kette get_data -> compare_hours -> get_target_hours mit, der
Dashboard-Chart "Meine Performance" blieb fuer alle Nutzer auf "Loading"
haengen (ITSD-22440).

Quelle sind jetzt genehmigte HR Absence Requests, deren Abwesenheitsart
das Soll reduziert (HR Absence Type.affects_target_hours). Halbe Tage
(half_day_start/half_day_end) halbieren das Tagessoll.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-21 15:33:59 +02:00
co-authored by Claude Opus 5
parent 87278edfe3
commit f81ba1681b
+54 -9
View File
@@ -3,6 +3,7 @@ import json
from datetime import datetime, date, timedelta
import math
from frappe.database import get_db
from frappe.utils import getdate
from erpnext.stock.utils import get_stock_balance
import pandas as pd
import requests
@@ -251,20 +252,64 @@ def get_holidays_list(from_date, to_date):
return holidays_list
def get_hr_employee(employee):
"""Loest einen ERPNext-Employee auf den zugehoerigen HR Employee auf."""
if not frappe.db.exists("DocType", "HR Employee"):
return None
hr_employee = frappe.db.get_value("HR Employee", {"source_employee": employee}, "name")
if not hr_employee and frappe.db.exists("HR Employee", employee):
hr_employee = employee
return hr_employee
def get_attendance_list(employee, from_date, to_date):
"""Soll-mindernde Abwesenheiten je Tag aus itsdave_hr.
# List of attendance data for the employee in the specified period
attendance_data = frappe.get_all("Attendance",
Liefert {Datum: "On Leave"} fuer ganze und {Datum: "Half Day"} fuer halbe
Abwesenheitstage. Beruecksichtigt werden nur genehmigte Antraege, deren
Abwesenheitsart das Soll reduziert (HR Absence Type.affects_target_hours).
"""
hr_employee = get_hr_employee(employee)
if not hr_employee:
return {}
from_date = getdate(from_date)
to_date = getdate(to_date)
absences = frappe.get_all("HR Absence Request",
filters={
"employee": employee,
"attendance_date": (">=", from_date),
"attendance_date": ("<=", to_date)
"employee": hr_employee,
"status": "Approved",
"from_date": ("<=", to_date),
"to_date": (">=", from_date)
},
fields=["attendance_date", "status"])
fields=["absence_type", "from_date", "to_date",
"half_day_start", "half_day_end"],
order_by="from_date asc, name asc")
# Dictionary with date as key and attendance status as value
attendance_dict = {entry["attendance_date"]: entry["status"] for entry in attendance_data}
return attendance_dict
day_factors = {}
for absence in absences:
if not frappe.get_cached_value("HR Absence Type", absence["absence_type"], "affects_target_hours"):
continue
absence_from = getdate(absence["from_date"])
absence_to = getdate(absence["to_date"])
current_date = max(from_date, absence_from)
last_date = min(to_date, absence_to)
while current_date <= last_date:
half_day = ((absence["half_day_start"] and current_date == absence_from)
or (absence["half_day_end"] and current_date == absence_to))
factor = 0.5 if half_day else 1.0
day_factors[current_date] = min(1.0, day_factors.get(current_date, 0.0) + factor)
current_date += timedelta(days=1)
return {day: ("On Leave" if factor >= 1.0 else "Half Day")
for day, factor in day_factors.items()}
@frappe.whitelist()