579 lines
22 KiB
Python
579 lines
22 KiB
Python
"""
|
|
Rapport journalier des interventions encodées par les membres d'une Company.
|
|
|
|
Génère un PDF et un Excel contenant :
|
|
- Un tableau de synthèse par thématique (lignes) avec colonnes :
|
|
Avarie | Défaut/Panne | Demande | StreetUp | PREGO | Terminées | Autres | Total
|
|
- Un tableau par personne (nombre d'interventions créées)
|
|
"""
|
|
|
|
import io
|
|
import os
|
|
import zipfile
|
|
from collections import defaultdict
|
|
from datetime import datetime, timedelta
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.http import HttpResponse, Http404
|
|
from django.utils import timezone
|
|
from django.utils.translation import gettext as _
|
|
|
|
from fpdf import FPDF
|
|
from openpyxl import Workbook
|
|
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
|
|
from openpyxl.utils import get_column_letter
|
|
|
|
from contracts.models import CompanyMember, CompanyTeam
|
|
from common.models import Thematic
|
|
from interventions.models import Intervention, STATUS_ORDERS
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers : collecte des données
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _get_authorized_companies_for_user(user):
|
|
"""
|
|
Retourne la liste des Company distinctes dont l'utilisateur est membre
|
|
via une équipe ayant can_generate_daily_report=True.
|
|
Un utilisateur peut appartenir à plusieurs équipes ; toutes sont vérifiées.
|
|
"""
|
|
members = (
|
|
CompanyMember.objects
|
|
.filter(user=user, teams__can_generate_daily_report=True)
|
|
.prefetch_related('teams__company')
|
|
)
|
|
seen = {}
|
|
for m in members:
|
|
for t in m.teams.all():
|
|
if t.can_generate_daily_report:
|
|
c = t.company
|
|
seen[c.id] = c
|
|
return list(seen.values())
|
|
|
|
|
|
def _collect_report_data(companies, report_date):
|
|
"""
|
|
Collecte les données du rapport journalier pour une liste de Company.
|
|
|
|
La fenêtre temporelle couvre de 20h00 (j-1) à 20h00 (j), heure de Bruxelles.
|
|
|
|
Retourne un dict :
|
|
{
|
|
'by_thematic': {thematic_name: {col_key: count, ...}, ...},
|
|
'by_person': {person_name: count, ...},
|
|
'totals': {col_key: count, ...}, # ligne totale
|
|
'total_all': int,
|
|
}
|
|
"""
|
|
import pytz
|
|
brussels = pytz.timezone('Europe/Brussels')
|
|
|
|
# Fenêtre : 20h00 j-1 → 20h00 j (heure de Bruxelles)
|
|
prev_day = report_date - timedelta(days=1)
|
|
period_start = brussels.localize(datetime(prev_day.year, prev_day.month, prev_day.day, 20, 0, 0))
|
|
period_end = brussels.localize(datetime(report_date.year, report_date.month, report_date.day, 20, 0, 0))
|
|
|
|
company_ids = [c.id for c in companies]
|
|
|
|
# Récupérer tous les utilisateurs membres des Companies concernées
|
|
company_members = CompanyMember.objects.filter(
|
|
teams__company_id__in=company_ids
|
|
).select_related('user').prefetch_related('teams__company')
|
|
|
|
member_user_ids = list(company_members.values_list('user_id', flat=True))
|
|
# En cas de doublons (même user dans plusieurs équipes), on garde le premier nom trouvé
|
|
member_name_map = {}
|
|
for m in company_members:
|
|
if m.user_id not in member_name_map:
|
|
member_name_map[m.user_id] = m.name
|
|
|
|
# Interventions créées dans la fenêtre par des membres des Companies
|
|
# Uniquement les thématiques avec include_in_daily_report=True ; annulées exclues
|
|
interventions = (
|
|
Intervention.objects
|
|
.filter(
|
|
created_by_id__in=member_user_ids,
|
|
creation_time__gte=period_start,
|
|
creation_time__lt=period_end,
|
|
thematic__include_in_daily_report=True,
|
|
)
|
|
.exclude(status__in=['in_preparation','canceled'])
|
|
.select_related('thematic', 'source_category')
|
|
)
|
|
|
|
# Colonnes
|
|
COL_KEYS = [
|
|
'damage', 'defect', 'demand',
|
|
'observation',
|
|
]
|
|
dynamic_cols = getattr(settings, 'DAILY_REPORT_SOURCE_COLUMNS', {})
|
|
for key in dynamic_cols.keys():
|
|
if key not in COL_KEYS:
|
|
COL_KEYS.append(key)
|
|
COL_KEYS.extend(['finished', 'other_status', 'total'])
|
|
|
|
by_thematic = defaultdict(lambda: {k: 0 for k in COL_KEYS})
|
|
by_person = defaultdict(int)
|
|
totals = {k: 0 for k in COL_KEYS}
|
|
|
|
# Pré-peupler avec toutes les thématiques marquées "inclure dans rapport journalier"
|
|
for thematic in Thematic.objects.filter(include_in_daily_report=True).order_by('name_fr'):
|
|
_ = by_thematic[thematic.name_fr] # initialise la ligne à zéros
|
|
|
|
for itv in interventions:
|
|
thematic_name = itv.thematic.name_fr if itv.thematic else "Sans thématique"
|
|
row = by_thematic[thematic_name]
|
|
|
|
# --- Origine ---
|
|
origin = itv.origin_type or ''
|
|
if origin == 'damage':
|
|
row['damage'] += 1
|
|
totals['damage'] += 1
|
|
elif origin == 'defect':
|
|
row['defect'] += 1
|
|
totals['defect'] += 1
|
|
elif origin == 'demand':
|
|
row['demand'] += 1
|
|
totals['demand'] += 1
|
|
|
|
# --- Source category ---
|
|
sc = itv.source_category
|
|
if sc:
|
|
sc_name = (sc.name_fr or '').strip().lower()
|
|
if sc_name == 'observation':
|
|
row['observation'] += 1
|
|
totals['observation'] += 1
|
|
elif sc_name in dynamic_cols:
|
|
row[sc_name] += 1
|
|
totals[sc_name] += 1
|
|
|
|
# --- Statut ---
|
|
if itv.status_order >= STATUS_ORDERS['finished']:
|
|
row['finished'] += 1
|
|
totals['finished'] += 1
|
|
else:
|
|
row['other_status'] += 1
|
|
totals['other_status'] += 1
|
|
|
|
# --- Total ---
|
|
row['total'] += 1
|
|
totals['total'] += 1
|
|
|
|
# --- Par personne ---
|
|
person_name = member_name_map.get(itv.created_by_id, str(itv.created_by))
|
|
by_person[person_name] += 1
|
|
|
|
return {
|
|
'by_thematic': dict(by_thematic),
|
|
'by_person': dict(by_person),
|
|
'totals': totals,
|
|
'total_all': totals['total'],
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Colonnes du tableau de synthèse (labels)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
COLUMN_HEADERS = [
|
|
('damage', 'Avarie'),
|
|
('defect', 'Défaut/Panne'),
|
|
('demand', 'Demande'),
|
|
('observation', 'StreetUp'),
|
|
]
|
|
|
|
# Add dynamic source columns from settings
|
|
dynamic_cols = getattr(settings, 'DAILY_REPORT_SOURCE_COLUMNS', {})
|
|
for key, cfg in dynamic_cols.items():
|
|
COLUMN_HEADERS.append((key, cfg['label']))
|
|
|
|
COLUMN_HEADERS.extend([
|
|
('finished', 'Résolu'),
|
|
('other_status', 'En cours'),
|
|
('total', 'Total'),
|
|
])
|
|
|
|
# Couleurs spéciales (R, G, B) pour l'en-tête et la ligne totale de certaines colonnes
|
|
# Les colonnes absentes de ce dict héritent de la couleur par défaut.
|
|
COLUMN_SPECIAL_COLORS_RGB = {
|
|
'finished': (112, 173, 71), # Vert
|
|
'other_status': ( 0, 176, 240), # Bleu ciel
|
|
}
|
|
# Version hex pour Excel (AARRGGBB sans alpha, format RRGGBB)
|
|
COLUMN_SPECIAL_COLORS_HEX = {
|
|
'finished': '70AD47',
|
|
'other_status': '00B0F0',
|
|
}
|
|
|
|
for key, cfg in dynamic_cols.items():
|
|
if 'color_rgb' in cfg:
|
|
COLUMN_SPECIAL_COLORS_RGB[key] = cfg['color_rgb']
|
|
if 'color_hex' in cfg:
|
|
COLUMN_SPECIAL_COLORS_HEX[key] = cfg['color_hex']
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PDF generation
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class DailyReportPDF(FPDF):
|
|
|
|
def __init__(self, company_name, report_date, *args, **kwargs):
|
|
super().__init__(orientation='L', *args, **kwargs)
|
|
self.company_name = company_name
|
|
self.report_date = report_date
|
|
|
|
# ---- header / footer ----
|
|
def header(self):
|
|
font_dir = os.path.join(settings.BASE_DIR, "common", "static", "common")
|
|
self.add_font("arial", style="", fname=os.path.join(font_dir, "arial.ttf"))
|
|
self.add_font("arial", style="B", fname=os.path.join(font_dir, "arial_bold.ttf"))
|
|
self.add_font("arial", style="I", fname=os.path.join(font_dir, "arial_italic.ttf"))
|
|
|
|
logo_path = os.path.join(settings.BASE_DIR, "common", "static", settings.ORGANIZATION_LOGO_PNG)
|
|
if os.path.exists(logo_path):
|
|
self.image(logo_path, x=10, y=8, w=50)
|
|
|
|
self.set_font("arial", "B", 14)
|
|
self.set_y(10)
|
|
self.cell(0, 8, _("Rapport journalier des interventions"), align="C", new_x="LMARGIN", new_y="NEXT")
|
|
self.set_font("arial", "", 11)
|
|
self.cell(0, 7, f"{self.company_name} — {self.report_date.strftime('%d/%m/%Y')}", align="C", new_x="LMARGIN", new_y="NEXT")
|
|
self.ln(4)
|
|
|
|
def footer(self):
|
|
self.set_y(-15)
|
|
self.set_font("arial", "I", 8)
|
|
self.cell(0, 10, f"Page {self.page_no()}/{{nb}}", align="C")
|
|
|
|
# ---- section title ----
|
|
def _section_title(self, text):
|
|
self.set_font("arial", "B", 11)
|
|
self.set_fill_color(*settings.ORGANIZATION_PRIMARY_COLOR_RGB)
|
|
self.set_text_color(255, 255, 255)
|
|
self.cell(0, 8, f" {text}", fill=True, new_x="LMARGIN", new_y="NEXT")
|
|
self.set_text_color(0, 0, 0)
|
|
self.ln(2)
|
|
|
|
# ---- table : synthèse par thématique ----
|
|
def add_thematic_table(self, data):
|
|
self._section_title(_("Synthèse par thématique"))
|
|
|
|
col_widths = [60] + [25] * len(COLUMN_HEADERS) # thématique + cols
|
|
headers = [_("Thématique")] + [label for _lbl, label in COLUMN_HEADERS]
|
|
|
|
# Header row — hauteur fixe pour toutes les cellules, texte centré verticalement
|
|
HEADER_ROW_H = 14 # mm
|
|
LINE_H = 4.5 # hauteur par ligne de texte
|
|
self.set_font("arial", "B", 8)
|
|
self.set_draw_color(0, 0, 0)
|
|
x0 = self.get_x()
|
|
y0 = self.get_y()
|
|
cumulative_x = x0
|
|
for i, h in enumerate(headers):
|
|
# Couleur de fond : spéciale si colonne concernée, sinon bleu clair
|
|
if i >= 1:
|
|
col_key = COLUMN_HEADERS[i - 1][0]
|
|
r, g, b = COLUMN_SPECIAL_COLORS_RGB.get(col_key, (220, 230, 241))
|
|
else:
|
|
r, g, b = 220, 230, 241
|
|
self.set_fill_color(r, g, b)
|
|
# Rectangle plein + bordure à hauteur fixe
|
|
self.rect(cumulative_x, y0, col_widths[i], HEADER_ROW_H, style='FD')
|
|
# Centrage vertical du texte
|
|
lines = str(h).split('\n')
|
|
text_block_h = len(lines) * LINE_H
|
|
text_y = y0 + (HEADER_ROW_H - text_block_h) / 2
|
|
for k, line in enumerate(lines):
|
|
self.set_xy(cumulative_x, text_y + k * LINE_H)
|
|
self.cell(col_widths[i], LINE_H, line, align='C')
|
|
cumulative_x += col_widths[i]
|
|
self.set_xy(x0, y0 + HEADER_ROW_H)
|
|
|
|
# Data rows
|
|
self.set_font("arial", "", 8)
|
|
fill = False
|
|
for thematic_name in sorted(data['by_thematic'].keys()):
|
|
row = data['by_thematic'][thematic_name]
|
|
if fill:
|
|
self.set_fill_color(245, 245, 245)
|
|
else:
|
|
self.set_fill_color(255, 255, 255)
|
|
|
|
self.cell(col_widths[0], 6, str(thematic_name), border=1, fill=True, align="L")
|
|
for j, (key, _lbl) in enumerate(COLUMN_HEADERS):
|
|
val = row.get(key, 0)
|
|
self.cell(col_widths[j + 1], 6, str(val), border=1, fill=True, align="C")
|
|
self.ln()
|
|
fill = not fill
|
|
|
|
# Totals row
|
|
self.set_font("arial", "B", 8)
|
|
self.set_text_color(255, 255, 255)
|
|
self.set_fill_color(0, 91, 153)
|
|
self.cell(col_widths[0], 7, _("TOTAL"), border=1, fill=True, align="L")
|
|
for j, (key, _lbl) in enumerate(COLUMN_HEADERS):
|
|
r, g, b = COLUMN_SPECIAL_COLORS_RGB.get(key, (0, 91, 153))
|
|
self.set_fill_color(r, g, b)
|
|
val = data['totals'].get(key, 0)
|
|
self.cell(col_widths[j + 1], 7, str(val), border=1, fill=True, align="C")
|
|
self.ln()
|
|
self.set_text_color(0, 0, 0)
|
|
self.ln(4)
|
|
|
|
# ---- table : par personne ----
|
|
def add_person_table(self, data):
|
|
self._section_title(_("Nombre d'interventions par personne"))
|
|
|
|
col_widths = [100, 40]
|
|
headers = [_("Personne"), _("Nombre")]
|
|
|
|
self.set_font("arial", "B", 9)
|
|
self.set_fill_color(220, 230, 241)
|
|
for i, h in enumerate(headers):
|
|
self.cell(col_widths[i], 7, str(h), border=1, fill=True, align="C")
|
|
self.ln()
|
|
|
|
self.set_font("arial", "", 9)
|
|
fill = False
|
|
total = 0
|
|
for person_name in sorted(data['by_person'].keys()):
|
|
count = data['by_person'][person_name]
|
|
total += count
|
|
if fill:
|
|
self.set_fill_color(245, 245, 245)
|
|
else:
|
|
self.set_fill_color(255, 255, 255)
|
|
self.cell(col_widths[0], 6, str(person_name), border=1, fill=True, align="L")
|
|
self.cell(col_widths[1], 6, str(count), border=1, fill=True, align="C")
|
|
self.ln()
|
|
fill = not fill
|
|
|
|
# Total
|
|
self.set_font("arial", "B", 9)
|
|
self.set_fill_color(0, 91, 153)
|
|
self.set_text_color(255, 255, 255)
|
|
self.cell(col_widths[0], 7, _("TOTAL"), border=1, fill=True, align="L")
|
|
self.cell(col_widths[1], 7, str(total), border=1, fill=True, align="C")
|
|
self.ln()
|
|
self.set_text_color(0, 0, 0)
|
|
|
|
|
|
def generate_daily_report_pdf(company_name, report_date, data):
|
|
"""Génère le PDF et retourne les bytes."""
|
|
pdf = DailyReportPDF(company_name, report_date)
|
|
pdf.alias_nb_pages()
|
|
pdf.add_page()
|
|
pdf.add_thematic_table(data)
|
|
pdf.add_person_table(data)
|
|
buffer = io.BytesIO()
|
|
pdf.output(buffer)
|
|
buffer.seek(0)
|
|
return buffer.read()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Excel generation
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def generate_daily_report_excel(company_name, report_date, data):
|
|
"""Génère le fichier Excel et retourne les bytes."""
|
|
wb = Workbook()
|
|
|
|
# --- Styles ---
|
|
header_font = Font(bold=True, color="FFFFFF", size=10)
|
|
header_fill = PatternFill(start_color="005B99", end_color="005B99", fill_type="solid")
|
|
header_alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
|
total_font = Font(bold=True, color="FFFFFF", size=10)
|
|
total_fill = PatternFill(start_color="005B99", end_color="005B99", fill_type="solid")
|
|
thin_border = Border(
|
|
left=Side(style='thin'),
|
|
right=Side(style='thin'),
|
|
top=Side(style='thin'),
|
|
bottom=Side(style='thin'),
|
|
)
|
|
alt_fill = PatternFill(start_color="F5F5F5", end_color="F5F5F5", fill_type="solid")
|
|
|
|
# Couleurs spéciales pour certaines colonnes (en-têtes et totaux)
|
|
col_special_fills = {
|
|
key: PatternFill(start_color=hex_color, end_color=hex_color, fill_type="solid")
|
|
for key, hex_color in COLUMN_SPECIAL_COLORS_HEX.items()
|
|
}
|
|
|
|
# =========== Feuille 1 : Synthèse par thématique ===========
|
|
ws1 = wb.active
|
|
ws1.title = _("Synthèse par thématique")
|
|
|
|
# Title row
|
|
ws1.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(COLUMN_HEADERS) + 1)
|
|
title_cell = ws1.cell(row=1, column=1, value=f"{_('Rapport journalier')} — {company_name} — {report_date.strftime('%d/%m/%Y')}")
|
|
title_cell.font = Font(bold=True, size=14)
|
|
title_cell.alignment = Alignment(horizontal="center")
|
|
|
|
# Headers
|
|
row_num = 3
|
|
col_headers = [_("Thématique")] + [label for _lbl, label in COLUMN_HEADERS]
|
|
for col_idx, header_text in enumerate(col_headers, 1):
|
|
cell = ws1.cell(row=row_num, column=col_idx, value=str(header_text))
|
|
cell.font = header_font
|
|
if col_idx >= 2:
|
|
cell.fill = col_special_fills.get(COLUMN_HEADERS[col_idx - 2][0], header_fill)
|
|
else:
|
|
cell.fill = header_fill
|
|
cell.alignment = header_alignment
|
|
cell.border = thin_border
|
|
|
|
# Data
|
|
row_num = 4
|
|
for idx, thematic_name in enumerate(sorted(data['by_thematic'].keys())):
|
|
row_data = data['by_thematic'][thematic_name]
|
|
cell = ws1.cell(row=row_num, column=1, value=str(thematic_name))
|
|
cell.border = thin_border
|
|
if idx % 2:
|
|
cell.fill = alt_fill
|
|
|
|
for col_idx, (key, _lbl) in enumerate(COLUMN_HEADERS, 2):
|
|
cell = ws1.cell(row=row_num, column=col_idx, value=row_data.get(key, 0))
|
|
cell.alignment = Alignment(horizontal="center")
|
|
cell.border = thin_border
|
|
if idx % 2:
|
|
cell.fill = alt_fill
|
|
row_num += 1
|
|
|
|
# Total row (formules SUM pour recalcul automatique)
|
|
data_first_row = 4
|
|
cell = ws1.cell(row=row_num, column=1, value=_("TOTAL"))
|
|
cell.font = total_font
|
|
cell.fill = total_fill
|
|
cell.border = thin_border
|
|
for col_idx, (key, _lbl) in enumerate(COLUMN_HEADERS, 2):
|
|
col_letter = get_column_letter(col_idx)
|
|
cell = ws1.cell(row=row_num, column=col_idx,
|
|
value=f"=SUM({col_letter}{data_first_row}:{col_letter}{row_num - 1})")
|
|
cell.font = total_font
|
|
cell.fill = col_special_fills.get(key, total_fill)
|
|
cell.alignment = Alignment(horizontal="center")
|
|
cell.border = thin_border
|
|
|
|
# Auto-adjust column widths
|
|
for col in range(1, len(col_headers) + 1):
|
|
ws1.column_dimensions[chr(64 + col) if col <= 26 else 'A'].width = 18 if col == 1 else 14
|
|
|
|
# =========== Feuille 2 : Par personne ===========
|
|
ws2 = wb.create_sheet(title=_("Par personne"))
|
|
|
|
ws2.merge_cells(start_row=1, start_column=1, end_row=1, end_column=2)
|
|
title_cell = ws2.cell(row=1, column=1, value=f"{_('Rapport journalier')} — {company_name} — {report_date.strftime('%d/%m/%Y')}")
|
|
title_cell.font = Font(bold=True, size=14)
|
|
title_cell.alignment = Alignment(horizontal="center")
|
|
|
|
row_num = 3
|
|
for col_idx, header_text in enumerate([_("Personne"), _("Nombre")], 1):
|
|
cell = ws2.cell(row=row_num, column=col_idx, value=str(header_text))
|
|
cell.font = header_font
|
|
cell.fill = header_fill
|
|
cell.alignment = header_alignment
|
|
cell.border = thin_border
|
|
|
|
row_num = 4
|
|
for idx, person_name in enumerate(sorted(data['by_person'].keys())):
|
|
count = data['by_person'][person_name]
|
|
cell = ws2.cell(row=row_num, column=1, value=str(person_name))
|
|
cell.border = thin_border
|
|
if idx % 2:
|
|
cell.fill = alt_fill
|
|
cell = ws2.cell(row=row_num, column=2, value=count)
|
|
cell.alignment = Alignment(horizontal="center")
|
|
cell.border = thin_border
|
|
if idx % 2:
|
|
cell.fill = alt_fill
|
|
row_num += 1
|
|
|
|
# Total (formule SUM pour recalcul automatique)
|
|
cell = ws2.cell(row=row_num, column=1, value=_("TOTAL"))
|
|
cell.font = total_font
|
|
cell.fill = total_fill
|
|
cell.border = thin_border
|
|
cell = ws2.cell(row=row_num, column=2, value=f"=SUM(B4:B{row_num - 1})")
|
|
cell.font = total_font
|
|
cell.fill = total_fill
|
|
cell.alignment = Alignment(horizontal="center")
|
|
cell.border = thin_border
|
|
|
|
ws2.column_dimensions['A'].width = 35
|
|
ws2.column_dimensions['B'].width = 15
|
|
|
|
# Output
|
|
buffer = io.BytesIO()
|
|
wb.save(buffer)
|
|
return buffer.getvalue()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Vue Django
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@login_required
|
|
def daily_report_download(request):
|
|
"""
|
|
Génère un ZIP contenant le PDF et l'Excel du rapport journalier.
|
|
Accessible uniquement aux membres d'une CompanyTeam ayant can_generate_daily_report=True.
|
|
|
|
Query params optionnels :
|
|
- date : date au format YYYY-MM-DD (défaut = aujourd'hui)
|
|
- format : 'pdf', 'excel' ou 'zip' (défaut = 'zip')
|
|
"""
|
|
# Vérifier que l'utilisateur appartient à au moins une équipe autorisée
|
|
# (toutes ses équipes sont vérifiées, pas seulement la première)
|
|
companies = _get_authorized_companies_for_user(request.user)
|
|
if not companies:
|
|
raise Http404(_("Aucune de vos équipes n'est autorisée à générer ce rapport."))
|
|
|
|
# Date du rapport
|
|
date_str = request.GET.get('date')
|
|
if date_str:
|
|
try:
|
|
report_date = datetime.strptime(date_str, '%Y-%m-%d').date()
|
|
except ValueError:
|
|
report_date = timezone.now().date()
|
|
else:
|
|
report_date = timezone.now().date()
|
|
|
|
# Collecter les données
|
|
data = _collect_report_data(companies, report_date)
|
|
company_name = " / ".join(sorted(c.name for c in companies))
|
|
date_label = report_date.strftime('%Y%m%d')
|
|
|
|
output_format = request.GET.get('format', 'zip')
|
|
|
|
if output_format == 'pdf':
|
|
pdf_bytes = generate_daily_report_pdf(company_name, report_date, data)
|
|
response = HttpResponse(pdf_bytes, content_type='application/pdf')
|
|
response['Content-Disposition'] = f'inline; filename="rapport_journalier_{date_label}.pdf"'
|
|
return response
|
|
|
|
elif output_format == 'excel':
|
|
excel_bytes = generate_daily_report_excel(company_name, report_date, data)
|
|
response = HttpResponse(
|
|
excel_bytes,
|
|
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
)
|
|
response['Content-Disposition'] = f'attachment; filename="rapport_journalier_{date_label}.xlsx"'
|
|
return response
|
|
|
|
else:
|
|
# ZIP contenant les deux
|
|
pdf_bytes = generate_daily_report_pdf(company_name, report_date, data)
|
|
excel_bytes = generate_daily_report_excel(company_name, report_date, data)
|
|
|
|
zip_buffer = io.BytesIO()
|
|
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf:
|
|
zf.writestr(f"rapport_journalier_{date_label}.pdf", pdf_bytes)
|
|
zf.writestr(f"rapport_journalier_{date_label}.xlsx", excel_bytes)
|
|
zip_buffer.seek(0)
|
|
|
|
response = HttpResponse(zip_buffer.getvalue(), content_type='application/zip')
|
|
response['Content-Disposition'] = f'attachment; filename="rapport_journalier_{date_label}.zip"'
|
|
return response
|