from datetime import timedelta from tempfile import NamedTemporaryFile from django.http import HttpResponse from openpyxl import Workbook from openpyxl.styles import Font from openpyxl.utils import get_column_letter from .utils import format_d_m_Y openxml_contenttype = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' class OpenXMLExport: def __init__(self, sheet_title=None): self.wb = Workbook() self.ws = self.wb.active if sheet_title: self.ws.title = sheet_title self.bold = Font(name='Calibri', bold=True) self.row_idx = 1 def write_line(self, values, bold=False, col_widths=()): # A values item can be an object with a `value` attribute for col_idx, value in enumerate(values, start=1): cell = self.ws.cell(row=self.row_idx, column=col_idx) if isinstance(value, timedelta): cell.number_format = '[h]:mm;@' elif hasattr(value, 'number_format'): cell.number_format = value.number_format cell.value = getattr(value, 'value', value) if bold: cell.font = self.bold if col_widths and len(col_widths) >= col_idx: self.ws.column_dimensions[get_column_letter(col_idx)].width = col_widths[col_idx - 1] self.row_idx += 1 def add_sheet(self, title): self.wb.create_sheet(title) self.ws = self.wb[title] self.row_idx = 1 def get_http_response(self, filename): with NamedTemporaryFile() as tmp: self.wb.save(tmp.name) tmp.seek(0) response = HttpResponse(tmp, content_type=openxml_contenttype) response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename) return response class ExportReporting(OpenXMLExport): def __init__(self): super().__init__('temp') self.first_sheet = True # Totaux pour vérification interne dans les tests. self._total_spe = {'eval': timedelta(0), 'suivi': timedelta(0), 'gen': timedelta(0)} def setup_sheet(self, title): if not self.first_sheet: self.add_sheet(title) else: self.ws.title = title self.first_sheet = False def produce_suivis(self, sheet_title, query, mois): self.setup_sheet(sheet_title) SuiviSheet(self, mois).produce(query) def produce_nouveaux(self, sheet_title, query): self.setup_sheet(sheet_title) NouveauxSheet(self).produce(query) def produce_termines(self, sheet_title, query): self.setup_sheet(sheet_title) TerminesSheet(self).produce(query) def produce_totaux(self, sheet_title): self.setup_sheet(sheet_title) self.write_line(['Évaluation', self._total_spe['eval']], col_widths=[25, 10]) self.write_line(['Accompagnement', self._total_spe['suivi']]) self.write_line(['Intervention générale', self._total_spe['gen']]) self.write_line(['Total', self._total_spe['eval'] + self._total_spe['suivi'] + self._total_spe['gen']]) class BaseFamilleSheet: en_tetes = [ ('Institution', 12), ('Prestation', 10), ('Nom', 25), ('Prenom', 15), ('Genre', 8), ('Date de naissance', 17), ('Adresse', 20), ('NPA', 8), ('Localité', 20), ('Canton', 8), ('OPE', 25), ('Nom mère', 20), ('Prénom mère', 20), ('Nom père', 20), ('Prénom père', 20), ('Autorité parentale', 18), ('Statut marital', 15), ('Statut financier', 15), ('Fam. monopar.', 15), ('Nbre enfants', 12), ('Date demande', 15), ('Provenance', 15), ('Motif demande', 40), ('Début suivi', 15), ] def __init__(self, exp): self.exp = exp def _set_col_dimensions(self): for col_idx, (_, size) in enumerate(self.en_tetes, start=1): self.exp.ws.column_dimensions[get_column_letter(col_idx)].width = size def produce(self, query): self._set_col_dimensions() self.exp.write_line([et[0] for et in self.en_tetes], bold=True) for famille in query: self.produce_famille(famille) self.exp.ws.freeze_panes = self.exp.ws['A2'] def produce_famille(self, famille): membres_suivis = famille.membres_suivis() if membres_suivis: for pers in famille.membres_suivis(): data = self.collect_pers_data(famille, pers) self.exp.write_line(data) else: data = self.collect_pers_data(famille, None) self.exp.write_line(data) def collect_pers_data(self, famille, pers): parents = famille.parents() mere = next((par for par in parents if par.role.nom == 'Mère'), None) pere = next((par for par in parents if par.role.nom == 'Père'), None) return [ 'Fondation Transit', 'AEMO', pers.nom if pers else famille.nom, pers.prenom if pers else "-", pers.genre if pers else "-", format_d_m_Y(pers.date_naissance) if pers else "-", pers.rue if pers else famille.rue, pers.npa if pers else famille.npa, pers.localite if pers else famille.localite, 'NE', famille.suivi.ope_referent.nom_prenom if famille.suivi.ope_referent else '', mere.nom if mere else '', mere.prenom if mere else '', pere.nom if pere else '', pere.prenom if pere else '', famille.get_autorite_parentale_display(), famille.get_statut_marital_display(), famille.get_statut_financier_display(), {True: 'OUI', False: 'NON', None: ''}[famille.monoparentale], len(famille.membres_suivis()) + len(famille.enfants_non_suivis()), format_d_m_Y(famille.suivi.date_demande), famille.get_provenance_display(), famille.suivi.get_motif_demande_display(), format_d_m_Y(famille.suivi.date_debut_suivi), ] class SuiviSheet(BaseFamilleSheet): en_tetes = BaseFamilleSheet.en_tetes + [('H. Évaluation', 12), ('H. Suivi', 12), ('H. Prest. gén.', 12)] def __init__(self, exp, date_debut_mois): self.date_debut_mois = date_debut_mois super().__init__(exp) def produce_famille(self, famille): # Prepare some data common to famille nb_membres_suivis = len(famille.membres_suivis()) or 1 famille._h_evaluation_par_pers = famille.total_mensuel_evaluation(self.date_debut_mois) // nb_membres_suivis famille._h_suivi_par_pers = famille.total_mensuel_suivi(self.date_debut_mois) // nb_membres_suivis super().produce_famille(famille) def collect_pers_data(self, famille, pers): data = super().collect_pers_data(famille, pers) h_evaluation = famille._h_evaluation_par_pers h_suivi = famille._h_suivi_par_pers h_prest_gen = famille.prest_gen # Annotation from the view. data.extend([h_evaluation, h_suivi, h_prest_gen]) # Variables des totaux self.exp._total_spe['eval'] += h_evaluation self.exp._total_spe['suivi'] += h_suivi self.exp._total_spe['gen'] += h_prest_gen return data class NouveauxSheet(BaseFamilleSheet): pass class TerminesSheet(BaseFamilleSheet): en_tetes = BaseFamilleSheet.en_tetes + [ ('Date fin suivi', 15), ('Motif fin suivi', 15), ('Destination', 15), ('Total heures', 12), ] def collect_pers_data(self, famille, pers): data = super().collect_pers_data(famille, pers) suivi = famille.suivi data.extend([ format_d_m_Y(suivi.date_fin_suivi), famille.suivi.get_motif_fin_suivi_display(), famille.get_destination_display(), famille.temps_total_prestations_reparti(), ]) return data class ExportStatistique(OpenXMLExport): def __init__(self, *args, col_widths=None, **kwargs): self.col_widths = col_widths super().__init__(*args, **kwargs) def fill_data(self, generator): for row in generator: if row and row[0] == 'BOLD': self.write_line(row[1:], bold=True, col_widths=self.col_widths) else: self.write_line(row, col_widths=self.col_widths)