fix: add total rows to service-record hours tables
Add a final 'Ukupno' row to the exported work-hours tables so PDF and DOCX service records include both work and travel totals. This keeps the generated reports aligned with the underlying task data. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -497,6 +497,67 @@ class WorkOrderImagesEndpointTests(TestCase):
|
|||||||
self.assertIn('MT170726', text)
|
self.assertIn('MT170726', text)
|
||||||
self.assertNotIn('MT150726', text)
|
self.assertNotIn('MT150726', text)
|
||||||
|
|
||||||
|
def test_service_records_docx_includes_work_and_travel_total_row(self):
|
||||||
|
TaskWorkHoursTable.objects.create(
|
||||||
|
task=self.task,
|
||||||
|
data={
|
||||||
|
'rows': [{
|
||||||
|
'day': 'PON',
|
||||||
|
'date': '01.12.2033',
|
||||||
|
'work_time_from': '08:00',
|
||||||
|
'work_time_to': '12:30',
|
||||||
|
'travel_time_from': '07:00',
|
||||||
|
'travel_time_to': '08:00',
|
||||||
|
'break_hours': '0,5',
|
||||||
|
'work_hours': '4,0',
|
||||||
|
'travel_hours': '1,0',
|
||||||
|
'departure_place': 'Zagreb',
|
||||||
|
'arrival_place': 'Split',
|
||||||
|
'vehicle_km': '120',
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.get(f"/api/fleet/work-orders/{self.work_order.pk}/service-records-docx/?task_id={self.task.pk}")
|
||||||
|
self.assertEqual(response.status_code, 200, response.content)
|
||||||
|
|
||||||
|
archive = zipfile.ZipFile(BytesIO(response.content))
|
||||||
|
document_xml = archive.read('word/document.xml').decode('utf-8', errors='ignore')
|
||||||
|
self.assertIn('Ukupno', document_xml)
|
||||||
|
self.assertIn('4,0', document_xml)
|
||||||
|
self.assertIn('1,0', document_xml)
|
||||||
|
|
||||||
|
def test_service_records_pdf_includes_work_and_travel_total_row(self):
|
||||||
|
TaskWorkHoursTable.objects.create(
|
||||||
|
task=self.task,
|
||||||
|
data={
|
||||||
|
'rows': [{
|
||||||
|
'day': 'PON',
|
||||||
|
'date': '01.12.2033',
|
||||||
|
'work_time_from': '08:00',
|
||||||
|
'work_time_to': '12:30',
|
||||||
|
'travel_time_from': '07:00',
|
||||||
|
'travel_time_to': '08:00',
|
||||||
|
'break_hours': '0,5',
|
||||||
|
'work_hours': '4,0',
|
||||||
|
'travel_hours': '1,0',
|
||||||
|
'departure_place': 'Zagreb',
|
||||||
|
'arrival_place': 'Split',
|
||||||
|
'vehicle_km': '120',
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.get(f"/api/fleet/work-orders/{self.work_order.pk}/service-records-pdf/?task_id={self.task.pk}")
|
||||||
|
self.assertEqual(response.status_code, 200, response.content)
|
||||||
|
self.assertEqual(response['Content-Type'], 'application/pdf')
|
||||||
|
|
||||||
|
reader = PdfReader(BytesIO(response.content))
|
||||||
|
text = "\n".join((page.extract_text() or '') for page in reader.pages)
|
||||||
|
self.assertIn('UKUPNO', text)
|
||||||
|
self.assertIn('4,0', text)
|
||||||
|
self.assertIn('1,0', text)
|
||||||
|
|
||||||
def test_monthly_service_tasks_archive_returns_zip_with_task_docx(self):
|
def test_monthly_service_tasks_archive_returns_zip_with_task_docx(self):
|
||||||
response = self.client.get('/api/fleet/reports/monthly-service-tasks-archive/?year=2033&month=12')
|
response = self.client.get('/api/fleet/reports/monthly-service-tasks-archive/?year=2033&month=12')
|
||||||
self.assertEqual(response.status_code, 200, response.content)
|
self.assertEqual(response.status_code, 200, response.content)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from io import StringIO
|
|||||||
import base64
|
import base64
|
||||||
import csv
|
import csv
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import tempfile
|
|
||||||
import threading
|
import threading
|
||||||
import zipfile
|
import zipfile
|
||||||
import re
|
import re
|
||||||
@@ -384,8 +383,6 @@ def _parse_format(value):
|
|||||||
|
|
||||||
GENERATED_PDF_TTL_HOURS = 24
|
GENERATED_PDF_TTL_HOURS = 24
|
||||||
GENERATED_ARCHIVE_TTL_DAYS = 7
|
GENERATED_ARCHIVE_TTL_DAYS = 7
|
||||||
TMP_ARCHIVE_RETENTION_HOURS = 24
|
|
||||||
TMP_ARCHIVE_DIRNAME = 'erp-generated-archives'
|
|
||||||
|
|
||||||
|
|
||||||
def _work_order_display_code(work_order):
|
def _work_order_display_code(work_order):
|
||||||
@@ -1337,6 +1334,7 @@ def _build_work_order_service_records_pdf(work_order, related_tasks=None):
|
|||||||
"Kilometri\nvozila",
|
"Kilometri\nvozila",
|
||||||
]
|
]
|
||||||
table3_rows = [table3_headers]
|
table3_rows = [table3_headers]
|
||||||
|
total_hours_work = 0.0
|
||||||
total_hours_travel = 0.0
|
total_hours_travel = 0.0
|
||||||
total_vehicle_km = 0.0
|
total_vehicle_km = 0.0
|
||||||
|
|
||||||
@@ -1353,13 +1351,24 @@ def _build_work_order_service_records_pdf(work_order, related_tasks=None):
|
|||||||
row['places'],
|
row['places'],
|
||||||
row['vehicle_km'],
|
row['vehicle_km'],
|
||||||
])
|
])
|
||||||
|
total_hours_work += _parse_decimal(row['work_hours'])
|
||||||
total_hours_travel += _parse_decimal(row['travel_hours'])
|
total_hours_travel += _parse_decimal(row['travel_hours'])
|
||||||
total_vehicle_km += _parse_decimal(row['vehicle_km'])
|
total_vehicle_km += _parse_decimal(row['vehicle_km'])
|
||||||
else:
|
else:
|
||||||
for _ in range(12):
|
for _ in range(12):
|
||||||
table3_rows.append(["-", "-", "-", "-", "-", "-", "-", "Polazak: -\nDolazak: -", "-"])
|
table3_rows.append(["-", "-", "-", "-", "-", "-", "-", "Polazak: -\nDolazak: -", "-"])
|
||||||
|
|
||||||
table3_rows.append(["UKUPNO", "", "", "", "", "", _format_decimal(total_hours_travel), "", str(int(total_vehicle_km) if total_vehicle_km.is_integer() else total_vehicle_km).replace('.', ',')])
|
table3_rows.append([
|
||||||
|
"UKUPNO",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
_format_decimal(total_hours_work),
|
||||||
|
_format_decimal(total_hours_travel),
|
||||||
|
"",
|
||||||
|
str(int(total_vehicle_km) if total_vehicle_km.is_integer() else total_vehicle_km).replace('.', ','),
|
||||||
|
])
|
||||||
y = draw_table(
|
y = draw_table(
|
||||||
y,
|
y,
|
||||||
table3_rows,
|
table3_rows,
|
||||||
@@ -1371,15 +1380,11 @@ def _build_work_order_service_records_pdf(work_order, related_tasks=None):
|
|||||||
('GRID', (0, 0), (-1, -1), 0.5, colors.black),
|
('GRID', (0, 0), (-1, -1), 0.5, colors.black),
|
||||||
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
||||||
('ALIGN', (0, 0), (0, -1), 'CENTER'),
|
('ALIGN', (0, 0), (0, -1), 'CENTER'),
|
||||||
('ALIGN', (1, 0), (6, -1), 'CENTER'),
|
('ALIGN', (1, 0), (8, -1), 'CENTER'),
|
||||||
('SPAN', (0, -1), (5, -1)),
|
|
||||||
('ALIGN', (0, -1), (5, -1), 'LEFT'),
|
|
||||||
('FONTNAME', (0, -1), (0, -1), 'Vera-Bold'),
|
('FONTNAME', (0, -1), (0, -1), 'Vera-Bold'),
|
||||||
('FONTNAME', (6, -1), (6, -1), 'Vera-Bold'),
|
('FONTNAME', (5, -1), (8, -1), 'Vera-Bold'),
|
||||||
('FONTNAME', (8, -1), (8, -1), 'Vera-Bold'),
|
|
||||||
('LEFTPADDING', (0, 0), (-1, -1), 3),
|
('LEFTPADDING', (0, 0), (-1, -1), 3),
|
||||||
('RIGHTPADDING', (0, 0), (-1, -1), 3),
|
('RIGHTPADDING', (0, 0), (-1, -1), 3),
|
||||||
('LEFTPADDING', (0, -1), (5, -1), 12),
|
|
||||||
('VALIGN', (7, 1), (7, -2), 'TOP'),
|
('VALIGN', (7, 1), (7, -2), 'TOP'),
|
||||||
('ALIGN', (7, 1), (7, -2), 'LEFT'),
|
('ALIGN', (7, 1), (7, -2), 'LEFT'),
|
||||||
]),
|
]),
|
||||||
@@ -2268,6 +2273,8 @@ def _build_work_order_service_records_docx_bytes(work_order, related_tasks=None)
|
|||||||
for index, header in enumerate(headers):
|
for index, header in enumerate(headers):
|
||||||
hours_table.rows[0].cells[index].text = header
|
hours_table.rows[0].cells[index].text = header
|
||||||
_docx_remove_rows_after(hours_table, keep_rows=1)
|
_docx_remove_rows_after(hours_table, keep_rows=1)
|
||||||
|
total_work_hours = sum(_parse_report_decimal_value(row['work_hours']) for row in normalized_rows)
|
||||||
|
total_travel_hours = sum(_parse_report_decimal_value(row['travel_hours']) for row in normalized_rows)
|
||||||
if normalized_rows:
|
if normalized_rows:
|
||||||
for row in normalized_rows:
|
for row in normalized_rows:
|
||||||
cells = hours_table.add_row().cells
|
cells = hours_table.add_row().cells
|
||||||
@@ -2284,6 +2291,16 @@ def _build_work_order_service_records_docx_bytes(work_order, related_tasks=None)
|
|||||||
cells = hours_table.add_row().cells
|
cells = hours_table.add_row().cells
|
||||||
for index in range(9):
|
for index in range(9):
|
||||||
cells[index].text = '-'
|
cells[index].text = '-'
|
||||||
|
cells = hours_table.add_row().cells
|
||||||
|
cells[0].text = 'Ukupno'
|
||||||
|
cells[1].text = ''
|
||||||
|
cells[2].text = ''
|
||||||
|
cells[3].text = ''
|
||||||
|
cells[4].text = ''
|
||||||
|
cells[5].text = _format_decimal_display(total_work_hours, default='0')
|
||||||
|
cells[6].text = _format_decimal_display(total_travel_hours, default='0')
|
||||||
|
cells[7].text = ''
|
||||||
|
cells[8].text = ''
|
||||||
_docx_move_table_after_paragraph_text(doc, hours_table, 'Tablica radnih sati')
|
_docx_move_table_after_paragraph_text(doc, hours_table, 'Tablica radnih sati')
|
||||||
_docx_cleanup_service_report_template(doc)
|
_docx_cleanup_service_report_template(doc)
|
||||||
_docx_remove_empty_page_break_paragraphs(doc)
|
_docx_remove_empty_page_break_paragraphs(doc)
|
||||||
@@ -2935,40 +2952,27 @@ def _unique_zip_entry_name(entry_name, used_names):
|
|||||||
return candidate
|
return candidate
|
||||||
|
|
||||||
|
|
||||||
def _tmp_archive_dir():
|
def _parse_year_month_params(request):
|
||||||
return Path(tempfile.gettempdir()) / TMP_ARCHIVE_DIRNAME
|
from datetime import date as _dt_date
|
||||||
|
|
||||||
|
raw_year = request.data.get('year') if isinstance(getattr(request, 'data', None), dict) else None
|
||||||
|
raw_month = request.data.get('month') if isinstance(getattr(request, 'data', None), dict) else None
|
||||||
|
if raw_year in (None, ''):
|
||||||
|
raw_year = request.query_params.get('year', _dt_date.today().year)
|
||||||
|
if raw_month in (None, ''):
|
||||||
|
raw_month = request.query_params.get('month', _dt_date.today().month)
|
||||||
|
|
||||||
|
try:
|
||||||
|
year = int(raw_year)
|
||||||
|
month = int(raw_month)
|
||||||
|
if not (1 <= month <= 12):
|
||||||
|
raise ValueError()
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
raise DRFValidationError({'detail': 'Nevažeći year/month parametar.'})
|
||||||
|
return year, month
|
||||||
|
|
||||||
|
|
||||||
def _create_tmp_archive_path(*, prefix):
|
def _build_monthly_service_tasks_archive_content(*, user, year, month):
|
||||||
target_dir = _tmp_archive_dir()
|
|
||||||
target_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
with tempfile.NamedTemporaryFile(
|
|
||||||
mode='wb',
|
|
||||||
suffix='.zip',
|
|
||||||
prefix=prefix,
|
|
||||||
dir=target_dir,
|
|
||||||
delete=False,
|
|
||||||
) as handle:
|
|
||||||
return Path(handle.name)
|
|
||||||
|
|
||||||
|
|
||||||
def _cleanup_stale_tmp_archives(*, retention_hours=TMP_ARCHIVE_RETENTION_HOURS):
|
|
||||||
target_dir = _tmp_archive_dir()
|
|
||||||
if not target_dir.exists():
|
|
||||||
return 0
|
|
||||||
cutoff_ts = timezone.now().timestamp() - (retention_hours * 3600)
|
|
||||||
deleted = 0
|
|
||||||
for item in target_dir.glob('*.zip'):
|
|
||||||
try:
|
|
||||||
if item.stat().st_mtime <= cutoff_ts:
|
|
||||||
item.unlink(missing_ok=True)
|
|
||||||
deleted += 1
|
|
||||||
except OSError:
|
|
||||||
logger.exception("Greška pri čišćenju privremene ZIP datoteke %s", item)
|
|
||||||
return deleted
|
|
||||||
|
|
||||||
|
|
||||||
def _build_monthly_service_tasks_archive_to_temp_file(*, user, year, month):
|
|
||||||
from modules.task_management.models import Task
|
from modules.task_management.models import Task
|
||||||
|
|
||||||
tasks = list(
|
tasks = list(
|
||||||
@@ -2988,26 +2992,24 @@ def _build_monthly_service_tasks_archive_to_temp_file(*, user, year, month):
|
|||||||
raise DRFValidationError({'detail': 'Nema servisnih taskova za odabrani mjesec.'})
|
raise DRFValidationError({'detail': 'Nema servisnih taskova za odabrani mjesec.'})
|
||||||
|
|
||||||
used_names = set()
|
used_names = set()
|
||||||
tmp_path = _create_tmp_archive_path(prefix='monthly-service-tasks-')
|
archive_buffer = BytesIO()
|
||||||
try:
|
with zipfile.ZipFile(archive_buffer, mode='w', compression=zipfile.ZIP_DEFLATED) as archive:
|
||||||
with zipfile.ZipFile(str(tmp_path), mode='w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as archive:
|
for task in tasks:
|
||||||
for task in tasks:
|
work_order = task.work_order
|
||||||
work_order = task.work_order
|
if work_order is None:
|
||||||
if work_order is None:
|
continue
|
||||||
continue
|
docx_bytes = _build_work_order_service_records_docx_bytes(work_order, related_tasks=[task])
|
||||||
docx_bytes = _build_work_order_service_records_docx_bytes(work_order, related_tasks=[task])
|
base_name = _service_records_docx_filename(work_order, task)
|
||||||
base_name = _service_records_docx_filename(work_order, task)
|
entry_name = _unique_zip_entry_name(base_name, used_names)
|
||||||
entry_name = _unique_zip_entry_name(base_name, used_names)
|
archive.writestr(entry_name, docx_bytes)
|
||||||
archive.writestr(entry_name, docx_bytes)
|
|
||||||
if tmp_path.stat().st_size <= 0:
|
archive_content = archive_buffer.getvalue()
|
||||||
raise DRFValidationError({'detail': 'Nije moguće kreirati ZIP za odabrani mjesec.'})
|
if not archive_content:
|
||||||
return tmp_path
|
raise DRFValidationError({'detail': 'Nije moguće kreirati ZIP za odabrani mjesec.'})
|
||||||
except Exception:
|
return archive_content
|
||||||
tmp_path.unlink(missing_ok=True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def _build_monthly_work_orders_archive_to_temp_file(*, user, year, month):
|
def _build_monthly_work_orders_archive_content(*, user, year, month):
|
||||||
from modules.task_management.models import Task
|
from modules.task_management.models import Task
|
||||||
|
|
||||||
monthly_tasks = (
|
monthly_tasks = (
|
||||||
@@ -3046,73 +3048,27 @@ def _build_monthly_work_orders_archive_to_temp_file(*, user, year, month):
|
|||||||
raise DRFValidationError({'detail': 'Nema putnih naloga za odabrani mjesec.'})
|
raise DRFValidationError({'detail': 'Nema putnih naloga za odabrani mjesec.'})
|
||||||
|
|
||||||
used_names = set()
|
used_names = set()
|
||||||
tmp_path = _create_tmp_archive_path(prefix='monthly-work-orders-')
|
archive_buffer = BytesIO()
|
||||||
try:
|
with zipfile.ZipFile(archive_buffer, mode='w', compression=zipfile.ZIP_DEFLATED) as archive:
|
||||||
with zipfile.ZipFile(str(tmp_path), mode='w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as archive:
|
for work_order in work_orders:
|
||||||
for work_order in work_orders:
|
pdf_bytes = _build_work_order_pdf(work_order)
|
||||||
pdf_bytes = _build_work_order_pdf(work_order)
|
work_order_pdf_name = _unique_zip_entry_name(_pdf_filename(work_order, 'work_order'), used_names)
|
||||||
work_order_pdf_name = _unique_zip_entry_name(_pdf_filename(work_order, 'work_order'), used_names)
|
archive.writestr(work_order_pdf_name, pdf_bytes)
|
||||||
archive.writestr(work_order_pdf_name, pdf_bytes)
|
|
||||||
|
|
||||||
display_code = _work_order_display_code(work_order)
|
display_code = _work_order_display_code(work_order)
|
||||||
invoices = work_order.invoices.filter(is_active=True).order_by('datum', 'created_at')
|
invoices = work_order.invoices.filter(is_active=True).order_by('datum', 'created_at')
|
||||||
for index, invoice in enumerate(invoices, start=1):
|
for index, invoice in enumerate(invoices, start=1):
|
||||||
if not invoice.image:
|
attachment = _file_attachment(invoice.image, fallback_name=f"invoice-{index}.bin")
|
||||||
continue
|
if not attachment:
|
||||||
invoice_filename = Path(str(getattr(invoice.image, 'name', '') or f"invoice-{index}.bin")).name
|
continue
|
||||||
archive_path = _unique_zip_entry_name(f"Racuni/{display_code}/{invoice_filename}", used_names)
|
invoice_filename, content, _content_type = attachment
|
||||||
try:
|
archive_path = f"Racuni/{display_code}/{invoice_filename}"
|
||||||
invoice.image.open('rb')
|
archive.writestr(_unique_zip_entry_name(archive_path, used_names), content)
|
||||||
with archive.open(archive_path, mode='w') as dest:
|
|
||||||
while True:
|
|
||||||
chunk = invoice.image.read(1024 * 1024)
|
|
||||||
if not chunk:
|
|
||||||
break
|
|
||||||
dest.write(chunk)
|
|
||||||
finally:
|
|
||||||
invoice.image.close()
|
|
||||||
if tmp_path.stat().st_size <= 0:
|
|
||||||
raise DRFValidationError({'detail': 'Nije moguće kreirati ZIP za odabrani mjesec.'})
|
|
||||||
return tmp_path
|
|
||||||
except Exception:
|
|
||||||
tmp_path.unlink(missing_ok=True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
archive_content = archive_buffer.getvalue()
|
||||||
def _parse_year_month_params(request):
|
if not archive_content:
|
||||||
from datetime import date as _dt_date
|
raise DRFValidationError({'detail': 'Nije moguće kreirati ZIP za odabrani mjesec.'})
|
||||||
|
return archive_content
|
||||||
raw_year = request.data.get('year') if isinstance(getattr(request, 'data', None), dict) else None
|
|
||||||
raw_month = request.data.get('month') if isinstance(getattr(request, 'data', None), dict) else None
|
|
||||||
if raw_year in (None, ''):
|
|
||||||
raw_year = request.query_params.get('year', _dt_date.today().year)
|
|
||||||
if raw_month in (None, ''):
|
|
||||||
raw_month = request.query_params.get('month', _dt_date.today().month)
|
|
||||||
|
|
||||||
try:
|
|
||||||
year = int(raw_year)
|
|
||||||
month = int(raw_month)
|
|
||||||
if not (1 <= month <= 12):
|
|
||||||
raise ValueError()
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
raise DRFValidationError({'detail': 'Nevažeći year/month parametar.'})
|
|
||||||
return year, month
|
|
||||||
|
|
||||||
|
|
||||||
def _build_monthly_service_tasks_archive_content(*, user, year, month):
|
|
||||||
tmp_path = _build_monthly_service_tasks_archive_to_temp_file(user=user, year=year, month=month)
|
|
||||||
try:
|
|
||||||
return tmp_path.read_bytes()
|
|
||||||
finally:
|
|
||||||
tmp_path.unlink(missing_ok=True)
|
|
||||||
|
|
||||||
|
|
||||||
def _build_monthly_work_orders_archive_content(*, user, year, month):
|
|
||||||
tmp_path = _build_monthly_work_orders_archive_to_temp_file(user=user, year=year, month=month)
|
|
||||||
try:
|
|
||||||
return tmp_path.read_bytes()
|
|
||||||
finally:
|
|
||||||
tmp_path.unlink(missing_ok=True)
|
|
||||||
|
|
||||||
|
|
||||||
def _cleanup_expired_generated_archive_records():
|
def _cleanup_expired_generated_archive_records():
|
||||||
@@ -3129,7 +3085,6 @@ def _cleanup_expired_generated_archive_records():
|
|||||||
item.status = 'failed'
|
item.status = 'failed'
|
||||||
item.error_message = 'ZIP arhiva je istekla.'
|
item.error_message = 'ZIP arhiva je istekla.'
|
||||||
item.save(update_fields=['is_active', 'status', 'error_message', 'updated_at'])
|
item.save(update_fields=['is_active', 'status', 'error_message', 'updated_at'])
|
||||||
_cleanup_stale_tmp_archives()
|
|
||||||
|
|
||||||
|
|
||||||
def _notify_monthly_archive_request(*, user, archive_type, stage, year, month, generated_archive=None):
|
def _notify_monthly_archive_request(*, user, archive_type, stage, year, month, generated_archive=None):
|
||||||
@@ -3421,8 +3376,11 @@ def generated_archive_download(request, archive_id):
|
|||||||
raise DRFValidationError({'detail': 'ZIP arhiva nije dostupna ili je istekla.'})
|
raise DRFValidationError({'detail': 'ZIP arhiva nije dostupna ili je istekla.'})
|
||||||
|
|
||||||
generated_archive.file.open('rb')
|
generated_archive.file.open('rb')
|
||||||
if generated_archive.file.size <= 0:
|
try:
|
||||||
|
archive_bytes = generated_archive.file.read()
|
||||||
|
finally:
|
||||||
generated_archive.file.close()
|
generated_archive.file.close()
|
||||||
|
if not archive_bytes:
|
||||||
raise DRFValidationError({'detail': 'ZIP arhiva je prazna ili nedostupna.'})
|
raise DRFValidationError({'detail': 'ZIP arhiva je prazna ili nedostupna.'})
|
||||||
filename = generated_archive.filename or _generated_archive_filename_for_user(
|
filename = generated_archive.filename or _generated_archive_filename_for_user(
|
||||||
request.user,
|
request.user,
|
||||||
@@ -3430,9 +3388,10 @@ def generated_archive_download(request, archive_id):
|
|||||||
month=generated_archive.month,
|
month=generated_archive.month,
|
||||||
archive_type=generated_archive.archive_type,
|
archive_type=generated_archive.archive_type,
|
||||||
)
|
)
|
||||||
response = FileResponse(generated_archive.file, content_type='application/zip')
|
response = HttpResponse(archive_bytes, content_type='application/zip')
|
||||||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||||||
response['Cache-Control'] = 'private, max-age=3600'
|
response['Cache-Control'] = 'private, max-age=3600'
|
||||||
|
response['Content-Length'] = str(len(archive_bytes))
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user