fix: apply travel-day quantity rules
Use business-rule based daily quantity for travel expense calculations and cover it with regression tests.
This commit is contained in:
@@ -26,7 +26,7 @@ from modules.fleet.models import (
|
|||||||
)
|
)
|
||||||
from modules.task_management.models import Task, TaskWorkHoursTable
|
from modules.task_management.models import Task, TaskWorkHoursTable
|
||||||
from modules.fleet.tasks import build_monthly_archive_cached_task
|
from modules.fleet.tasks import build_monthly_archive_cached_task
|
||||||
from modules.fleet.views import _build_monthly_servicer_report_rows
|
from modules.fleet.views import _build_monthly_servicer_report_rows, _calculate_daily_quantity_from_hours
|
||||||
|
|
||||||
|
|
||||||
def create_test_image(filename='test.jpg', size=(40, 40), color='red'):
|
def create_test_image(filename='test.jpg', size=(40, 40), color='red'):
|
||||||
@@ -477,15 +477,15 @@ class WorkOrderImagesEndpointTests(TestCase):
|
|||||||
'date': '24.12.2033',
|
'date': '24.12.2033',
|
||||||
'work_time_from': '08:00',
|
'work_time_from': '08:00',
|
||||||
'work_time_to': '16:00',
|
'work_time_to': '16:00',
|
||||||
'travel_hours': '2',
|
'travel_hours': '1',
|
||||||
'work_hours': '6',
|
'work_hours': '6',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'date': '25.12.2033',
|
'date': '25.12.2033',
|
||||||
'work_time_from': '09:00',
|
'work_time_from': '09:00',
|
||||||
'work_time_to': '15:00',
|
'work_time_to': '15:00',
|
||||||
'travel_hours': '1,5',
|
'travel_hours': '0,5',
|
||||||
'work_hours': '4',
|
'work_hours': '2',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -499,8 +499,16 @@ class WorkOrderImagesEndpointTests(TestCase):
|
|||||||
text = '\n'.join((page.extract_text() or '') for page in reader.pages)
|
text = '\n'.join((page.extract_text() or '') for page in reader.pages)
|
||||||
self.assertIn('24.12.2033.', text)
|
self.assertIn('24.12.2033.', text)
|
||||||
self.assertIn('25.12.2033.', text)
|
self.assertIn('25.12.2033.', text)
|
||||||
self.assertIn('13,5', text)
|
self.assertIn('9,5', text)
|
||||||
self.assertIn('0,6', text)
|
self.assertIn('0,5', text)
|
||||||
|
|
||||||
|
def test_calculate_daily_quantity_from_hours_follows_business_rules(self):
|
||||||
|
self.assertEqual(_calculate_daily_quantity_from_hours(0), 0.0)
|
||||||
|
self.assertEqual(_calculate_daily_quantity_from_hours(7), 0.0)
|
||||||
|
self.assertEqual(_calculate_daily_quantity_from_hours(8), 0.5)
|
||||||
|
self.assertEqual(_calculate_daily_quantity_from_hours(10.5), 0.5)
|
||||||
|
self.assertEqual(_calculate_daily_quantity_from_hours(12), 1.0)
|
||||||
|
self.assertEqual(_calculate_daily_quantity_from_hours(13.5), 1.0)
|
||||||
|
|
||||||
def test_monthly_servicer_report_rows_use_task_work_hours_table_hours(self):
|
def test_monthly_servicer_report_rows_use_task_work_hours_table_hours(self):
|
||||||
TaskWorkHoursTable.objects.create(
|
TaskWorkHoursTable.objects.create(
|
||||||
|
|||||||
@@ -196,6 +196,17 @@ def _parse_report_decimal_value(value):
|
|||||||
return Decimal('0.00')
|
return Decimal('0.00')
|
||||||
|
|
||||||
|
|
||||||
|
def _calculate_daily_quantity_from_hours(total_hours):
|
||||||
|
numeric = Decimal(str(total_hours)) if total_hours not in (None, '') else Decimal('0.00')
|
||||||
|
if numeric <= 0:
|
||||||
|
return 0.0
|
||||||
|
if numeric < Decimal('8'):
|
||||||
|
return 0.0
|
||||||
|
if numeric < Decimal('12'):
|
||||||
|
return 0.5
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
|
||||||
def _task_work_hours_entries(task):
|
def _task_work_hours_entries(task):
|
||||||
table_data = getattr(getattr(task, 'work_hours_table', None), 'data', None)
|
table_data = getattr(getattr(task, 'work_hours_table', None), 'data', None)
|
||||||
rows = table_data.get('rows', []) if isinstance(table_data, dict) else []
|
rows = table_data.get('rows', []) if isinstance(table_data, dict) else []
|
||||||
@@ -720,7 +731,7 @@ def _build_work_order_pdf(work_order):
|
|||||||
start_candidates = [entry['start_dt'] for entry in trip_entries if entry.get('start_dt')]
|
start_candidates = [entry['start_dt'] for entry in trip_entries if entry.get('start_dt')]
|
||||||
end_candidates = [entry['end_dt'] for entry in trip_entries if entry.get('end_dt')]
|
end_candidates = [entry['end_dt'] for entry in trip_entries if entry.get('end_dt')]
|
||||||
total_hours = sum((entry['total_hours'] for entry in trip_entries), Decimal('0.00'))
|
total_hours = sum((entry['total_hours'] for entry in trip_entries), Decimal('0.00'))
|
||||||
travel_hours = float(total_hours)
|
travel_hours = float(total_hours.quantize(Decimal('0.01')))
|
||||||
if entry_dates:
|
if entry_dates:
|
||||||
trip_start_date = min(entry_dates)
|
trip_start_date = min(entry_dates)
|
||||||
trip_end_date = max(entry_dates)
|
trip_end_date = max(entry_dates)
|
||||||
@@ -729,7 +740,7 @@ def _build_work_order_pdf(work_order):
|
|||||||
if end_candidates:
|
if end_candidates:
|
||||||
travel_end = max(end_candidates)
|
travel_end = max(end_candidates)
|
||||||
|
|
||||||
daily_qty = round(travel_hours / 24.0, 1) if travel_hours > 0 else 0.0
|
daily_qty = _calculate_daily_quantity_from_hours(travel_hours)
|
||||||
daily_rate = 0.0
|
daily_rate = 0.0
|
||||||
daily_total = daily_qty * daily_rate
|
daily_total = daily_qty * daily_rate
|
||||||
transport_total = float(work_order.servicer_vehicle_fuel_cost or 0.0)
|
transport_total = float(work_order.servicer_vehicle_fuel_cost or 0.0)
|
||||||
|
|||||||
Reference in New Issue
Block a user