Updated remove travel time related in calculation
This commit is contained in:
@@ -5053,7 +5053,6 @@ def payroll_dashboard():
|
|||||||
date_from = request.args.get('date_from', '')
|
date_from = request.args.get('date_from', '')
|
||||||
date_to = request.args.get('date_to', '')
|
date_to = request.args.get('date_to', '')
|
||||||
project_filter = request.args.get('project_filter', '')
|
project_filter = request.args.get('project_filter', '')
|
||||||
include_travel_time = request.args.get('include_travel_time', 'true').lower() == 'true'
|
|
||||||
|
|
||||||
# Set default date range if not provided (last 2 weeks)
|
# Set default date range if not provided (last 2 weeks)
|
||||||
if not date_from or not date_to:
|
if not date_from or not date_to:
|
||||||
@@ -5163,7 +5162,6 @@ def payroll_dashboard():
|
|||||||
date_to=date_to,
|
date_to=date_to,
|
||||||
project_filter=project_filter,
|
project_filter=project_filter,
|
||||||
selected_project_name=selected_project_name,
|
selected_project_name=selected_project_name,
|
||||||
include_travel_time=include_travel_time,
|
|
||||||
user_role=user_role)
|
user_role=user_role)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -5199,7 +5197,6 @@ def export_payroll_excel():
|
|||||||
date_from = request.form.get('date_from')
|
date_from = request.form.get('date_from')
|
||||||
date_to = request.form.get('date_to')
|
date_to = request.form.get('date_to')
|
||||||
project_filter = request.form.get('project_filter', '')
|
project_filter = request.form.get('project_filter', '')
|
||||||
include_travel_time = request.form.get('include_travel_time', 'false').lower() == 'true'
|
|
||||||
report_type = request.form.get('report_type', 'payroll') # 'payroll' or 'detailed'
|
report_type = request.form.get('report_type', 'payroll') # 'payroll' or 'detailed'
|
||||||
|
|
||||||
if not date_from or not date_to:
|
if not date_from or not date_to:
|
||||||
@@ -5283,7 +5280,7 @@ def export_payroll_excel():
|
|||||||
# Generate Excel file with employee names
|
# Generate Excel file with employee names
|
||||||
if report_type == 'detailed':
|
if report_type == 'detailed':
|
||||||
excel_file = exporter.create_detailed_hours_report(
|
excel_file = exporter.create_detailed_hours_report(
|
||||||
start_date, end_date, attendance_records, employee_names, include_travel_time
|
start_date, end_date, attendance_records, employee_names
|
||||||
)
|
)
|
||||||
filename_prefix = 'detailed_hours_report'
|
filename_prefix = 'detailed_hours_report'
|
||||||
elif report_type == 'template':
|
elif report_type == 'template':
|
||||||
@@ -5298,12 +5295,12 @@ def export_payroll_excel():
|
|||||||
print(f"⚠️ Error getting project name for template: {e}")
|
print(f"⚠️ Error getting project name for template: {e}")
|
||||||
|
|
||||||
excel_file = exporter.create_template_format_report(
|
excel_file = exporter.create_template_format_report(
|
||||||
start_date, end_date, attendance_records, employee_names, include_travel_time, project_name
|
start_date, end_date, attendance_records, employee_names, project_name
|
||||||
)
|
)
|
||||||
filename_prefix = 'time_attendance_report'
|
filename_prefix = 'time_attendance_report'
|
||||||
else:
|
else:
|
||||||
excel_file = exporter.create_payroll_report(
|
excel_file = exporter.create_payroll_report(
|
||||||
start_date, end_date, attendance_records, employee_names, include_travel_time
|
start_date, end_date, attendance_records, employee_names
|
||||||
)
|
)
|
||||||
filename_prefix = 'payroll_report'
|
filename_prefix = 'payroll_report'
|
||||||
|
|
||||||
@@ -5320,8 +5317,7 @@ def export_payroll_excel():
|
|||||||
if excel_file:
|
if excel_file:
|
||||||
# Generate filename with timestamp and project name
|
# Generate filename with timestamp and project name
|
||||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||||
travel_suffix = '_with_travel' if include_travel_time else '_no_travel'
|
filename = f'{filename_prefix}_{date_from}_to_{date_to}{project_name}_{timestamp}.xlsx'
|
||||||
filename = f'{filename_prefix}_{date_from}_to_{date_to}{project_name}{travel_suffix}_{timestamp}.xlsx'
|
|
||||||
|
|
||||||
print(f"📊 Payroll Excel file generated successfully: {filename}")
|
print(f"📊 Payroll Excel file generated successfully: {filename}")
|
||||||
|
|
||||||
@@ -5449,7 +5445,6 @@ def get_miss_punch_details(employee_id):
|
|||||||
date_from = request.args.get('date_from')
|
date_from = request.args.get('date_from')
|
||||||
date_to = request.args.get('date_to')
|
date_to = request.args.get('date_to')
|
||||||
project_filter = request.args.get('project_filter', '')
|
project_filter = request.args.get('project_filter', '')
|
||||||
include_travel_time = request.args.get('include_travel_time', 'true').lower() == 'true'
|
|
||||||
|
|
||||||
if not all([date_from, date_to]):
|
if not all([date_from, date_to]):
|
||||||
return jsonify({
|
return jsonify({
|
||||||
|
|||||||
@@ -160,8 +160,7 @@ class PayrollExcelExporter:
|
|||||||
|
|
||||||
@log_database_operations('payroll_excel_export')
|
@log_database_operations('payroll_excel_export')
|
||||||
def create_payroll_report(self, start_date: datetime, end_date: datetime,
|
def create_payroll_report(self, start_date: datetime, end_date: datetime,
|
||||||
attendance_records: List[Dict], employee_names: Dict[str, str] = None,
|
attendance_records: List[Dict], employee_names: Dict[str, str] = None) -> io.BytesIO:
|
||||||
include_travel_time: bool = True) -> io.BytesIO:
|
|
||||||
"""
|
"""
|
||||||
Create a comprehensive payroll report with working hours
|
Create a comprehensive payroll report with working hours
|
||||||
|
|
||||||
@@ -170,7 +169,6 @@ class PayrollExcelExporter:
|
|||||||
end_date: Report end date
|
end_date: Report end date
|
||||||
attendance_records: List of attendance records
|
attendance_records: List of attendance records
|
||||||
employee_names: Dictionary mapping employee_id to full name
|
employee_names: Dictionary mapping employee_id to full name
|
||||||
include_travel_time: Whether to include travel time in calculations
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
BytesIO buffer containing the Excel file
|
BytesIO buffer containing the Excel file
|
||||||
@@ -370,7 +368,6 @@ class PayrollExcelExporter:
|
|||||||
("Total Regular Hours:", round(total_regular_hours, 2)),
|
("Total Regular Hours:", round(total_regular_hours, 2)),
|
||||||
("Total Overtime Hours:", round(total_overtime_hours, 2)),
|
("Total Overtime Hours:", round(total_overtime_hours, 2)),
|
||||||
("Grand Total Hours:", round(total_hours, 2)),
|
("Grand Total Hours:", round(total_hours, 2)),
|
||||||
("Travel Time Included:", "Yes" if hours_data['include_travel_time'] else "No"),
|
|
||||||
("Report Generated:", datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
|
("Report Generated:", datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -399,8 +396,7 @@ class PayrollExcelExporter:
|
|||||||
|
|
||||||
@log_database_operations('detailed_hours_export')
|
@log_database_operations('detailed_hours_export')
|
||||||
def create_detailed_hours_report(self, start_date: datetime, end_date: datetime,
|
def create_detailed_hours_report(self, start_date: datetime, end_date: datetime,
|
||||||
attendance_records: List[Dict], employee_names: Dict[str, str] = None,
|
attendance_records: List[Dict], employee_names: Dict[str, str] = None) -> io.BytesIO:
|
||||||
include_travel_time: bool = True) -> io.BytesIO:
|
|
||||||
"""
|
"""
|
||||||
Create a detailed daily hours report for all employees
|
Create a detailed daily hours report for all employees
|
||||||
|
|
||||||
@@ -409,7 +405,6 @@ class PayrollExcelExporter:
|
|||||||
end_date: Report end date
|
end_date: Report end date
|
||||||
attendance_records: List of attendance records
|
attendance_records: List of attendance records
|
||||||
employee_names: Dictionary mapping employee_id to full name
|
employee_names: Dictionary mapping employee_id to full name
|
||||||
include_travel_time: Whether to include travel time in calculations
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
BytesIO buffer containing the Excel file
|
BytesIO buffer containing the Excel file
|
||||||
@@ -526,7 +521,7 @@ class PayrollExcelExporter:
|
|||||||
@log_database_operations('template_hours_export')
|
@log_database_operations('template_hours_export')
|
||||||
def create_template_format_report(self, start_date: datetime, end_date: datetime,
|
def create_template_format_report(self, start_date: datetime, end_date: datetime,
|
||||||
attendance_records: List[Dict], employee_names: Dict[str, str] = None,
|
attendance_records: List[Dict], employee_names: Dict[str, str] = None,
|
||||||
include_travel_time: bool = True, project_name: str = None) -> io.BytesIO:
|
project_name: str = None) -> io.BytesIO:
|
||||||
"""
|
"""
|
||||||
Create a template-format report matching the provided Excel template.
|
Create a template-format report matching the provided Excel template.
|
||||||
This creates a single sheet with all employees' detailed reports.
|
This creates a single sheet with all employees' detailed reports.
|
||||||
@@ -536,7 +531,6 @@ class PayrollExcelExporter:
|
|||||||
end_date: Report end date
|
end_date: Report end date
|
||||||
attendance_records: List of attendance records
|
attendance_records: List of attendance records
|
||||||
employee_names: Dictionary mapping employee_id to full name
|
employee_names: Dictionary mapping employee_id to full name
|
||||||
include_travel_time: Whether to include travel time in calculations
|
|
||||||
project_name: Project name for the report header
|
project_name: Project name for the report header
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|||||||
@@ -680,12 +680,6 @@
|
|||||||
Calculate Hours
|
Calculate Hours
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="checkbox-group">
|
|
||||||
<input type="checkbox" id="include_travel_time" name="include_travel_time"
|
|
||||||
value="true" {% if include_travel_time %}checked{% endif %}>
|
|
||||||
<label for="include_travel_time">Include travel time between locations</label>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -786,7 +780,6 @@
|
|||||||
<input type="hidden" name="date_from" value="{{ date_from }}">
|
<input type="hidden" name="date_from" value="{{ date_from }}">
|
||||||
<input type="hidden" name="date_to" value="{{ date_to }}">
|
<input type="hidden" name="date_to" value="{{ date_to }}">
|
||||||
<input type="hidden" name="project_filter" value="{{ project_filter }}">
|
<input type="hidden" name="project_filter" value="{{ project_filter }}">
|
||||||
<input type="hidden" name="include_travel_time" value="{{ include_travel_time|lower }}">
|
|
||||||
<input type="hidden" name="report_type" value="payroll">
|
<input type="hidden" name="report_type" value="payroll">
|
||||||
<button type="submit" class="btn btn-success">
|
<button type="submit" class="btn btn-success">
|
||||||
<i class="fas fa-file-excel"></i>
|
<i class="fas fa-file-excel"></i>
|
||||||
@@ -798,7 +791,6 @@
|
|||||||
<input type="hidden" name="date_from" value="{{ date_from }}">
|
<input type="hidden" name="date_from" value="{{ date_from }}">
|
||||||
<input type="hidden" name="date_to" value="{{ date_to }}">
|
<input type="hidden" name="date_to" value="{{ date_to }}">
|
||||||
<input type="hidden" name="project_filter" value="{{ project_filter }}">
|
<input type="hidden" name="project_filter" value="{{ project_filter }}">
|
||||||
<input type="hidden" name="include_travel_time" value="{{ include_travel_time|lower }}">
|
|
||||||
<input type="hidden" name="report_type" value="detailed">
|
<input type="hidden" name="report_type" value="detailed">
|
||||||
<button type="submit" class="btn btn-secondary">
|
<button type="submit" class="btn btn-secondary">
|
||||||
<i class="fas fa-list-alt"></i>
|
<i class="fas fa-list-alt"></i>
|
||||||
@@ -810,7 +802,6 @@
|
|||||||
<input type="hidden" name="date_from" value="{{ date_from }}">
|
<input type="hidden" name="date_from" value="{{ date_from }}">
|
||||||
<input type="hidden" name="date_to" value="{{ date_to }}">
|
<input type="hidden" name="date_to" value="{{ date_to }}">
|
||||||
<input type="hidden" name="project_filter" value="{{ project_filter }}">
|
<input type="hidden" name="project_filter" value="{{ project_filter }}">
|
||||||
<input type="hidden" name="include_travel_time" value="{{ include_travel_time|lower }}">
|
|
||||||
<input type="hidden" name="report_type" value="template">
|
<input type="hidden" name="report_type" value="template">
|
||||||
<button type="submit" class="btn btn-info">
|
<button type="submit" class="btn btn-info">
|
||||||
<i class="fas fa-file-contract"></i>
|
<i class="fas fa-file-contract"></i>
|
||||||
@@ -822,7 +813,6 @@
|
|||||||
<div style="margin-top: 1rem; padding: 1rem; background: #f7fafc; border-radius: 8px; color: #4a5568; font-size: 0.9rem;">
|
<div style="margin-top: 1rem; padding: 1rem; background: #f7fafc; border-radius: 8px; color: #4a5568; font-size: 0.9rem;">
|
||||||
<strong>Report Details:</strong><br>
|
<strong>Report Details:</strong><br>
|
||||||
Period: {{ working_hours_data.period_start }} to {{ working_hours_data.period_end }}<br>
|
Period: {{ working_hours_data.period_start }} to {{ working_hours_data.period_end }}<br>
|
||||||
Travel Time: {{ "Included" if working_hours_data.include_travel_time else "Excluded" }}<br>
|
|
||||||
Generated: {{ working_hours_data.calculation_date }}
|
Generated: {{ working_hours_data.calculation_date }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -871,20 +861,6 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
console.log('🚀 Payroll dashboard JavaScript starting...');
|
console.log('🚀 Payroll dashboard JavaScript starting...');
|
||||||
|
|
||||||
// Auto-submit form when travel time checkbox changes
|
|
||||||
const travelTimeCheckbox = document.getElementById('include_travel_time');
|
|
||||||
if (travelTimeCheckbox) {
|
|
||||||
travelTimeCheckbox.addEventListener('change', function() {
|
|
||||||
// Only auto-submit if we have dates
|
|
||||||
const dateFrom = document.getElementById('date_from').value;
|
|
||||||
const dateTo = document.getElementById('date_to').value;
|
|
||||||
|
|
||||||
if (dateFrom && dateTo) {
|
|
||||||
this.form.submit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set max date to today for date inputs
|
// Set max date to today for date inputs
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
document.getElementById('date_from').setAttribute('max', today);
|
document.getElementById('date_from').setAttribute('max', today);
|
||||||
@@ -966,7 +942,6 @@ function showMissPunchDetails(employeeId) {
|
|||||||
const dateFrom = document.querySelector('input[name="date_from"]')?.value || '';
|
const dateFrom = document.querySelector('input[name="date_from"]')?.value || '';
|
||||||
const dateTo = document.querySelector('input[name="date_to"]')?.value || '';
|
const dateTo = document.querySelector('input[name="date_to"]')?.value || '';
|
||||||
const projectFilter = document.querySelector('select[name="project_filter"]')?.value || '';
|
const projectFilter = document.querySelector('select[name="project_filter"]')?.value || '';
|
||||||
const includeTravelTime = document.querySelector('input[name="include_travel_time"]')?.checked || false;
|
|
||||||
|
|
||||||
// Show modal with loading state
|
// Show modal with loading state
|
||||||
const modal = document.getElementById('missPunchModal');
|
const modal = document.getElementById('missPunchModal');
|
||||||
@@ -987,7 +962,6 @@ function showMissPunchDetails(employeeId) {
|
|||||||
date_from: dateFrom,
|
date_from: dateFrom,
|
||||||
date_to: dateTo,
|
date_to: dateTo,
|
||||||
project_filter: projectFilter,
|
project_filter: projectFilter,
|
||||||
include_travel_time: includeTravelTime.toString()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const apiUrl = `/api/employee/${employeeId}/miss-punch-details?${params.toString()}`;
|
const apiUrl = `/api/employee/${employeeId}/miss-punch-details?${params.toString()}`;
|
||||||
|
|||||||
@@ -24,11 +24,9 @@ import math
|
|||||||
from logger_handler import log_database_operations
|
from logger_handler import log_database_operations
|
||||||
|
|
||||||
# Constants from Java implementation
|
# Constants from Java implementation
|
||||||
TRAVEL_TIME_MAX_MINUTES = 60
|
|
||||||
RECORD_GROUPING_MAX_MINUTES = 60 * 6 # 6 hours
|
RECORD_GROUPING_MAX_MINUTES = 60 * 6 # 6 hours
|
||||||
MAX_REGULAR_TIME_MINUTES = 60 * 40 # 40 hours per week
|
MAX_REGULAR_TIME_MINUTES = 60 * 40 # 40 hours per week
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AttendanceRecord:
|
class AttendanceRecord:
|
||||||
"""Represents a single attendance record"""
|
"""Represents a single attendance record"""
|
||||||
@@ -45,7 +43,6 @@ class AttendanceRecord:
|
|||||||
# Combine date and time for timestamp
|
# Combine date and time for timestamp
|
||||||
self.timestamp = datetime.combine(self.check_in_date, self.check_in_time)
|
self.timestamp = datetime.combine(self.check_in_date, self.check_in_time)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RecordPair:
|
class RecordPair:
|
||||||
"""Represents a paired check-in/check-out record"""
|
"""Represents a paired check-in/check-out record"""
|
||||||
@@ -72,7 +69,6 @@ class RecordPair:
|
|||||||
duration = self.check_out.timestamp - self.check_in.timestamp
|
duration = self.check_out.timestamp - self.check_in.timestamp
|
||||||
return int(duration.total_seconds() / 60)
|
return int(duration.total_seconds() / 60)
|
||||||
|
|
||||||
|
|
||||||
class TimeCalculator:
|
class TimeCalculator:
|
||||||
"""Base time calculator with rounding functionality"""
|
"""Base time calculator with rounding functionality"""
|
||||||
|
|
||||||
@@ -85,7 +81,6 @@ class TimeCalculator:
|
|||||||
# Round to nearest 15-minute interval
|
# Round to nearest 15-minute interval
|
||||||
return round(minutes / 15) * 15
|
return round(minutes / 15) * 15
|
||||||
|
|
||||||
|
|
||||||
class DailyTimeCalculator(TimeCalculator):
|
class DailyTimeCalculator(TimeCalculator):
|
||||||
"""Calculate daily working hours with travel time options"""
|
"""Calculate daily working hours with travel time options"""
|
||||||
|
|
||||||
@@ -96,19 +91,6 @@ class DailyTimeCalculator(TimeCalculator):
|
|||||||
"""Add a record pair to the daily calculation"""
|
"""Add a record pair to the daily calculation"""
|
||||||
self.record_pairs.append(pair)
|
self.record_pairs.append(pair)
|
||||||
|
|
||||||
def get_minutes_total_include_travel_time(self) -> int:
|
|
||||||
"""Calculate total minutes including travel time between locations"""
|
|
||||||
minute_total = 0
|
|
||||||
grouped_pairs = self._group_record_pairs(self.record_pairs)
|
|
||||||
|
|
||||||
for group in grouped_pairs:
|
|
||||||
group_minutes = self._get_minutes_per_record_pair_group(group)
|
|
||||||
if group_minutes < 0:
|
|
||||||
return -1 # Miss punch detected
|
|
||||||
minute_total += group_minutes
|
|
||||||
|
|
||||||
return self.round_time_to_nearest_quarter_hour(minute_total)
|
|
||||||
|
|
||||||
def get_minutes_total_exclude_travel_time(self) -> int:
|
def get_minutes_total_exclude_travel_time(self) -> int:
|
||||||
"""Calculate total minutes excluding travel time"""
|
"""Calculate total minutes excluding travel time"""
|
||||||
minute_total = 0
|
minute_total = 0
|
||||||
@@ -121,55 +103,6 @@ class DailyTimeCalculator(TimeCalculator):
|
|||||||
|
|
||||||
return self.round_time_to_nearest_quarter_hour(minute_total)
|
return self.round_time_to_nearest_quarter_hour(minute_total)
|
||||||
|
|
||||||
def _group_record_pairs(self, record_pairs: List[RecordPair]) -> List[List[RecordPair]]:
|
|
||||||
"""Group record pairs based on time gaps (similar to Java implementation)"""
|
|
||||||
if not record_pairs:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Sort pairs by time
|
|
||||||
sorted_pairs = sorted(record_pairs, key=lambda p: p.check_in.timestamp if p.check_in else p.check_out.timestamp)
|
|
||||||
|
|
||||||
all_groups = []
|
|
||||||
current_group = [sorted_pairs[0]]
|
|
||||||
|
|
||||||
for i in range(1, len(sorted_pairs)):
|
|
||||||
prev_pair = sorted_pairs[i-1]
|
|
||||||
current_pair = sorted_pairs[i]
|
|
||||||
|
|
||||||
# Calculate time gap
|
|
||||||
if prev_pair.check_out and current_pair.check_in:
|
|
||||||
gap_minutes = (current_pair.check_in.timestamp - prev_pair.check_out.timestamp).total_seconds() / 60
|
|
||||||
|
|
||||||
if gap_minutes > TRAVEL_TIME_MAX_MINUTES:
|
|
||||||
# Start new group
|
|
||||||
all_groups.append(current_group)
|
|
||||||
current_group = [current_pair]
|
|
||||||
else:
|
|
||||||
current_group.append(current_pair)
|
|
||||||
else:
|
|
||||||
current_group.append(current_pair)
|
|
||||||
|
|
||||||
all_groups.append(current_group)
|
|
||||||
return all_groups
|
|
||||||
|
|
||||||
def _get_minutes_per_record_pair_group(self, group: List[RecordPair]) -> int:
|
|
||||||
"""Calculate minutes for a group of record pairs with travel time"""
|
|
||||||
if not group:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# Check for miss punches
|
|
||||||
for pair in group:
|
|
||||||
if pair.is_miss_punch:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
# Find overall start and end times for the group
|
|
||||||
start_time = min(pair.check_in.timestamp for pair in group if pair.check_in)
|
|
||||||
end_time = max(pair.check_out.timestamp for pair in group if pair.check_out)
|
|
||||||
|
|
||||||
duration = end_time - start_time
|
|
||||||
return int(duration.total_seconds() / 60)
|
|
||||||
|
|
||||||
|
|
||||||
class WeeklyTimeCalculator(TimeCalculator):
|
class WeeklyTimeCalculator(TimeCalculator):
|
||||||
"""Calculate weekly regular and overtime hours"""
|
"""Calculate weekly regular and overtime hours"""
|
||||||
|
|
||||||
@@ -178,29 +111,18 @@ class WeeklyTimeCalculator(TimeCalculator):
|
|||||||
self.total_minutes = 0
|
self.total_minutes = 0
|
||||||
self.regular_minutes = 0
|
self.regular_minutes = 0
|
||||||
self.overtime_minutes = 0
|
self.overtime_minutes = 0
|
||||||
self.include_travel_time = True # Default setting
|
|
||||||
|
|
||||||
def add_daily_calculator(self, daily_calc: DailyTimeCalculator):
|
def add_daily_calculator(self, daily_calc: DailyTimeCalculator):
|
||||||
"""Add a daily time calculator to the weekly calculation"""
|
"""Add a daily time calculator to the weekly calculation"""
|
||||||
self.daily_calculators.append(daily_calc)
|
self.daily_calculators.append(daily_calc)
|
||||||
|
|
||||||
def set_include_travel_time(self, include: bool):
|
|
||||||
"""Set whether to include travel time in calculations"""
|
|
||||||
self.include_travel_time = include
|
|
||||||
|
|
||||||
def calculate_time(self):
|
def calculate_time(self):
|
||||||
"""Calculate weekly totals with regular and overtime split"""
|
"""Calculate weekly totals with regular and overtime split"""
|
||||||
self.total_minutes = 0
|
self.total_minutes = 0
|
||||||
|
|
||||||
for daily_calc in self.daily_calculators:
|
for daily_calc in self.daily_calculators:
|
||||||
if self.include_travel_time:
|
|
||||||
daily_minutes = daily_calc.get_minutes_total_include_travel_time()
|
|
||||||
else:
|
|
||||||
daily_minutes = daily_calc.get_minutes_total_exclude_travel_time()
|
daily_minutes = daily_calc.get_minutes_total_exclude_travel_time()
|
||||||
|
|
||||||
if daily_minutes > 0:
|
|
||||||
self.total_minutes += daily_minutes
|
|
||||||
|
|
||||||
# Calculate regular and overtime
|
# Calculate regular and overtime
|
||||||
if self.total_minutes > MAX_REGULAR_TIME_MINUTES:
|
if self.total_minutes > MAX_REGULAR_TIME_MINUTES:
|
||||||
self.regular_minutes = MAX_REGULAR_TIME_MINUTES
|
self.regular_minutes = MAX_REGULAR_TIME_MINUTES
|
||||||
@@ -224,7 +146,6 @@ class WeeklyTimeCalculator(TimeCalculator):
|
|||||||
"""Get overtime hours as decimal"""
|
"""Get overtime hours as decimal"""
|
||||||
return self.overtime_minutes / 60.0
|
return self.overtime_minutes / 60.0
|
||||||
|
|
||||||
|
|
||||||
class RecordPairBuilder:
|
class RecordPairBuilder:
|
||||||
"""Build record pairs from attendance records"""
|
"""Build record pairs from attendance records"""
|
||||||
|
|
||||||
@@ -274,12 +195,11 @@ class RecordPairBuilder:
|
|||||||
|
|
||||||
return pairs
|
return pairs
|
||||||
|
|
||||||
|
|
||||||
class WorkingHoursCalculator:
|
class WorkingHoursCalculator:
|
||||||
"""Main calculator for employee working hours"""
|
"""Main calculator for employee working hours"""
|
||||||
|
|
||||||
def __init__(self, include_travel_time: bool = True):
|
def __init__(self):
|
||||||
self.include_travel_time = include_travel_time
|
pass
|
||||||
|
|
||||||
@log_database_operations('working_hours_calculation')
|
@log_database_operations('working_hours_calculation')
|
||||||
def calculate_employee_hours(self, employee_id: str, start_date: datetime, end_date: datetime,
|
def calculate_employee_hours(self, employee_id: str, start_date: datetime, end_date: datetime,
|
||||||
@@ -353,9 +273,6 @@ class WorkingHoursCalculator:
|
|||||||
daily_calc.add_record_pair(pair)
|
daily_calc.add_record_pair(pair)
|
||||||
|
|
||||||
# Calculate daily totals
|
# Calculate daily totals
|
||||||
if self.include_travel_time:
|
|
||||||
total_minutes = daily_calc.get_minutes_total_include_travel_time()
|
|
||||||
else:
|
|
||||||
total_minutes = daily_calc.get_minutes_total_exclude_travel_time()
|
total_minutes = daily_calc.get_minutes_total_exclude_travel_time()
|
||||||
|
|
||||||
daily_hours[date_key] = {
|
daily_hours[date_key] = {
|
||||||
@@ -368,7 +285,6 @@ class WorkingHoursCalculator:
|
|||||||
# Add to weekly calculator (group by week)
|
# Add to weekly calculator (group by week)
|
||||||
if current_date.weekday() == 0: # Monday - start new week
|
if current_date.weekday() == 0: # Monday - start new week
|
||||||
weekly_calc = WeeklyTimeCalculator()
|
weekly_calc = WeeklyTimeCalculator()
|
||||||
weekly_calc.set_include_travel_time(self.include_travel_time)
|
|
||||||
weekly_calculators.append(weekly_calc)
|
weekly_calculators.append(weekly_calc)
|
||||||
|
|
||||||
if weekly_calculators:
|
if weekly_calculators:
|
||||||
@@ -398,7 +314,6 @@ class WorkingHoursCalculator:
|
|||||||
'employee_id': employee_id,
|
'employee_id': employee_id,
|
||||||
'start_date': start_date.strftime('%Y-%m-%d'),
|
'start_date': start_date.strftime('%Y-%m-%d'),
|
||||||
'end_date': end_date.strftime('%Y-%m-%d'),
|
'end_date': end_date.strftime('%Y-%m-%d'),
|
||||||
'include_travel_time': self.include_travel_time,
|
|
||||||
'daily_hours': daily_hours,
|
'daily_hours': daily_hours,
|
||||||
'weekly_hours': weekly_hours,
|
'weekly_hours': weekly_hours,
|
||||||
'grand_totals': {
|
'grand_totals': {
|
||||||
@@ -435,7 +350,6 @@ class WorkingHoursCalculator:
|
|||||||
'calculation_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
'calculation_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
'period_start': start_date.strftime('%Y-%m-%d'),
|
'period_start': start_date.strftime('%Y-%m-%d'),
|
||||||
'period_end': end_date.strftime('%Y-%m-%d'),
|
'period_end': end_date.strftime('%Y-%m-%d'),
|
||||||
'include_travel_time': self.include_travel_time,
|
|
||||||
'employee_count': len(employee_ids),
|
'employee_count': len(employee_ids),
|
||||||
'employees': results
|
'employees': results
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user