import os
import shutil
import tempfile
from datetime import date, timedelta
from pathlib import Path
from unittest import skip
from unittest.mock import patch

from freezegun import freeze_time

from django.conf.global_settings import PASSWORD_HASHERS
from django.contrib.auth.models import Group, Permission
from django.core.files import File
from django.db.utils import IntegrityError
from django.forms import Form
from django.forms.models import model_to_dict
from django.test import RequestFactory, TestCase, tag
from django.test.utils import override_settings
from django.urls import reverse

from .export import ExportReporting, openxml_contenttype
from .forms import (
    AgendaForm, FamilleCreateForm, HMDurationField, NiveauForm, PersonneForm,
    PrestationForm, UtilisateurForm
)
from .models import (
    Bilan, CercleScolaire, Contact, EtapeFin, Famille, Formation, Intervenant,
    JournalAcces,  LibellePrestation, Niveau, Personne, Prestation, Rapport, Region, Role,
    Service, Suivi, Utilisateur,
)
from .utils import ANTICIPATION_POUR_DEBUT_SUIVI, format_duree, format_d_m_Y
from .views_stats import Month, StatistiquesView


class InitialDataMixin:
    @classmethod
    def setUpTestData(cls):
        s1, _ = Service.objects.get_or_create(sigle='CRXNE')
        Service.objects.bulk_create([
            Service(sigle='OPEN'),
            Service(sigle='SSE')
        ])

        CercleScolaire.objects.bulk_create([
            CercleScolaire(nom='EOREN-MAIL'),
            CercleScolaire(nom='EOCF-NUMA-DROZ')
        ])

        grp_aemo = Group.objects.create(name='aemo')
        grp_aemo.permissions.add(*list(
            Permission.objects.filter(codename__in=[
                'view_famille', 'add_famille', 'add_prestation'
            ])
        ))

        Role.objects.bulk_create([
            Role(nom='Père', est_famille=True),
            Role(nom='Mère', est_famille=True),
            Role(nom='Beau-père', est_famille=True),
            Role(nom='Enfant suivi', est_famille=True),
            Role(nom='Enfant non-suivi', est_famille=True),
            Role(nom='Médecin', est_famille=False),
            Role(nom='Psy', est_famille=False, est_intervenant=True, est_editeur=True),
            Role(nom='Educ', est_famille=False, est_intervenant=True, est_editeur=True),
            Role(nom='Assistant-e social-e', est_famille=False, est_intervenant=True),
            Role(nom='Référent', est_famille=False),
        ])

        Contact.objects.bulk_create([
            Contact(nom='Sybarnez', prenom='Tina', tel_prof='032 886 88 88', service=s1),
            Contact(nom='Rathfeld', prenom='Christophe', service=Service.objects.get(sigle='OPEN')),
            Contact(nom='DrSpontz', prenom='Igor'),
        ])
        Contact.objects.get(nom='DrSpontz').roles.add(Role.objects.get(nom='Médecin'))

        Region.objects.bulk_create([
            Region(nom='Littoral Est'),
            Region(nom='Littoral Ouest'),
            Region(nom='Montagnes'),
            Region(nom='Val-de-Ruz'),
            Region(nom='Val-de-Travers'),
        ], ignore_conflicts=True)

        LibellePrestation.objects.bulk_create([
            LibellePrestation(code='aemo01', nom='Évaluation'),
            LibellePrestation(code='aemo02', nom='Accompagnement'),
            LibellePrestation(code='aemo03', nom='Prestation gén.'),
            LibellePrestation(code='aemo04', nom='Activités ASE'),
        ])

        grp_admin = Group.objects.create(name='admin')
        grp_admin.permissions.add(*Permission.objects.filter(codename__in=[
            'add_role', 'change_role', 'delete_role', 'add_service',
            'add_utilisateur', 'change_utilisateur', 'delete_utilisateur',
            'view_famille', 'change_famille', 'export_stats'
        ]))
        cls.aemo_group = Group.objects.get(name='aemo')
        cls.aemo_group.permissions.add(Permission.objects.get(codename='add_famille'))
        cls.user_admin = Utilisateur.objects.create_user(
            'user_admin', 'user_admin@example.com', nom='Admin', prenom='Prénom', sigle='ADM'
        )
        cls.user_admin.groups.add(grp_admin)
        cls.user_admin.groups.add(cls.aemo_group)
        resp = Role.objects.create(nom='Responsable/coordinateur')
        cls.user_admin.roles.add(resp)

        cls.user_aemo = Utilisateur.objects.create_user(
            'user_aemo', 'user_aemo@example.org', nom='Aemo', prenom='Prénom', sigle='SP', taux_activite=60,
        )
        cls.user_aemo.roles.add(Role.objects.get(nom='Educ'))
        cls.user_aemo2 = Utilisateur.objects.create_user(
            'user_aemo2', 'user_aemo2@example.org', nom='Aemo2', prenom='Prénom', sigle='SP2'
        )
        cls.user_aemo2.roles.add(Role.objects.get(nom='Psy'))
        cls.user_aemo.groups.add(cls.aemo_group)
        cls.user_aemo2.groups.add(cls.aemo_group)

        cls.user_externe = Utilisateur.objects.create_user(
            'externe', 'externe@example.org', 'mepassword', prenom='Bruce', nom='Externe',
        )
        cls.create_famille()

    @classmethod
    def create_famille(cls, name='Haddock'):
        famille = Famille.objects.create_famille(
            nom=name, rue='Château1', npa=2000, localite='Moulinsart',
            equipe='montagnes', autorite_parentale='conjointe', statut_marital='divorce', monoparentale=False
        )
        pere = Role.objects.get(nom='Père')
        Personne.objects.create_personne(
            famille=famille, role=pere,
            nom=name, prenom='Archibald', genre='M', date_naissance=date(1956, 2, 16),
            rue='Château1', npa=2000, localite='Moulinsart',
        )
        famille.suivi.ope_referent = Contact.objects.get(nom="Sybarnez")
        famille.suivi.save()
        Personne.objects.create_personne(
            famille=famille, role=Role.objects.get(nom='Enfant suivi'),
            nom=name, prenom='Toto', genre='M', date_naissance=date(2010, 2, 16),
            rue='Château1', npa=2000, localite='Moulinsart',
        )
        return famille


class TempMediaRootMixin:
    @classmethod
    def setUpClass(cls):
        cls._temp_media = tempfile.mkdtemp()
        cls._overridden_settings = cls._overridden_settings or {}
        cls._overridden_settings['MEDIA_ROOT'] = cls._temp_media
        super().setUpClass()

    @classmethod
    def tearDownClass(cls):
        shutil.rmtree(cls._temp_media)
        super().tearDownClass()


class FamilleTests(InitialDataMixin, TestCase):
    personne_data = {
        'nom': 'Dupont',
        'prenom': 'Jean',
        'date_naissance': '1950-03-30',
        'genre': 'M',
    }
    famille_data = {
        'nom': 'Dupont',
        'rue': 'Rue du Moulins',
        'npa': '3000',
        'localite': 'Paris',
        'autorite_parentale': 'conjointe',
        'equipe': 'montagnes',
    }

    def setUp(self):
        self.client.force_login(self.user_aemo)

    def test_acces(self):
        """
        Sans permission adaptée, un utilisateur ne peut pas accéder à certaines
        pages (création de famille, etc.).
        """
        user = Utilisateur.objects.create_user(
            'qqun', 'qqun@example.org', 'pwd', first_name='Qqun', last_name='Personne',
        )
        self.client.force_login(user)
        with self.assertLogs('django.request', level='WARNING'):
            response = self.client.get(reverse('famille-add'))
        self.assertEqual(response.status_code, 403)

    def test_personne_creation(self):
        famille = Famille.objects.get(nom='Haddock')
        add_url = reverse('personne-add', args=[famille.pk])
        response = self.client.get(add_url)
        self.assertEqual(response.context['form'].initial['nom'], famille.nom)

        # Données minimales: nom, genre, rôle
        role = Role.objects.get(nom='Beau-père')
        response = self.client.post(add_url, data={'nom': 'Smith', 'genre': 'M', 'role': role.pk})
        self.assertRedirects(response, reverse('famille-edit', args=[famille.pk]))
        response = self.client.post(
            add_url,
            data={**self.personne_data, 'role': Role.objects.get(nom='Beau-père').pk, 'localite': "bevaix"}
        )
        self.assertEqual(response.status_code, 302)
        self.assertQuerySetEqual(Personne.objects.all(), [
            '<Personne: Dupont Jean>',
            '<Personne: Haddock Archibald>',
            '<Personne: Haddock Toto>',
            '<Personne: Smith >',
        ], transform=repr)
        p1 = Personne.objects.get(nom='Dupont')
        self.assertEqual(p1.famille, famille)
        self.assertEqual(p1.localite, 'Bevaix')
        with self.assertRaises(Formation.DoesNotExist):
            p1.formation

        # Personne < 4 ans
        pers = Personne.objects.create_personne(
            famille=famille, prenom='Gaston', nom='Lagaffe',
            date_naissance=date.today() - timedelta(days=720),
            role=Role.objects.get(nom='Enfant suivi')
        )
        self.assertEqual(pers.formation.get_statut_display(), 'Pré-scolaire')
        self.assertTrue(famille.suivi.demande_prioritaire)

    def test_label_profession_variable(self):
        famille = Famille.objects.get(nom='Haddock')
        response = self.client.get(
            '{}?role={}'.format(reverse('personne-add', args=[famille.pk]),
                                Role.objects.get(nom='Enfant suivi').pk)
        )
        self.assertContains(response, '<label for="id_profession">Profession/École :</label>', html=True)

        response = self.client.get(
            '{}?role={}'.format(reverse('personne-add', args=[famille.pk]),
                                Role.objects.get(nom='Père').pk)
        )
        self.assertContains(response, '<label for="id_profession">Profession :</label>', html=True)

    def test_get_membres(self):
        # Obtention des différents membres avec 2 requêtes: la famille, les membres (prefetch_related)
        with self.assertNumQueries(2):
            famille = Famille.objects.get(nom='Haddock')
            self.assertEqual(famille.membres_suivis()[0].prenom, 'Toto')
            self.assertEqual(famille.enfants_non_suivis(), [])
            self.assertEqual([p.nom_prenom for p in famille.parents()], ['Haddock Archibald'])
            self.assertEqual(famille.autres_parents(), [])

    def test_personne_age(self):
        with patch('aemo.models.date') as mock_date:
            mock_date.today.return_value = date(2019, 1, 16)
            self.assertEqual(Personne(date_naissance=date(1999, 11, 4)).age, 19.2)
            self.assertEqual(Personne(date_naissance=date(2000, 1, 1)).age, 19.0)
            self.assertEqual(Personne(date_naissance=date(2000, 1, 31)).age, 18.9)

    def test_personne_age_str(self):
        pers = Personne(prenom='Toto')
        pers.date_naissance = date.today() - timedelta(days=12)
        self.assertEqual(pers.age_str(), '12 jours')
        pers.date_naissance = date.today() - timedelta(days=33)
        self.assertEqual(pers.age_str(), '4 sem. 5 jours')
        pers.date_naissance = date.today() - timedelta(days=77)
        self.assertEqual(pers.age_str(), '2 mois 2 sem.')
        pers.date_naissance = date.today() - timedelta(days=689)
        self.assertEqual(pers.age_str(), '22 mois 4 sem.')
        pers.date_naissance = date.today() - timedelta(days=690)
        self.assertEqual(pers.age_str(), '1 an 10 mois')
        pers.date_naissance = date.today() - timedelta(days=691)
        self.assertEqual(pers.age_str(format_='jour'), '691 jours')

    def test_personne_edition(self):
        famille = Famille.objects.create_famille(nom='Dupont', equipe='montagnes')
        pers_data = dict(self.personne_data, famille=famille.pk, role=Role.objects.get(nom='Père').pk)
        form = PersonneForm(data=pers_data, famille=famille)
        self.assertTrue(form.is_valid(), msg=form.errors)
        pers = form.save()
        edit_url = reverse('personne-edit', args=[famille.pk, pers.pk])
        new_data = dict(pers_data, rue='Rue du Parc 12')
        response = self.client.post(edit_url, data=new_data)
        self.assertEqual(response.status_code, 302)
        pers.refresh_from_db()
        self.assertEqual(pers.rue, 'Rue du Parc 12')
        # Edition is refused if user is missing the permission
        user = Utilisateur.objects.create_user(
            'joe', 'joe@example.org', 'pwd', first_name='Joe', last_name='Cook',
        )
        self.client.force_login(user)
        with self.assertLogs('django.request', level='WARNING'):
            response = self.client.get(edit_url)
        self.assertEqual(response.status_code, 403)

    def test_personne_formation(self):
        famille = Famille.objects.get(nom='Haddock')
        pers = Personne.objects.create_personne(
            famille=famille, prenom='Gaston', nom='Lagaffe',
            role=Role.objects.get(nom='Enfant suivi')
        )
        self.assertTrue(famille.can_edit(self.user_aemo))
        form_url = reverse('formation', args=[pers.pk])
        response = self.client.get(form_url)
        self.assertFalse(response.context['form'].readonly)
        self.assertContains(response, "Enregistrer")
        response = self.client.post(form_url, data={
            'statut': 'cycle2',
            'cercle_scolaire': CercleScolaire.objects.first().pk,
            'college': 'École parfaite',
            'classe': '6H',
        })
        self.assertRedirects(response, reverse('famille-edit', args=[famille.pk]))
        pers.refresh_from_db()
        self.assertEqual(pers.formation.classe, '6H')

    def test_delete_personne_formation_impossible(self):
        pers = Personne.objects.create_personne(
            famille=Famille.objects.first(), prenom='Gaston', nom='Lagaffe',
            role=Role.objects.get(nom='Enfant suivi')
        )
        self.assertRaises(IntegrityError, pers.formation.delete)

    def test_delete_personne(self):
        famille = Famille.objects.first()
        pers = Personne.objects.create_personne(
            famille=famille, prenom='Gaston', nom='Lagaffe',
            role=Role.objects.get(nom='Père')
        )
        response = self.client.post(
            reverse('personne-delete', args=[famille.pk, pers.pk]), follow=True
        )
        self.assertRedirects(response, reverse('famille-edit', args=[famille.pk]), status_code=302)

    def test_famille_creation(self):
        response = self.client.get(reverse('famille-add'))
        self.assertContains(response, '<option value="montagnes">Montagnes et V-d-T</option>', html=True)
        self.assertContains(
            response,
            '<button class="btn btn-sm btn-success" name="save" type="submit">Enregistrer</button>',
            html=True
        )
        self.assertContains(response, "id_motif_detail")
        response = self.client.post(reverse('famille-add'), data={
            **self.famille_data, 'motif_detail': "Un test parmi d'autres"
        })
        famille = Famille.objects.get(nom='Dupont')
        self.assertRedirects(response, reverse('famille-edit', args=[famille.pk]))
        famille = Famille.objects.get(nom='Dupont')
        self.assertEqual(famille.suivi.equipe, 'montagnes')
        self.assertEqual(famille.suivi.date_demande, date.today())
        self.assertEqual(famille.suivi.motif_detail, "Un test parmi d'autres")

    def test_famille_edition(self):
        famille = Famille.objects.get(nom='Haddock')
        edit_url = reverse('famille-edit', args=[famille.pk])
        response = self.client.get(edit_url)
        self.assertContains(response, '<select name="provenance" class="form-select" id="id_provenance">')
        self.assertContains(response, reverse('print-coord-famille', args=[famille.pk]))
        self.assertContains(response, "Changer l’adresse")
        self.assertNotContains(response, "id_motif_detail")
        data = {**self.famille_data, 'localite': 'Monaco'}
        response = self.client.post(edit_url, data=data)
        self.assertRedirects(response, edit_url)
        famille.refresh_from_db()
        self.assertEqual(famille.localite, 'Monaco')

    def test_famille_edition_perm(self):
        famille = Famille.objects.get(nom='Haddock')
        user_1 = Utilisateur.objects.create(username='jean')
        user_1.groups.add(Group.objects.get(name='aemo'))
        user_2 = Utilisateur.objects.create(username='ben')
        user_2.groups.add(Group.objects.get(name='aemo'))
        manager = Utilisateur.objects.create(username='boss')
        manager.user_permissions.add(Permission.objects.get(codename='change_famille'))
        manager2 = Utilisateur.objects.create(username='boss2')
        manager2.roles.add(Role.objects.get(nom='Responsable/coordinateur'))
        # No referent set, any team user can edit
        self.assertTrue(famille.can_edit(user_1))
        interv = Intervenant.objects.create(
            suivi=famille.suivi, intervenant=user_2, role=Role.objects.get(nom='Educ'),
            date_debut=date.today() - timedelta(days=3)
        )
        # Only referent (or manager) can access
        self.assertFalse(famille.can_edit(user_1))
        self.assertTrue(famille.can_edit(user_2))
        self.assertTrue(famille.can_edit(manager))
        self.assertTrue(famille.can_edit(manager2))
        interv.date_fin = date.today() - timedelta(days=1)
        self.assertTrue(famille.can_edit(user_2))

    def test_famille_list(self):
        reg_montagnes = Region.objects.get_or_create(nom='Montagnes')[0]
        reg_litt_ouest = Region.objects.get_or_create(nom='Littoral Ouest')[0]
        fam1 = Famille.objects.get(nom='Haddock')
        fam2 = Famille.objects.create_famille(
            nom='Dupond', rue='Château 4', npa=2299, localite='Klow',
            region=reg_montagnes, equipe='montagnes',
        )
        fam2.suivi.date_debut_evaluation = date.today() - timedelta(days=3)
        fam2.suivi.save()
        fam2.suivi.intervenants.add(self.user_aemo, through_defaults={'role': Role.objects.get(nom='Educ')})
        Niveau.objects.create(famille=fam2, niveau_interv=1, date_debut=date.today())
        # famille sans équipe
        Famille.objects.create_famille(
            nom='Sprutz', rue='Fleur 12', npa=2299, localite='Klow',
            region=reg_montagnes, equipe='montagnes'
        )
        Famille.objects.create_famille(
            nom='Tournesol', rue='Château1', npa=2000, localite='Moulinsart',
            region=reg_litt_ouest, equipe='littoral',
        )
        response = self.client.get(reverse('famille-list'))
        self.assertEqual(len(response.context['object_list']), Famille.objects.count())
        self.assertContains(
            response,
            '<a href="%s" title="Suivi">Haddock</a>' % reverse('famille-suivi', args=[fam1.pk]),
            html=True
        )
        # Apply filters
        response = self.client.get(
            reverse('famille-list') + '?equipe=montagnes&niveau=1&nom=du'
        )
        self.assertEqual(response.context['form'].errors, {})
        self.assertQuerySetEqual(response.context['object_list'], [fam2])
        # All levels
        response = self.client.get(reverse('famille-list') + '?niveau=')
        self.assertEqual(len(response.context['object_list']), 4)

        # Liste d'attente
        response = self.client.get(reverse('famille-attente'))
        self.assertEqual(len(response.context['object_list']), 4)
        self.assertContains(response, f'<td>{reg_montagnes.nom}</td>', html=True)
        self.assertContains(response, '<td class="red">À faire</td>', html=True)

        response = self.client.get(reverse('famille-attente') + '?equipe=littoral')
        self.assertEqual(len(response.context['object_list']), 1)

        response = self.client.get(reverse('famille-attente') + '?equipe=montagnes')
        self.assertEqual(len(response.context['object_list']), 3)

    def test_famille_list_filtre_intervenant(self):
        fam1 = Famille.objects.get(nom='Haddock')
        # Ajout intervention passée
        fam1.suivi.intervenants.add(
            self.user_aemo, through_defaults={
                'role': Role.objects.get(nom='Educ'), 'date_fin': date.today() - timedelta(days=3)
            }
        )
        fam2 = Famille.objects.create_famille(
            nom='Dupond', rue='Château 4', npa=2299, localite='Klow', equipe='montagnes',
        )
        fam2.suivi.date_debut_evaluation = date.today() - timedelta(days=3)
        fam2.suivi.save()
        fam2.suivi.intervenants.add(
            self.user_aemo, through_defaults={'role': Role.objects.get(nom='Educ')}
        )
        response = self.client.get(reverse('famille-list') + f'?&interv={self.user_aemo.pk}')
        self.assertEqual(response.context['form'].errors, {})
        self.assertQuerySetEqual(response.context['object_list'], [fam2])
        # Familles sans intervenant
        response = self.client.get(reverse('famille-list') + '?interv=0')
        self.assertQuerySetEqual(response.context['object_list'], [fam1])
        self.assertEqual(response.context['object_list'][0].suivi.intervenant_set.count(), 0)

    def test_calcul_charge_liste_famille(self):
        fam1 = Famille.objects.get(nom='Haddock')
        fam1.suivi.date_debut_suivi = date.today()
        fam1.suivi.save()
        fam1.suivi.intervenants.add(self.user_aemo, through_defaults={'role': Role.objects.get(nom='Educ')})
        Niveau.objects.create(famille=fam1, niveau_interv=2, date_debut=date.today())

        fam2 = Famille.objects.create_famille(
            nom='Dupond', rue='Château 4', npa=2299, localite='Klow',
        )
        fam2.suivi.date_debut_evaluation = date.today() - timedelta(days=3)
        fam2.suivi.heure_coord = True
        fam2.suivi.save()
        fam2.suivi.intervenants.add(self.user_aemo, through_defaults={'role': Role.objects.get(nom='Educ')})
        Niveau.objects.create(famille=fam2, niveau_interv=1, date_debut=date.today())

        fam3 = Famille.objects.create_famille(
            nom='Dupond2', rue='Château 4', npa=2299, localite='Klow',
        )
        fam3.suivi.date_debut_evaluation = date.today() - timedelta(days=3)
        fam3.suivi.save()
        fam3.suivi.intervenants.add(self.user_aemo, through_defaults={'role': Role.objects.get(nom='Educ')})
        Niveau.objects.create(famille=fam3, niveau_interv=3, date_debut=date.today())

        # Ne doit pas compter car intervention terminée
        fam4 = Famille.objects.create_famille(
            nom='Dupond3', rue='Château 6', npa=2299, localite='Klow',
        )
        fam4.suivi.date_debut_evaluation = date.today() - timedelta(days=3)
        fam4.suivi.save()
        fam4.suivi.intervenants.add(
            self.user_aemo, through_defaults={
                'role': Role.objects.get(nom='Educ'), 'date_fin': date.today() - timedelta(days=1),
            }
        )
        Niveau.objects.create(famille=fam4, niveau_interv=2, date_debut=date.today())

        response = self.client.get(reverse('famille-list'))
        self.assertEqual(
            response.context['ma_charge'],
            {'heures': 9, 'nbre_eval': 2, 'nbre_suivi': 1, 'charge_diff': 7}
        )

        self.client.force_login(self.user_admin)
        response = self.client.get(reverse('charge-utilisateurs'))
        self.assertEqual(response.context['utilisateurs'], [self.user_aemo, self.user_aemo2])
        self.assertEqual(response.context['utilisateurs'][0].charge, 8)
        self.assertEqual(response.context['utilisateurs'][0].heures, 9)
        self.assertEqual(response.context['utilisateurs'][0].nbre_suivi, 1)
        self.assertEqual(response.context['utilisateurs'][0].nbre_eval, 2)
        self.assertEqual(response.context['utilisateurs'][1].charge, 0)
        self.assertEqual(response.context['utilisateurs'][1].heures, 0)
        self.assertEqual(response.context['utilisateurs'][1].nbre_suivi, 0)
        self.assertEqual(response.context['utilisateurs'][1].nbre_eval, 0)

    def test_delete_enfant_suivi(self):
        famille = Famille.objects.first()
        pers = Personne.objects.create_personne(
            famille=famille, prenom='Gaston', nom='Lagaffe',
            role=Role.objects.get(nom='Enfant suivi')
        )
        with self.assertLogs('django.request', level='WARNING'):
            response = self.client.post(reverse('personne-delete', args=[famille.pk, pers.pk]), follow=True)
        self.assertTrue(response.status_code, 403)

    def test_affichage_familles_libres_pour_tous_les_educs(self):
        famille = Famille.objects.create_famille(
            nom='Simpson', rue='Château1', npa=2000, localite='Moulinsart',
            equipe='montagnes'
        )
        self.assertEqual(famille.suivi.intervenants.count(), 0)
        response = self.client.get(reverse('famille-list'))
        self.assertContains(response, 'Simpson')

    def test_affichage_adresse(self):
        famille = Famille(nom='Simpson', rue='Château1', npa=2000, localite='Moulinsart')
        self.assertEqual(famille.adresse, 'Château1, 2000 Moulinsart')
        famille.rue = ''
        self.assertEqual(famille.adresse, '2000 Moulinsart')
        famille.npa = ''
        self.assertEqual(famille.adresse, 'Moulinsart')

    def test_acces_lecture_seule_famille_archivee(self):
        famille = Famille.objects.get(nom='Haddock')
        famille.suivi.date_fin_suivi = '2020-04-15'
        famille.suivi.save()
        response = self.client.get(reverse('famille-suivi', args=[famille.pk]))
        self.assertFalse(response.context['can_edit'])
        response = self.client.get(reverse('famille-edit', args=[famille.pk]))
        self.assertFalse(response.context['can_edit'])
        response = self.client.get(reverse('personne-edit', args=[famille.pk, famille.membres.first().pk]))
        self.assertTrue(response.context['form'].fields['nom'].disabled)
        # Changement adresse impossible
        response = self.client.get(reverse('famille-adresse-change', args=[famille.pk]))
        self.assertEqual(response.status_code, 403)

    def test_affichage_bouton_changement_adresse(self):
        famille = Famille.objects.get(nom='Haddock')
        response = self.client.get(reverse('famille-edit', args=[famille.pk]))
        url = reverse('famille-adresse-change', args=[famille.pk])
        self.assertContains(
            response,
            f'<a class="btn btn-sm btn-outline-primary" href="{url}">Changer l’adresse</a>',
            html=True
        )

    def test_membres_famille_dans_form_changement_adresse(self):
        famille = Famille.objects.get(nom='Haddock')
        pers = Personne.objects.create_personne(
            famille=famille, prenom='Gaston', nom='Lagaffe', rue='Fleurs 44',
            role=Role.objects.get(nom='Enfant suivi')
        )
        edit_url = reverse('famille-adresse-change', args=[famille.pk])
        response = self.client.get(edit_url)
        self.assertContains(response, 'Lagaffe Gaston (Enfant suivi)')
        self.assertContains(response, 'Haddock Archibald (Père)')

        data = {'rue': 'Champs-Elysées 1', 'npa': '7500', 'localite': 'Paris', 'membres': [pers.pk]}
        response = self.client.post(edit_url, data=data)
        self.assertRedirects(response, reverse('famille-edit', args=[famille.pk]))
        pers.refresh_from_db()
        self.assertEqual(pers.rue, "Champs-Elysées 1")
        for parent in famille.parents():
            self.assertEqual(parent.rue, 'Château1')
        # Test changement adresse sans changer pour les membres
        data = {'rue': 'Champs-Elysées 2', 'npa': '7500', 'localite': 'Paris', 'membres': []}
        response = self.client.post(edit_url, data=data)
        self.assertRedirects(response, reverse('famille-edit', args=[famille.pk]))

    def test_nom_famille_formatage(self):
        form = FamilleCreateForm(data={'nom': 'dubois', 'equipe': 'montagnes'})
        self.assertTrue(form.is_valid())
        form.save()
        self.assertTrue(Famille.objects.filter(nom='Dubois').exists())

        form = FamilleCreateForm(data={'nom': 'de Gaule', 'equipe': 'montagnes'})
        self.assertTrue(form.is_valid())
        form.save()
        self.assertTrue(Famille.objects.filter(nom='de Gaule').exists())

    def test_nom_personne_formatage(self):
        famille = Famille.objects.get(nom='Haddock')
        pers = Personne.objects.get(nom='Haddock', prenom='Archibald')
        edit_url = reverse('personne-edit', args=[famille.pk, pers.pk])
        self.client.post(
            edit_url,
            data={'nom': 'doe', 'prenom': 'john', 'role': pers.role.pk, 'genre': 'M'},
            follow=True
        )
        doe = Personne.objects.get(nom='Doe', prenom='John')
        self.assertEqual(pers.pk, doe.pk)

    def test_interventions_actives(self):
        famille = Famille.objects.get(nom='Haddock')
        educ = Role.objects.get(nom='Educ')
        today = date.today()
        date_passe = today - timedelta(days=30)
        date_futur = today + timedelta(days=30)
        int1, int2, int3, int4 = Intervenant.objects.bulk_create([
            Intervenant(
                suivi=famille.suivi, intervenant=self.user_aemo, role=educ,
                date_debut=debut, date_fin=fin
            ) for debut, fin in [
                (date_passe, date_passe + timedelta(days=5)),  # complètement passé
                (date_futur, None),  # futur
                (date_passe, date_futur),
                (date_passe, None),
            ]
        ])
        self.assertQuerySetEqual(famille.interventions_actives().order_by('pk'), [int3, int4])
        self.assertQuerySetEqual(famille.interventions_actives(today - timedelta(days=31)), [])

    def test_ajout_intervenant(self):
        famille = Famille.objects.get(nom='Haddock')
        intervenant = self.user_aemo
        role = Role.objects.get(nom='Educ')
        date_debut = date.today()
        response = self.client.post(
            reverse('intervenant-add', args=[famille.pk]),
            data={'intervenant': intervenant.pk, 'role': role.pk, 'date_debut': date_debut}
        )
        self.assertRedirects(response, reverse('famille-suivi', args=[famille.pk]))
        self.assertTrue(
            Intervenant.objects.filter(
                suivi=famille.suivi, intervenant=intervenant, role=role, date_debut=date_debut
            ).exists()
        )

    def test_modif_intervenant(self):
        famille = Famille.objects.get(nom='Haddock')
        intervenant = self.user_aemo
        role = Role.objects.get(nom='Educ')
        date_debut = date.today()
        intervention = Intervenant.objects.create(
            suivi=famille.suivi, intervenant=intervenant, role=role, date_debut=date_debut
        )
        suivi_url = reverse('famille-suivi', args=[famille.pk])
        add_url = reverse("intervenant-add", args=[famille.pk])
        edit_url = reverse("intervenant-edit", args=[famille.pk, intervention.pk])
        response = self.client.get(suivi_url)
        # Test boutons ajouter/modifier
        self.assertContains(
            response,
            ('<a href="" class="btn btn-sm btn-mini btn-primary js-add" '
             f'data-url="{add_url}" role="button" title="Ajouter un intervenant">+</a>'),
            html=True
        )
        self.assertContains(
            response,
            ('<a href="" class="btn btn-sm btn-mini js-edit"'
             f'data-url="{edit_url}" role="button" title="Modifier une intervention">'
             '<img src="/static/admin/img/icon-changelink.svg"></a>'),
            html=True
        )
        response = self.client.post(
            reverse('intervenant-edit', args=[famille.pk, intervention.pk]), data={
                'intervenant': intervenant.pk, 'role': role.pk,
                'date_debut': date_debut, 'date_fin': date.today()
            }
        )
        self.assertRedirects(response, reverse('famille-suivi', args=[famille.pk]))
        intervention.refresh_from_db()
        self.assertEqual(intervention.date_fin, date.today())

    def test_document_upload_delete(self):
        famille = Famille.objects.first()
        with open(__file__, mode='rb') as fh:
            response = self.client.post(reverse('famille-doc-upload', args=[famille.pk]), data={
                'famille': famille.pk,
                'fichier': File(fh),
                'titre': 'Titre1',
            })
        self.assertRedirects(response, famille.suivi_url)
        self.assertEqual(famille.documents.count(), 1)
        os.remove(famille.documents.first().fichier.path)
        # Adding second file with same title gives a validation error
        with open(__file__, mode='rb') as fh:
            response = self.client.post(reverse('famille-doc-upload', args=[famille.pk]), data={
                'famille': famille.pk,
                'fichier': File(fh),
                'titre': 'Titre1',
            })
        self.assertEqual(
            response.context['form'].errors['__all__'],
            ['Un objet Document avec ces champs Famille et Titre existe déjà.']
        )
        # Delete
        response = self.client.post(
            reverse('famille-doc-delete', args=[famille.pk, famille.documents.first().pk])
        )
        self.assertRedirects(response, famille.suivi_url)

    def test_ajout_rapport(self):
        famille = Famille.objects.first()
        other_user = Utilisateur.objects.create_user(
            'other', 'other@example.org', 'mepassword', prenom='Bruce', nom='Externe',
        )
        other_user.groups.add(Group.objects.get(name='aemo'))
        famille.suivi.intervenants.add(other_user, through_defaults={'role': Role.objects.get(nom='Psy')})
        famille.suivi.intervenants.add(self.user_aemo, through_defaults={
            'role': Role.objects.get(nom='Psy'), 'date_fin': date.today() - timedelta(days=3)
        })
        response = self.client.get(reverse('famille-rapport-add', args=[famille.pk]))
        # Seule l'intervention active est visible
        self.assertQuerySetEqual(response.context['form']['sig_interv'].field.queryset, [other_user])

        rapport_data = {
            'date': date.today(),
            'situation': '<p>Assez bonne</p>',
            'evolutions': '<p>1<br>2<br>3</p>',
            'evaluation': '<p>Ha!</p>',
            'projet': '<p>Bon</p>',
        }
        response = self.client.post(
            reverse('famille-rapport-add', args=[famille.pk]), data=rapport_data
        )
        self.assertRedirects(response, reverse('famille-agenda', args=[famille.pk]))
        rapport = famille.rapports.first()
        self.assertTrue(rapport.can_edit(self.user_aemo))
        self.assertTrue(rapport.can_edit(other_user))
        # Test édition
        rapport_data.update({'date': date.today(), 'projet': '<p>Un autre</p>'})
        response = self.client.post(
            reverse('famille-rapport-edit', args=[famille.pk, rapport.pk]), data=rapport_data
        )
        if response.status_code == 200:
            self.fail(response.context['form'].errors)
        rapport.refresh_from_db()
        self.assertEqual(rapport.date, date.today())
        # Test vue/impression
        response = self.client.get(reverse('famille-rapport-view', args=[famille.pk, rapport.pk]))
        self.assertContains(response, "<p>Assez bonne</p>")
        response = self.client.get(reverse('famille-rapport-print', args=[famille.pk, rapport.pk]))
        self.assertEqual(response['content-type'], 'application/pdf')

    def test_suppression_rapport(self):
        famille = Famille.objects.first()
        rapp = Rapport.objects.create(
            famille=famille, date=date(2020, 4, 18), auteur=self.user_aemo, situation="La situation"
        )
        response = self.client.post(reverse('famille-rapport-delete', args=[famille.pk, rapp.pk]))
        self.assertRedirects(response, reverse('famille-agenda', args=[famille.pk]))

    def test_ajoute_contact_a_enfant_suivi(self):
        famille = Famille.objects.get(nom='Haddock')
        pers = Personne.objects.create_personne(
            famille=famille, prenom='Gaston', nom='Lagaffe',
            role=Role.objects.get(nom='Enfant suivi')
        )
        new_contact = Contact.objects.get(nom='DrSpontz')
        add_contact_url = reverse('personne-reseau-add', args=[pers.pk])
        response = self.client.post(add_contact_url, data={'contacts[]': new_contact.pk})
        self.assertEqual(response.json(), {'is_valid': True})
        self.assertEqual(pers.reseaux.count(), 1)

    def test_creation_contact_possible_pour_educ(self):
        famille = Famille.objects.get(nom='Haddock')
        pers = Personne.objects.create_personne(
            famille=famille, prenom='Gaston', nom='Lagaffe',
            role=Role.objects.get(nom='Enfant suivi')
        )
        educ = Utilisateur.objects.create_user(
            'other', 'other@example.org', 'mepassword', prenom='Bruce', nom='Externe',
        )
        educ.groups.add(Group.objects.get(name='aemo'))
        self.client.force_login(self.user_aemo)
        add_contact_url = f"{reverse('contact-add')}?forpers={pers.pk}"
        new_contact = {
            'nom': "ContactNom",
            'prenom': 'ContactPrenom'
        }
        response = self.client.post(add_contact_url, data=new_contact)
        self.assertTrue(response.status_code, 200)
        response = self.client.get(reverse('personne-reseau-list', args=[pers.pk]))
        self.assertContains(response, "<td>ContactNom ContactPrenom</td>", html=True)
        self.assertEqual(pers.reseaux.count(), 1)

    def test_retirer_contact_du_reseau(self):
        famille = Famille.objects.get(nom='Haddock')
        pers = Personne.objects.create_personne(
            famille=famille, prenom='Gaston', nom='Lagaffe',
            role=Role.objects.get(nom='Enfant suivi')
        )
        new_contact = Contact.objects.get(nom='DrSpontz')
        pers.reseaux.add(new_contact)
        self.assertEqual(pers.reseaux.count(), 1)

        remove_url = reverse('personne-reseau-remove', args=[pers.pk, new_contact.pk])
        response = self.client.post(remove_url, data={})
        self.assertTrue(response.status_code, 302)
        pers.refresh_from_db()
        self.assertEqual(pers.reseaux.count(), 0)

    def test_suivis_en_cours(self):
        dates = [
            # date_demande, date_debut_eval, date_debut_suivi, suivi en cours entre 1-28.2 ?
            (None, None, None, False),
            (date(2020, 2, 15), None, None, True),
            (None, date(2020, 2, 15), None, True),
            (None, None, date(2020, 2, 15), True),
            (None, None, date(2020, 3, 3), False),
        ]
        famille = Famille.objects.get(nom='Haddock')
        for date_demande, date_debut_evaluation, date_debut_suivi, en_cours in dates:
            famille.suivi.date_demande = date_demande
            famille.suivi.date_debut_evaluation = date_debut_evaluation
            famille.suivi.date_debut_suivi = date_debut_suivi
            famille.suivi.save()
            familles = Famille.suivis_en_cours(date(2020, 2, 1), date(2020, 2, 28))
            if en_cours:
                self.assertIn(famille, familles)
            else:
                self.assertNotIn(famille, familles)


class SuiviTests(InitialDataMixin, TestCase):
    def setUp(self):
        super().setUp()
        self.client.force_login(self.user_aemo)

    def test_suivi_dossier(self):
        famille = Famille.objects.get(nom='Haddock')
        suivi_url = reverse('famille-suivi', args=[famille.pk])
        response = self.client.get(suivi_url)
        self.assertContains(
            response,
            '<button class="btn btn-sm btn-success" name="save" type="submit">Enregistrer</button>',
            html=True
        )
        response = self.client.post(suivi_url, data={
            'equipe': 'montagnes',
            'ope_referent': Contact.objects.get(nom='Rathfeld').pk,
            'date_demande': '2018-10-24',
            'etape_suivi': 'demande',
        })
        self.assertRedirects(response, suivi_url)
        suivi = famille.suivi
        self.assertEqual(suivi.equipe, 'montagnes')

    def test_suivi_lecture_seule(self):
        user_other = Utilisateur.objects.create_user(
            'user_other', 'user_other@example.org', nom='Aemo2', prenom='Prénom', sigle='SP2'
        )
        user_other.groups.add(Group.objects.get(name='aemo'))
        famille = Famille.objects.get(nom='Haddock')
        famille.suivi.intervenants.add(
            self.user_aemo, through_defaults={'role': Role.objects.get(nom='Educ')}
        )
        self.client.force_login(user_other)
        suivi_url = reverse('famille-suivi', args=[famille.pk])
        response = self.client.get(suivi_url + '?confirm=1')
        self.assertFalse(response.context['can_edit'])
        self.assertNotContains(response, 'Enregistrer')

    def test_permission_effacement_definitif_saisie_par_erreur(self):
        famille = Famille.objects.create_famille(
            nom='Simpson', rue='Château1', npa=2000, localite='Moulinsart',
            equipe='montagnes'
        )
        famille.suivi.date_fin_suivi = date.today() - timedelta(days=500)
        famille.suivi.save()
        self.assertFalse(famille.can_be_deleted(self.user_aemo))

        self.user_aemo.user_permissions.add(Permission.objects.get(codename='change_famille'))
        self.user_aemo.user_permissions.add(Permission.objects.get(codename='can_archive'))
        # Recharger pour réinitialiser cache des permissions
        user_mont = Utilisateur.objects.get(pk=self.user_aemo.pk)
        self.client.force_login(user_mont)
        self.assertTrue(famille.can_be_archived(user_mont))

        famille.suivi.date_fin_suivi = None
        famille.suivi.save()
        response = self.client.post(
            reverse('famille-agenda', args=[famille.pk]),
            data={'date_demande': date.today(),
                  'date_debut_evaluation': date.today(),
                  'date_fin_evaluation': date.today(),
                  'motif_fin_suivi': 'erreur'}
        )
        # Lorsque le dossier est fermé, la redirection se fait sur la liste des familles
        self.assertRedirects(response, reverse('famille-list'))

        famille.refresh_from_db()
        self.assertEqual(famille.suivi.date_fin_suivi, date.today())
        self.assertEqual(famille.suivi.motif_fin_suivi, 'erreur')
        self.assertTrue(famille.can_be_deleted(user_mont))

    def test_date_suivante_bilan(self):
        famille = Famille.objects.create_famille(
            nom='Simpson', rue='Château1', npa=2000, localite='Moulinsart',
            equipe='montagnes'
        )
        famille.suivi.date_debut_suivi = date.today() - timedelta(days=90)
        famille.suivi.save()
        self.assertEqual(famille.suivi.etape.code, 'debut_suivi')
        self.assertEqual(famille.suivi.date_suivante(), date.today())
        Bilan.objects.create(
            date=date.today() - timedelta(days=5),
            auteur=self.user_aemo,
            famille=famille,
        )
        self.assertEqual(famille.suivi.date_suivante(), date.today() + timedelta(days=90 - 5))

    def test_date_fin_suivi_prevue(self):
        famille = Famille.objects.create_famille(
            nom='Simpson', rue='Château1', npa=2000, localite='Moulinsart',
            equipe='montagnes'
        )
        famille.suivi.date_debut_suivi = date.today() - timedelta(days=90)
        famille.suivi.save()
        Niveau.objects.create(
            famille=famille, niveau_interv=2, date_debut=famille.suivi.date_debut_suivi
        )
        self.assertEqual(
            Suivi.WORKFLOW['fin_suivi'].delai_depuis(famille.suivi),
            famille.suivi.date_debut_suivi + timedelta(days=EtapeFin.delai_standard)
        )
        Niveau.objects.create(
            famille=famille, niveau_interv=3, date_debut=date.today() - timedelta(days=5)
        )
        famille = Famille.objects.get(pk=famille.pk)
        self.assertEqual(famille.niveau_actuel(), 3)
        self.assertEqual(
            Suivi.WORKFLOW['fin_suivi'].delai_depuis(famille.suivi),
            date.today() - timedelta(days=5) + timedelta(days=EtapeFin.delai_niveau3)
        )

    def test_bilan_form(self):
        famille = Famille.objects.create_famille(
            nom='Simpson', rue='Château1', npa=2000, localite='Moulinsart',
            equipe='montagnes'
        )
        famille.suivi.intervenants.add(
            self.user_aemo, through_defaults={'role': Role.objects.get(nom='Educ')}
        )
        famille.suivi.intervenants.add(
            self.user_aemo2, through_defaults={
                'role': Role.objects.get(nom='Educ'), 'date_fin': date.today() - timedelta(days=3)
            }
        )

        self.client.force_login(self.user_aemo)
        response = self.client.get(reverse('famille-bilan-add', args=[famille.pk]))
        # Seule l'intervention active est visible
        self.assertQuerySetEqual(response.context['form']['sig_interv'].field.queryset, [self.user_aemo])

        response = self.client.post(reverse('famille-bilan-add', args=[famille.pk]), data={
            'date': '2020-08-30',
            'objectifs': '<p class="msoNormal"><span>Objectifs</span></p>',
            'rythme': '<p class="msoNormal"><span>Rythme</span></p>',
        })
        self.assertRedirects(response, reverse('famille-agenda', args=[famille.pk]))
        bilan = famille.bilans.first()
        self.assertEqual(bilan.objectifs, '<p>Objectifs</p>')
        self.assertEqual(bilan.rythme, '<p>Rythme</p>')


class AgendaTests(InitialDataMixin, TestCase):
    def setUp(self):
        super().setUp()
        self.client.force_login(self.user_aemo)
        self.request = RequestFactory().get('/')
        self.request.user = self.user_aemo

    def test_affichage_agenda(self):
        famille = Famille.objects.get(nom='Haddock')
        self.client.force_login(self.user_aemo)
        suivi_url = reverse('famille-agenda', args=[famille.pk])
        response = self.client.get(suivi_url)
        self.assertContains(
            response,
            '<input type="text" name="date_demande" value="{}" '
            'class="vDateField vDateField-rounded" size="10" id="id_date_demande">'.format(format_d_m_Y(date.today())),
            html=True
        )

    def test_dates_obligatoires(self):
        today = date.today()
        form_data = {
            field_name: today for field_name in AgendaForm._meta.fields
            if field_name not in ['date_fin_suivi', 'motif_fin_suivi', 'destination']
        }
        for field, etape in Suivi.WORKFLOW.items():
            data2 = dict(form_data)
            if etape.oblig is True:
                etape_preced_oblig = Suivi.WORKFLOW[etape.preced_oblig]
                data2[etape_preced_oblig.date_nom()] = ''
                if etape.code == 'fin_suivi':
                    data2['date_fin_suivi'] = today
                    data2['motif_fin_suivi'] = 'placement'
                    data2['destination'] = 'fah'
                form = AgendaForm(data2, request=self.request)
                self.assertFalse(form.is_valid())
                self.assertEqual(form.errors, {'__all__': ['La date «{}» est obligatoire'.format(etape_preced_oblig)]})
            else:
                form = AgendaForm(data2, request=self.request)
                self.assertTrue(form.is_valid())

    def test_fin_suivi(self):
        famille = Famille.objects.create_famille(
            nom='Loiseau', rue='Château1', npa=2000, localite='Moulinsart', equipe='montagnes',
        )
        today = date.today()
        suivi = famille.suivi
        form_data = {}
        suivi.date_demande = form_data['date_demande'] = today - timedelta(days=360)
        suivi.date_debut_evaluation = form_data['date_debut_evaluation'] = today - timedelta(days=290)
        suivi.date_fin_evaluation = form_data['date_fin_evaluation'] = today - timedelta(days=250)
        suivi.date_debut_suivi = form_data['date_debut_suivi'] = today - timedelta(days=120)
        suivi.save()
        form = AgendaForm(
            data={**form_data, 'date_fin_suivi': today - timedelta(days=5),
                  'motif_fin_suivi': 'evol_positive', 'destination': 'fah'},
            instance=suivi,
            request=self.request,
        )
        self.assertTrue(form.is_valid(), msg=form.errors)
        suivi = form.save()
        self.assertEqual(suivi.date_fin_suivi, today - timedelta(days=5))

    def test_motif_fin_suivi_sans_date(self):
        data = {'date_demande': date.today(),
                'date_debut_evaluation': date.today(),
                'date_fin_evaluation': date.today(),
                'date_debut_suivi': date.today(),
                'motif_fin_suivi': 'evol_positive'}
        form = AgendaForm(data, request=self.request)
        self.assertFalse(form.is_valid())
        self.assertEqual(
            form.errors,
            {'__all__': ["Les champs «Fin de l'accompagnement», «Motif de fin» et «Destination» "
                         "sont obligatoires pour fermer le dossier."]}
        )
        # Test avec date_fin_suivi non valide
        data['date_fin_suivi'] = '2019-01-32'
        form = AgendaForm(data, request=self.request)
        self.assertFalse(form.is_valid())
        self.assertEqual(
            form.errors,
            {'date_fin_suivi': ['Saisissez une date valide.'],
             '__all__': ["Les champs «Fin de l'accompagnement», «Motif de fin» et «Destination» "
                         "sont obligatoires pour fermer le dossier."]
             }
        )

    def test_dates_non_chronologiques(self):
        data = {
            field_name: '2019-01-15' for field_name in AgendaForm._meta.fields
            if field_name not in ['date_fin_suivi', 'motif_fin_suivi', 'destination']
        }
        date_field_preced = None
        for field_name in AgendaForm._meta.fields:
            if field_name in ['date_demande', 'motif_fin_suivi', 'destination']:
                date_field_preced = field_name
                continue
            elif field_name == 'date_fin_suivi':
                data = dict(
                    data, date_fin_suivi='2019-01-15', motif_fin_suivi='placement', destination='famille'
                )
            form = AgendaForm(dict(data, **{date_field_preced: '2019-01-16'}), request=self.request)
            self.assertFalse(form.is_valid())
            self.assertEqual(
                form.errors,
                {'__all__':
                    ["La date «{}» ne respecte pas l’ordre chronologique!".format(form.fields[field_name].label)]}
            )
            date_field_preced = field_name
        # Test avec trou dans les valeurs de dates
        data = {
            'date_demande': '2019-01-15',
            'date_fin_evaluation': '2019-01-01',  # Ordre chronologique non respecté
            'date_debut_suivi': '2019-02-01'
        }
        form = AgendaForm(data, request=self.request)
        self.assertFalse(form.is_valid())
        self.assertEqual(
            form.errors,
            {'__all__': ["La date «Fin de l’évaluation le» ne respecte pas l’ordre chronologique!"]}
        )

    def test_saisie_date_invalide(self):
        form = AgendaForm({
            'date_demande': '20.8',
        }, request=self.request)
        self.assertFalse(form.is_valid())
        self.assertEqual(form.errors, {'date_demande': ['Saisissez une date valide.']})

    def test_saisie_par_erreur_OK(self):
        data = {'motif_fin_suivi': 'erreur'}
        form = AgendaForm(data, request=self.request)
        self.assertTrue(form.is_valid())
        self.assertEqual(form.cleaned_data['date_fin_suivi'], date.today())

    def test_demande_non_aboutie(self):
        data = {'motif_fin_suivi': 'non_aboutie'}
        form = AgendaForm(data, request=self.request)
        self.assertTrue(form.is_valid())
        self.assertEqual(form.cleaned_data['date_fin_suivi'], date.today())

    def test_abandon_durant_evaluation_1(self):
        data = {'date_demande': date.today(), 'motif_fin_suivi': 'placement',
                'destination': 'famille'}
        form = AgendaForm(data, request=self.request)
        self.assertTrue(form.is_valid(), form.errors)

    def test_abandon_durant_evaluation_2(self):
        data = {'date_demande': date.today(), 'motif_fin_suivi': 'placement', 'destination': 'famille'}
        form = AgendaForm(data, request=self.request)
        self.assertTrue(form.is_valid())

    def test_dates_tardives(self):
        date_passee = date.today() - timedelta(days=31 + Prestation.DELAI_SAISIE_SUPPL)
        form_data = {}
        suivi = Suivi()
        for date_field in AgendaForm._meta.fields:
            if not date_field.startswith('date'):
                continue
            if date_field == 'date_fin_suivi':
                form_data['motif_fin_suivi'] = 'placement'
                form_data['destination'] = 'famille'
            form_data[date_field] = date_passee
            form = AgendaForm(data=form_data, instance=suivi, request=self.request)
            self.assertFalse(form.is_valid())
            if date_field == 'date_fin_suivi':
                self.assertEqual(
                    form.errors,
                    {date_field: ["La saisie de dates pour le mois précédent n’est pas permise !"],
                     '__all__': ["Les champs «Fin de l'accompagnement», «Motif de fin» et «Destination» "
                                 "sont obligatoires pour fermer le dossier."]}
                )
            else:
                self.assertEqual(
                    form.errors,
                    {date_field: ["La saisie de dates pour le mois précédent n’est pas permise !"]}
                )
            setattr(suivi, date_field, date.today() - timedelta(days=90))
            form_data[date_field] = date.today() - timedelta(days=90)

    def test_date_demande_anticipee(self):
        date_demande = date.today() + timedelta(days=1)
        data = {'date_demande': date_demande}
        form = AgendaForm(data, request=self.request)
        self.assertFalse(form.is_valid())
        self.assertEqual(
            form.errors,
            {'date_demande': ["La saisie anticipée est impossible !"]}
        )

    def test_sans_destination_erreur(self):
        data = {'date_demande': date.today(),
                'date_debut_evaluation': date.today(),
                'date_fin_evaluation': date.today(),
                'date_debut_suivi': date.today(),
                'date_fin_suivi': date.today(),
                'motif_fin_suivi': 'evol_positive'}
        form = AgendaForm(data, request=self.request)
        self.assertFalse(form.is_valid())
        self.assertEqual(
            form.errors,
            {'__all__': ["Les champs «Fin de l'accompagnement», «Motif de fin» et «Destination» "
                         "sont obligatoires pour fermer le dossier."]}
        )

        data['destination'] = ''
        form = AgendaForm(data, request=self.request)
        self.assertFalse(form.is_valid())
        self.assertEqual(
            form.errors,
            {'__all__': ["Les champs «Fin de l'accompagnement», «Motif de fin» et «Destination» "
                         "sont obligatoires pour fermer le dossier."]}
        )

    def test_date_debut_suivi_anticipe_ok(self):
        data = {'date_demande': date.today(),
                'date_debut_evaluation': date.today() + timedelta(days=ANTICIPATION_POUR_DEBUT_SUIVI),
                'date_fin_evaluation': date.today() + timedelta(days=ANTICIPATION_POUR_DEBUT_SUIVI),
                'date_debut_suivi': date.today() + timedelta(days=ANTICIPATION_POUR_DEBUT_SUIVI)}
        form = AgendaForm(data, request=self.request)
        self.assertTrue(form.is_valid())

    def test_date_debut_suivi_anticipe_error(self):
        data = {'date_demande': date.today(),
                'date_debut_evaluation': date.today() + timedelta(days=ANTICIPATION_POUR_DEBUT_SUIVI + 1),
                'date_fin_evaluation': date.today() + timedelta(days=ANTICIPATION_POUR_DEBUT_SUIVI + 1),
                'date_debut_suivi': date.today() + timedelta(days=ANTICIPATION_POUR_DEBUT_SUIVI + 1)}
        form = AgendaForm(data, request=self.request)
        self.assertFalse(form.is_valid())

    def test_date_anticipee_pour_groupe_admin(self):
        famille = Famille.objects.get(nom='Haddock')
        self.client.force_login(self.user_admin)
        response = self.client.post(
            reverse('famille-agenda', args=[famille.pk]),
            data={'date_demande': '2022-09-01'},
            follow=True
        )
        self.assertContains(
            response,
            '<li class="alert alert-warning">Les dates saisies peuvent affecter les statistiques '
            'déjà communiquées !</li>',
            html=True
        )
        self.assertContains(
            response,
            '<li class="alert alert-success">Les modifications ont été enregistrées avec succès</li>',
            html=True
        )

    def test_affichage_intervention_temporaire(self):
        famille = Famille.objects.get(nom='Haddock')
        intervenant = self.user_aemo
        role = Role.objects.get(nom='Educ')
        date_debut = date.today()
        intervention = Intervenant.objects.create(
            suivi=famille.suivi, intervenant=intervenant, role=role, date_debut=date_debut, date_fin=date.today()
        )
        self.client.force_login(self.user_aemo)
        suivi_url = reverse('famille-agenda', args=[famille.pk])
        response = self.client.get(suivi_url)
        self.assertContains(
            response,
            (
                f"<div>Aemo Prénom (Educ) ({format_d_m_Y(intervention.date_debut)} - "
                f"{format_d_m_Y(intervention.date_fin)})</div>"
            ),
            html=True
        )


class JournalisationTests(InitialDataMixin, TestCase):
    def test_acces_famille_journalise(self):
        famille = Famille.objects.get(nom='Haddock')
        Intervenant.objects.create(
            suivi=famille.suivi, intervenant=self.user_aemo,
            role=Role.objects.get(nom='Educ'),
            date_debut=date.today(),
        )
        Intervenant.objects.create(
            suivi=famille.suivi, intervenant=self.user_aemo2,
            role=Role.objects.get(nom='Educ'),
            date_debut=date.today() - timedelta(days=3),
            date_fin=date.today() - timedelta(days=1),
        )
        self.client.force_login(self.user_aemo)
        suivi_url = reverse('famille-suivi', args=[famille.pk])
        response = self.client.get(suivi_url)
        self.assertTemplateUsed(response, 'aemo/suivi_edit.html')

        self.client.force_login(self.user_aemo2)
        response = self.client.get(suivi_url)
        self.assertTemplateUsed(response, 'aemo/acces_famille.html')
        response = self.client.get(suivi_url + '?confirm=1')
        self.assertTemplateUsed(response, 'aemo/suivi_edit.html')
        line = JournalAcces.objects.get(utilisateur=self.user_aemo)
        self.assertTrue(line.ordinaire)
        line = JournalAcces.objects.get(utilisateur=self.user_aemo2)
        self.assertFalse(line.ordinaire)


@tag('pdf')
class PdfTests(InitialDataMixin, TestCase):
    def setUp(self):
        super().setUp()
        self.client.force_login(self.user_aemo)

    def _test_print_pdf(self, url, filename):
        self.client.force_login(self.user_aemo)
        response = self.client.get(url)
        self.assertEqual(
            response['content-disposition'],
            'attachment; filename="{}"'.format(filename)
        )
        self.assertEqual(response['content-type'], 'application/pdf')
        self.assertGreater(len(response.getvalue()), 200)

    def test_print_evaluation(self):
        fam = Famille.objects.get(nom='Haddock')
        self._test_print_pdf(
            reverse('print-evaluation', args=(fam.pk,)),
            'haddock_aemo_evaluation.pdf'
        )
        # Avec genogramme en PDF
        with (Path('.').parent / 'aemo' / 'test.pdf').open(mode='rb') as fh:
            fam.genogramme = File(fh)
            fam.save()
        self._test_print_pdf(
            reverse('print-evaluation', args=(fam.pk,)),
            'haddock_aemo_evaluation.pdf'
        )

    def test_print_bilan(self):
        fam = Famille.objects.get(nom='Haddock')
        bilan = Bilan.objects.create(
            date=date(2020, 11, 3),
            auteur=self.user_aemo,
            famille=fam,
            objectifs="<p>Para 1<br><br>Para 2</p>",
            rythme="<p>Para 1<br><br>Para 2</p>",
        )
        self._test_print_pdf(
            reverse('print-bilan', args=(bilan.pk,)),
            'haddock_bilan_20201103.pdf'
        )

    def test_print_coord_famille_un_enfant_un_parent(self):
        fam = Famille.objects.get(nom='Haddock')
        Personne.objects.create_personne(
            famille=fam, prenom='Gaston', nom='Lagaffe',
            role=Role.objects.get(nom='Enfant suivi')
        )
        self._test_print_pdf(
            reverse('print-coord-famille', args=(fam.pk,)),
            'haddock_coordonnees.pdf'
        )

    def test_print_coord_un_enfant_deux_parents(self):
        fam = Famille.objects.get(nom='Haddock')
        Personne.objects.create_personne(
            famille=fam, prenom='Gaston', nom='Lagaffe',
            role=Role.objects.get(nom='Enfant suivi')
        )
        Personne.objects.create_personne(
            famille=fam, prenom='Maude', nom='Zarella',
            role=Role.objects.get(nom='Mère')
        )
        self._test_print_pdf(
            reverse('print-coord-famille', args=(fam.pk,)),
            'haddock_coordonnees.pdf'
        )

    def test_print_coord_pere_mere_beaupere(self):
        fam = Famille.objects.get(nom='Haddock')
        Personne.objects.create_personne(
            famille=fam, prenom='Gaston', nom='Lagaffe',
            role=Role.objects.get(nom='Enfant suivi')
        )
        Personne.objects.create_personne(
            famille=fam, prenom='Maude', nom='Zarella',
            role=Role.objects.get(nom='Mère')
        )
        Personne.objects.create_personne(
            famille=fam, prenom='Aloïs', nom='Silalune',
            role=Role.objects.get(nom='Beau-père')
        )
        self._test_print_pdf(
            reverse('print-coord-famille', args=(fam.pk,)),
            'haddock_coordonnees.pdf'
        )

    def test_print_sans_famille(self):
        famille = Famille.objects.create_famille(
            nom='Haddock', rue='Château1', npa=2000, localite='Moulinsart',
            equipe='montagnes', autorite_parentale='conjointe', statut_marital='divorce',
            monoparentale=False
        )
        Personne.objects.create_personne(
            famille=famille, prenom='Gaston', nom='Lagaffe',
            role=Role.objects.get(nom='Enfant suivi')
        )
        self._test_print_pdf(
            reverse('print-coord-famille', args=(famille.pk,)),
            'haddock_coordonnees.pdf'
        )


class PrestationTests(InitialDataMixin, TestCase):

    def setUp(self):
        super().setUp()
        self.famille = Famille.objects.get(nom='Haddock')
        Personne.objects.create_personne(
            famille=self.famille, role=Role.objects.get(nom='Enfant suivi'),
            nom='Haddock', prenom='Paulet', genre='M', date_naissance=date(1956, 2, 16),
            rue='Château1', npa=2000, localite='Moulinsart',
        )
        self.famille.suivi.intervenants.add(
            self.user_aemo, through_defaults={'role': Role.objects.get(nom='Educ')}
        )
        self.prest_fam = LibellePrestation.objects.get(code='aemo01')  # Évaluation
        self.prest_gen = LibellePrestation.objects.get(code='aemo03')

    def create_prestation_fam(self, texte=""):
        prest = Prestation.objects.create(
            auteur=self.user_aemo, date_prestation=date.today(), duree=timedelta(hours=8),
            famille=self.famille, lib_prestation=self.prest_fam, texte=texte
        )
        prest.intervenants.add(self.user_aemo, self.user_aemo2)
        return prest

    def create_prestation_gen(self):
        data = dict(
            auteur=self.user_aemo, date_prestation=date.today(), duree=timedelta(hours=8),
            famille=None, lib_prestation=self.prest_gen
        )
        prest = Prestation.objects.create(**data)
        prest.intervenants.add(self.user_aemo)
        return prest

    def test_prestation_texte_clean(self):
        """Leading and trailing empty paragraphs are stripped."""
        texte = (
            '<p></p>\n'
            '<p>Echanges mail fin de suivi, au revoir à la famille<br></p>\n'
            '<p> </p>'
        )
        data = dict(
            duree='3:40', date_prestation=date.today(), lib_prestation=self.prest_fam.pk,
            intervenants=[self.user_aemo.pk], texte=texte
        )
        form = PrestationForm(famille=self.famille, user=self.user_aemo, data=data)
        self.assertTrue(form.is_valid())
        self.assertEqual(
            form.cleaned_data['texte'],
            '<p>Echanges mail fin de suivi, au revoir à la famille<br></p>'
        )

    def test_prestation_date_prestation_dans_mois_courant(self):
        data = dict(
            duree='3:40', date_prestation=date.today(), lib_prestation=self.prest_fam.pk,
            intervenants=[self.user_aemo.pk]
        )
        form = PrestationForm(famille=self.famille, user=self.user_aemo, data=data)
        self.assertTrue(form.is_valid(), msg=form.errors)

    def test_prestation_saisie_anticipee(self):
        data = dict(
            duree='3:40', date_prestation=date.today() + timedelta(days=1),
            lib_prestation=self.prest_fam.pk, intervenants=[self.user_aemo.pk]
        )
        form = PrestationForm(famille=self.famille, user=self.user_aemo, data=data)
        self.assertFalse(form.is_valid())
        self.assertEqual(form.errors, {'date_prestation': ['La saisie anticipée est impossible !']})

    def test_prestation_saisie_tardive(self):
        data = dict(
            duree='3:40', date_prestation=date.today() - timedelta(days=31 + Prestation.DELAI_SAISIE_SUPPL),
            lib_prestation=self.prest_fam.pk, intervenants=[self.user_aemo.pk]
        )
        form = PrestationForm(famille=self.famille, user=self.user_aemo, data=data)
        self.assertFalse(form.is_valid())
        self.assertEqual(
            form.errors,
            {'date_prestation': ['La saisie des prestations des mois précédents est close !']}
        )

    def test_prestation_edit_perm_speciale(self):
        prest = self.create_prestation_fam()
        prest.date_prestation = date.today() - timedelta(days=31 + Prestation.DELAI_SAISIE_SUPPL)
        prest.save()
        self.user_admin.user_permissions.add(
            Permission.objects.get(codename='edit_prest_prev_month', content_type__app_label='aemo')
        )
        self.client.force_login(self.user_admin)
        self.client.post(reverse('prestation-edit', args=[self.famille.pk, prest.pk]), data={
            'date_prestation': prest.date_prestation,
            'duree': '00:45',
            'intervenants': [self.user_aemo.pk],
            'texte': 'foo',
        })
        prest.refresh_from_db()
        self.assertEqual(prest.texte, 'foo')

    def test_add_prestation_fam(self):
        add_url = reverse('prestation-famille-add', args=[self.famille.pk])
        data = dict(
            duree='3:40', date_prestation=date.today() - timedelta(days=3), famille=self.famille.pk,
            intervenants=[self.user_aemo.pk]
        )
        self.client.force_login(self.user_aemo)
        response = self.client.post(add_url, data)
        if response.status_code == 200:
            self.fail(response.context['form'].errors)
        self.assertRedirects(response, reverse('journal-list', args=[self.famille.pk]))
        prestation = self.famille.prestations.get(duree="3:40")
        # Évaluation choisie auto car pas de date de début de suivi
        self.assertEqual(prestation.lib_prestation.code, 'aemo01')

        # Le type de prestation dépend de la date exacte de début de suivi.
        self.famille.suivi.date_debut_suivi = date.today() - timedelta(days=1)
        self.famille.suivi.save()
        data.update({'date_prestation': date.today(), 'duree': '1:00'})
        response = self.client.post(add_url, data)
        prestation = self.famille.prestations.get(duree="1:00")
        self.assertEqual(prestation.lib_prestation.code, 'aemo02')
        data.update({'date_prestation': date.today() - timedelta(days=2), 'duree': '1:30'})
        response = self.client.post(add_url, data)
        prestation = self.famille.prestations.get(duree="1:30")
        self.assertEqual(prestation.lib_prestation.code, 'aemo01')

    def test_affichage_prestation_menu_fam(self):
        self.create_prestation_fam()
        menu_url = reverse('prestation-menu')
        self.client.force_login(self.user_aemo)
        response = self.client.get(menu_url)
        self.assertContains(
            response,
            '<td class="total">16:00</td>',
            html=True
        )
        self.assertContains(
            response,
            '<td class="mesprest">08:00</td>',
            html=True
        )

    def test_add_prestation_gen(self):
        add_url = reverse('prestation-gen-add')
        data = dict(
            duree='3:40', famille='',
            date_prestation=date.today() - timedelta(days=360),  # Too old!
            intervenants=[self.user_aemo.pk]
        )
        self.client.force_login(self.user_aemo)
        response = self.client.post(add_url, data)
        self.assertEqual(
            response.context['form'].errors,
            {'date_prestation': ['La saisie des prestations des mois précédents est close !']}
        )
        data['date_prestation'] = date.today()
        response = self.client.post(add_url, data)
        self.assertRedirects(response, reverse('prestation-gen-list'))
        self.assertEqual(self.user_aemo.prestations.first().lib_prestation.code, 'aemo03')

    def test_update_prestation_fam(self):
        prest = self.create_prestation_fam()
        url = reverse('prestation-edit', args=[self.famille.pk, prest.pk])
        self.client.force_login(self.user_aemo)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        data = model_to_dict(prest)
        data['fichier'] = ''
        data['intervenants'] = [str(interv.pk) for interv in data['intervenants']]
        data['duree'] = '12:00'
        response = self.client.post(url, data=data)
        prest.refresh_from_db()
        self.assertEqual(prest.duree, timedelta(hours=12))

    def test_correction_prestation_debut_suivi_passe(self):
        """
        Quand un début de suivi est saisi dans le passé, les prestations depuis
        cette date sont passées en accompagnement.
        """
        prest = self.create_prestation_fam()
        self.assertEqual(prest.lib_prestation.code, 'aemo01')
        self.assertIsNone(self.famille.suivi.date_debut_suivi)
        self.famille.suivi.intervenants.add(
            self.user_aemo, through_defaults={'role': Role.objects.get(nom='Educ')}
        )
        self.client.force_login(self.user_aemo)
        response = self.client.post(
            reverse('famille-agenda', args=[self.famille.pk]),
            data={
                'date_demande': date.today() - timedelta(days=4),
                'date_debut_evaluation': date.today() - timedelta(days=4),
                'date_fin_evaluation': date.today() - timedelta(days=2),
                'date_debut_suivi': date.today() - timedelta(days=2),
            },
        )
        self.assertEqual(response.status_code, 302)
        self.famille.refresh_from_db()
        self.assertIsNotNone(self.famille.suivi.date_debut_suivi)
        prest.refresh_from_db()
        self.assertEqual(prest.lib_prestation.code, 'aemo02')

    def test_droit_modification_prestation_familiale(self):
        prest = Prestation.objects.create(
            famille=self.famille,
            auteur=self.user_admin,
            lib_prestation=LibellePrestation.objects.get(code='aemo01'),
            date_prestation=date.today(),
            duree='1:10'
        )
        prest.intervenants.set([self.user_aemo])

        self.client.force_login(self.user_aemo2)
        with self.assertLogs('django.request', level='WARNING'):
            response = self.client.get(reverse('prestation-edit', args=[self.famille.pk, prest.pk]))
            self.assertEqual(response.status_code, 403)
        self.assertTrue(prest.can_edit(self.user_aemo))

        self.client.force_login(self.user_admin)
        response = self.client.get(reverse('prestation-edit', args=[self.famille.pk, prest.pk]))
        self.assertEqual(response.status_code, 200)

    def test_affichage_prestation_personnelle(self):
        self.create_prestation_fam()
        self.create_prestation_gen()
        self.client.force_login(self.user_aemo)
        response = self.client.get(reverse('prestation-personnelle'))
        self.assertContains(
            response,
            '<tr><th>Total prestations aemo01</th><td align="right">08:00</td></tr>',
            html=True
        )
        self.assertContains(
            response,
            '<tr><th>Total prestations aemo03</th><td align="right">08:00</td></tr>',
            html=True
        )
        self.assertContains(
            response,
            '<tr class="border-top"><th>Total</th><th class ="text-end">16:00</th></tr>',
            html=True
        )

    def test_affichage_prestation_generale(self):
        self.create_prestation_fam()
        self.create_prestation_gen()
        self.client.force_login(self.user_aemo)
        response = self.client.get(reverse('prestation-generale'))
        self.assertContains(
            response,
            '<td>08:00</td>',
            html=True,
            count=1
        )

    def test_suppression_prestation_famille(self):
        prest_fam = self.create_prestation_fam()
        self.client.force_login(self.user_aemo)
        response = self.client.post(reverse('prestation-delete', args=[prest_fam.famille_id, prest_fam.pk]))
        self.assertRedirects(response, reverse('journal-list', args=[prest_fam.famille_id]))
        self.assertFalse(Prestation.objects.filter(pk=prest_fam.pk).exists())

    def test_recherche_prestations(self):
        texte1 = "La famille Lucky va bien"
        texte2 = "En été, Luke tire bien plus vite que son ombre!"
        prest1 = self.create_prestation_fam(texte1)
        prest2 = self.create_prestation_fam(texte2)
        self.client.force_login(self.user_aemo)
        url = reverse('journal-list', args=[self.famille.pk])

        # Note: les chaînes à rechercher sont en majuscule pour s'assurer que
        # la recherche n'est pas sensible à la casse

        # mot contenu dans les deux enregistrements
        response = self.client.get(url, {"recherche": "BIEN"})
        self.assertQuerySetEqual(response.context["object_list"], [prest1, prest2])

        # mot contenu dans le premier enregistrement
        response = self.client.get(url, {"recherche": "FAMILLE"})
        self.assertQuerySetEqual(response.context["object_list"], [prest1])

        # mot contenu dans le deuxième enregistrement
        response = self.client.get(url, {"recherche": "OMBRE"})
        self.assertQuerySetEqual(response.context["object_list"], [prest2])

        # mot contenu dans aucun enregistrement
        response = self.client.get(url, {"recherche": "TINTIN"})
        self.assertQuerySetEqual(response.context["object_list"], [])

        # deux mots contenus chacun dans un enregistrement différent
        response = self.client.get(url, {"recherche": "FAMILLE OMBRE"})
        self.assertQuerySetEqual(response.context["object_list"], [])

        # deux mots contenus dans le même enregistrement
        response = self.client.get(url, {"recherche": "BIEN OMBRE"})
        self.assertQuerySetEqual(response.context["object_list"], [prest2])

        # Recherche d'un mot avec accent (mot clé sans accent)
        response = self.client.get(url, {"recherche": "ETE"})
        self.assertQuerySetEqual(response.context["object_list"], [prest2])

        # Recherche d'un mot sans accent (mot clé avec accent)
        response = self.client.get(url, {"recherche": "FÂMÌLLÉ"})
        self.assertQuerySetEqual(response.context["object_list"], [prest1])

    def test_message_recherche_prestations_sans_resultat(self):
        pas_de_prestation = "Aucune prestation saisie"
        recherche_vide = "Pas de résultat pour votre recherche."
        self.client.force_login(self.user_aemo)

        # Message pour contenu vide sans recherche
        response = self.client.get(reverse('journal-list', args=[self.famille.pk]))
        self.assertContains(response, pas_de_prestation)
        self.assertNotContains(response, recherche_vide)

        # Message pour contenu vide lors d'une recherche
        response = self.client.get(reverse('journal-list', args=[self.famille.pk]), {"recherche": "VIDE"})
        self.assertContains(response, recherche_vide)
        self.assertNotContains(response, pas_de_prestation)


class RapportTests(InitialDataMixin, TestCase):

    @classmethod
    def setUpTestData(cls):
        super().setUpTestData()
        cls.famille = Famille.objects.create_famille(nom='Doe')
        cls.educ = Utilisateur.objects.create_user('Educ', 'educ@exemple.org', '123')
        cls.educ.user_permissions.add(Permission.objects.get(codename='change_famille'))

    def setUp(self) -> None:
        self.create_kwargs = {
            'famille': self.famille,
            'auteur': self.educ,
            'date': date.today(),
            'situation': 'Situation initiale',
            'observations': 'Observation'
        }

    def test_create_model(self):
        rapport = Rapport.objects.create(**self.create_kwargs)
        self.assertIsInstance(rapport, Rapport)
        self.assertEqual(str(rapport), f"Résumé du {format_d_m_Y(date.today())} pour la famille Doe - ")

    def test_create_view_with_observations(self):
        self.client.force_login(self.educ)
        response = self.client.get(reverse('famille-rapport-add', args=[self.famille.pk]))
        self.assertContains(
            response,
            '<label for="id_observations">Observations, évolution et hypothèses :</label>',
            html=True
        )
        self.assertNotIn('Évaluation / Hypothèses', response)
        self.assertNotIn('Évolutions et observations', response)

    def test_display_rapport_with_observations(self):
        rapport = Rapport.objects.create(**self.create_kwargs)
        self.client.force_login(self.educ)
        response = self.client.get(reverse('famille-rapport-view', args=[self.famille.pk, rapport.pk]))

        self.assertContains(response, '<h3>Observations, évolution et hypothèses</h3>', html=True)


class NiveauTests(InitialDataMixin, TestCase):

    def setUp(self) -> None:
        self.famille = Famille.objects.get(nom='Haddock')
        self.client.force_login(self.user_aemo)

    def test_niveau_model(self):
        niv = Niveau.objects.create(
            famille=self.famille,
            niveau_interv=2,
            date_debut=date.today()
        )
        self.assertEqual(niv.famille, self.famille)
        self.assertEqual(niv.niveau_interv, 2)
        self.assertEqual(niv.date_debut, date.today())
        self.assertEqual(niv.date_fin, None)

    def test_niveau_add_view(self):
        auj = date.today()
        demain = auj + timedelta(days=1)
        response = self.client.post(
            path=reverse('niveau-add', args=[self.famille.pk]),
            data={'niveau_interv': 3, 'date_debut': demain},
            follow=True
        )
        self.assertContains(response, f"<td>{format_d_m_Y(demain)}</td><td>---</td><td>3</td>", html=True)

    def test_niveau_add_form(self):
        auj = date.today()
        demain = auj + timedelta(days=1)
        form = NiveauForm(famille=self.famille, data={'niveau_interv': 3, 'date_debut': demain})
        self.assertTrue(form.is_valid())

    def test_niveau_add_second_enregistrement(self):
        auj = date.today()
        demain = auj + timedelta(days=1)
        niv = Niveau.objects.create(famille=self.famille, niveau_interv=2, date_debut=auj, date_fin=None)
        self.client.post(
            path=reverse('niveau-add', args=[self.famille.pk]),
            data={'niveau_interv': 3, 'date_debut': demain},
            follow=True
        )
        # Mise à jour dernier enreg.
        niv.refresh_from_db()
        self.assertEqual(niv.date_fin, auj)

        # Test nouvel enreg.
        self.assertEqual(self.famille.niveaux.count(), 2)
        der_niv = self.famille.niveaux.last()
        self.assertEqual(der_niv.famille, self.famille)
        self.assertEqual(der_niv.niveau_interv, 3)
        self.assertEqual(der_niv.date_debut, demain)
        self.assertEqual(der_niv.date_fin, None)

    def test_niveau_edit_view(self):
        auj = date.today()
        demain = auj + timedelta(days=1)
        niv = Niveau.objects.create(famille=self.famille, niveau_interv=2, date_debut=auj, date_fin=None)
        self.client.post(
            path=reverse('niveau-edit', args=[self.famille.pk, niv.pk]),
            data={'niveau_interv': 3, 'date_debut': demain},
            follow=True
        )
        niv.refresh_from_db()
        self.assertEqual(niv.niveau_interv, 3)

    def test_niveau_edit_form(self):
        auj = date.today()
        demain = auj + timedelta(days=1)
        niv = Niveau.objects.create(famille=self.famille, niveau_interv=2, date_debut=auj, date_fin=None)
        form = NiveauForm(famille=self.famille, instance=niv, data={'niveau_interv': 3, 'date_debut': demain})
        self.assertTrue(form.is_valid())
        form.save()
        niv.refresh_from_db()
        self.assertEqual(niv.niveau_interv, 3)

    def test_niveau_delete(self):
        auj = date.today()
        niv = Niveau.objects.create(famille=self.famille, niveau_interv=2, date_debut=auj, date_fin=None)
        response = self.client.post(
            path=reverse('niveau-delete', args=[self.famille.pk, niv.pk]),
            follow=True
        )
        self.assertEqual(len(response.context['niveaux']), 0)

    def test_niveau_affichage_dans_agenda(self):
        auj = date.today()
        Niveau.objects.create(
            famille=self.famille, niveau_interv=2, date_debut=auj - timedelta(days=10), date_fin=None
        )
        agenda_url = reverse('famille-agenda', args=[self.famille.pk])
        response = self.client.get(agenda_url)
        self.assertContains(response, '<span class="fw-bold">Niv. d’intervention 2</span>', html=True)
        # Fin niveau affiché au plus tard à fin du suivi.
        self.famille.suivi.date_fin_suivi = auj
        self.famille.suivi.save()
        response = self.client.get(agenda_url)
        self.assertEqual(response.context['niveaux'][0].date_fin_calc, auj)

    def test_debut_suivi_selon_niveau(self):
        """Début du suivi peut varier selon changement de niveau 2/3."""
        auj = date.today()
        self.famille.suivi.date_debut_suivi = auj - timedelta(days=20)
        self.famille.suivi.save()
        Niveau.objects.create(
            famille=self.famille, niveau_interv=2,
            date_debut=auj - timedelta(days=20), date_fin=auj - timedelta(days=10)
        )
        Niveau.objects.create(
            famille=self.famille, niveau_interv=3,
            date_debut=auj - timedelta(days=10), date_fin=None
        )
        self.assertEqual(self.famille.suivi.debut_suivi_selon_niveau, auj - timedelta(days=10))


class UtilisateurTests(InitialDataMixin, TestCase):

    def test_utilisateur_list(self):
        self.client.force_login(self.user_admin)
        response = self.client.get(reverse('utilisateur-list'))
        self.assertEqual(len(response.context['object_list']), Utilisateur.objects.count())

    def test_create_utilisateur(self):
        self.client.force_login(self.user_admin)
        response = self.client.post(reverse('utilisateur-add'), data={
            'nom': 'Muller',
            'prenom': 'Hans',
            'username': 'MullerH',
            'sigle': 'HM',
            'groups': [Group.objects.create(name='grp1').pk, Group.objects.create(name='grp2').pk],
            'taux_activite': 100,
            'decharge': 2,
        })
        self.assertEqual(response.status_code, 302)
        user = Utilisateur.objects.get(nom='Muller')
        self.assertEqual(user.service.sigle, 'CRNE')
        self.assertEqual(user.username, 'MullerH')
        self.assertEqual(user.groups.count(), 2)
        self.assertEqual(user.charge_max, 30)

    def test_taux_activite(self):
        data = {
            'nom': 'Muller',
            'prenom': 'Hans',
            'username': 'MullerH',
            'sigle': 'HM',
            'taux_activite': 90,
        }
        form = UtilisateurForm(data=data)
        self.assertTrue(form.is_valid())

        form = UtilisateurForm(data={**data, 'taux_activite': 110})
        self.assertFalse(form.is_valid())
        self.assertEqual(
            form.errors['taux_activite'],
            ['Assurez-vous que cette valeur est inférieure ou égale à 100.']
        )

    def test_delete_utilisateur(self):
        user = Utilisateur.objects.create_user(username='user1', nom='Supprimer')
        self.client.force_login(self.user_admin)
        self.client.post(reverse('utilisateur-delete', args=[user.pk]))
        user.refresh_from_db()
        self.assertIsNotNone(user.date_desactivation)
        self.assertFalse(user.is_active)
        self.assertFalse(user.est_actif)

        response = self.client.get(reverse('utilisateur-autocomplete') + '?q=Sup')
        self.assertEqual(response.json()['results'], [])

    @override_settings(PASSWORD_HASHERS=PASSWORD_HASHERS)
    def test_reinit_password(self):
        Utilisateur.objects.create_superuser(
            'somebody', 'somebody@example.org', 'somebodypassword', prenom='Jean', nom='Valjean',
        )
        new_user = Utilisateur.objects.create_user(username='user')
        self.assertTrue(new_user.password.startswith('!'))  # Unusable password
        self.client.login(username='somebody', password='somebodypassword')
        self.client.post(reverse('utilisateur-password-reinit', args=[new_user.pk]))
        new_user.refresh_from_db()
        self.assertTrue(new_user.password.startswith('pbkdf2_sha256$'))  # Usable password

    def test_reinit_mobile(self):
        from django_otp.plugins.otp_totp.models import TOTPDevice
        user = Utilisateur.objects.create_user(username='user', nom="Spontz")
        TOTPDevice.objects.create(user=user, name='default')
        self.client.force_login(self.user_admin)
        response = self.client.post(reverse('utilisateur-otp-device-reinit', args=[user.pk]), follow=True)
        self.assertEqual(str(list(response.context['messages'])[0]), 'Le mobile de «Spontz » a été réinitialisé.')
        response = self.client.post(reverse('utilisateur-otp-device-reinit', args=[user.pk]), follow=True)
        self.assertEqual(
            str(list(response.context['messages'])[0]),
            'Aucune configuration mobile trouvée pour «Spontz »'
        )

    def test_liste_utilisateurs_actifs_inactifs(self):
        user1 = Utilisateur.objects.create_user(
            'User1P', 'user1@exemple.org', 'user1_password', prenom='Paul', nom='User1',
        )
        Utilisateur.objects.create_user(
            'User2M', 'user2@exemple.org', 'user2_password', prenom='Max', nom='User2',
        )
        self.client.force_login(self.user_admin)
        response = self.client.get(reverse('utilisateur-list'))
        self.assertContains(response, 'User1 Paul')
        self.assertContains(response, 'User2 Max')
        self.client.post(reverse('utilisateur-delete', args=[user1.pk]))
        response = self.client.get(reverse('utilisateur-list'))
        self.assertNotContains(response, 'User1 Paul')
        response = self.client.get(reverse('utilisateur-desactive-list'))
        self.assertContains(response, 'User1 Paul')

    def test_reactivation_utilisateur(self):
        user1 = Utilisateur.objects.create_user(
            'User1P', 'user1@exemple.org', 'user1_password', prenom='Paul', nom='User1',
        )
        Utilisateur.objects.create_user(
            'User2M', 'user2@exemple.org', 'user2_password', prenom='Max', nom='User2',
        )
        self.client.force_login(self.user_admin)
        response = self.client.post(reverse('utilisateur-delete', args=[user1.pk]), follow=True)
        self.assertNotContains(response, 'User1 Paul')
        user1.refresh_from_db()
        self.assertIsNotNone(user1.date_desactivation)
        response = self.client.post(reverse('utilisateur-reactiver', args=[user1.pk]), follow=True)
        self.assertContains(response, 'User1 Paul')
        user1.refresh_from_db()
        self.assertIsNone(user1.date_desactivation)


class OtherTests(InitialDataMixin, TestCase):
    def setUp(self):
        self.client.force_login(self.user_aemo)

    def test_contact_list(self):
        response = self.client.get(reverse('contact-list'))
        self.assertEqual(len(response.context['object_list']), 3)
        response = self.client.get(reverse('contact-list') + '?service=%s' % Service.objects.get(sigle='OPEN').pk)
        self.assertEqual(len(response.context['object_list']), 1)
        response = self.client.get(reverse('contact-list') + '?role=%s' % Role.objects.get(nom='Médecin').pk)
        self.assertEqual(len(response.context['object_list']), 1)
        response = self.client.get(reverse('contact-list') + '?texte=barn')
        self.assertEqual(len(response.context['object_list']), 1)

    def test_contact_autocomplete(self):
        medecin = Role.objects.get(nom='Médecin')
        ope_service = Service.objects.create(sigle="OPEC")
        other_service = Service.objects.get(sigle="SSE")
        contact_ope = Contact.objects.create(
            nom="Duplain", prenom="Irma", service=ope_service
        )
        contact_ope.roles.add(medecin)
        contact_other = Contact.objects.create(
            nom="Dupont", prenom="Paul", service=other_service
        )
        contact_other.roles.add(medecin)
        response = self.client.get(reverse('contact-autocomplete') + '?q=Dup')
        self.assertEqual(
            [res['text'] for res in response.json()['results']],
            ['Duplain Irma (OPEC)', 'Dupont Paul (SSE)']
        )
        # The OPE version
        response = self.client.get(reverse('contact-ope-autocomplete') + '?q=Dup')
        self.assertEqual(
            [res['text'] for res in response.json()['results']],
            ['Duplain Irma (OPEC)']
        )

    def test_supression_affichage_du_contact_desactive_dans_contact_autocomplete(self):
        medecin = Role.objects.get(nom='Médecin')
        ope_service = Service.objects.create(sigle="OPEC")
        other_service = Service.objects.get(sigle="SSE")
        contact_ope = Contact.objects.create(
            nom="Duplain", prenom="Irma", service=ope_service, est_actif=False
        )
        contact_ope.roles.add(medecin)
        contact_other = Contact.objects.create(
            nom="Dupont", prenom="Paul", service=other_service
        )
        contact_other.roles.add(medecin)
        response = self.client.get(reverse('contact-autocomplete') + '?q=Dup')
        self.assertEqual(
            [res['text'] for res in response.json()['results']],
            ['Dupont Paul (SSE)']
        )

    def test_suppression_affichage_contact_desactive_dans_list_contact(self):
        medecin = Role.objects.get(nom='Médecin')
        ope_service = Service.objects.create(sigle="OPEC")
        other_service = Service.objects.get(sigle="SSE")
        contact_ope = Contact.objects.create(
            nom="Duplain", prenom="Irma", service=ope_service, est_actif=False
        )
        contact_ope.roles.add(medecin)
        contact_other = Contact.objects.create(
            nom="Dupont", prenom="Paul", service=other_service
        )
        contact_other.roles.add(medecin)
        response = self.client.get(reverse('contact-list'))
        self.assertNotContains(response, 'Duplain')

    def test_controler_doublon_contact(self):
        medecin = Role.objects.get(nom='Médecin')
        contact1 = Contact.objects.create(
            nom="Duplain", prenom="Irma", est_actif=True
        )
        contact1.roles.add(medecin)
        doublon_url = reverse('contact-doublon')
        response = self.client.post(doublon_url, data={'nom': "Duplain", 'prenom': "Irma"})
        self.assertEqual(response.json(), [{'nom': "Duplain", 'prenom': "Irma"}])
        response = self.client.post(doublon_url, data={'nom': "Nouveau", 'prenom': "Contact"})
        # Réponse vide signifie pas de doublon détecté.
        self.assertEqual(response.json(), '')

    def test_service_creation(self):
        s1 = Service.objects.create(sigle='lower')  # transform code from lower to uppercase
        self.assertEqual(s1.sigle, 'LOWER')
        # By form
        self.client.force_login(self.user_admin)
        response = self.client.post(
            reverse('service-add'), data={'sigle': 'SERVICE', 'nom_complet': 'Super service'}
        )
        self.assertRedirects(response, reverse('service-list'))
        self.assertEqual(Service.objects.filter(sigle='SERVICE').count(), 1)

    def test_service_list(self):
        response = self.client.get(reverse('service-list'))
        self.assertEqual(len(response.context['object_list']), 3)

    def test_raise_unique_constraint_school_center_creation(self):
        sc = CercleScolaire(nom='EOREN-MAIL')
        self.assertRaises(IntegrityError, sc.save)

    def test_cerclescolaire_list(self):
        response = self.client.get(reverse('cercle-list'))
        self.assertEqual(len(response.context['object_list']), 2)

    def test_role_create(self):
        self.client.force_login(self.user_admin)
        response = self.client.post(reverse('role-add'), data={'nom': 'ROLE1', 'famille': False})
        self.assertRedirects(response, reverse('role-list'))
        self.assertEqual(Role.objects.filter(nom='ROLE1').count(), 1)

    def test_role_list(self):
        response = self.client.get(reverse('role-list'))
        self.assertGreater(len(response.context['object_list']), 10)
        # Les rôles "protégés" ne sont pas éditables.
        self.assertContains(response, '<td>Enfant suivi</td>')

    def test_delete_used_role(self):
        """A role with at least one attached Personne cannot be deleted."""
        role = Role.objects.get(nom="Père")
        Personne.objects.create_personne(
            famille=Famille.objects.create(nom='Schmurz'), role=role,
            nom='Haddock', prenom='Archibald', genre='M', date_naissance=date(1956, 2, 16),
        )
        self.assertGreater(role.personne_set.count(), 0)
        self.client.force_login(self.user_admin)
        response = self.client.post(reverse('role-delete', args=[role.pk]), follow=True)
        self.assertContains(response, "Cannot delete")

    def test_truncate_with_more_ttag(self):
        from aemo.templatetags.my_tags import truncate_html_with_more
        txt = '<p>Ceci <b>est un très long</b> texte HTML.<br> Seconde ligne</p>'
        self.assertHTMLEqual(
            truncate_html_with_more(txt, 3),
            '<div class="long hidden"><p>Ceci <b>est un très long</b> texte HTML.<br>'
            ' Seconde ligne</p></div>'
            '<div class="short"><p>Ceci <b>est un…</b></p></div>'
            '<a class="read_more" href=".">Afficher la suite</a>'
        )

    def test_info_ope_ttag(self):
        from aemo.templatetags.my_tags import info_ope
        self.assertEqual(
            info_ope(Contact.objects.get(nom='Sybarnez')),
            '<span title="032 886 88 88">Sybarnez Tina</span>'
        )

    def test_check_date_allowed(self):
        with freeze_time("2021-01-20"):
            self.assertFalse(Prestation.check_date_allowed(self.user_aemo, date(2020, 1, 20)))
        with freeze_time("2021-01-04"):
            self.assertFalse(Prestation.check_date_allowed(self.user_aemo, date(2020, 11, 30)))
            self.assertTrue(Prestation.check_date_allowed(self.user_aemo, date(2020, 12, 2)))
            self.assertTrue(Prestation.check_date_allowed(self.user_aemo, date(2021, 1, 2)))

        with freeze_time("2020-11-02"):
            self.assertFalse(Prestation.check_date_allowed(self.user_aemo, date(2019, 10, 15)))
            self.assertTrue(Prestation.check_date_allowed(self.user_aemo, date(2020, 10, 15)))
            self.assertTrue(Prestation.check_date_allowed(self.user_aemo, date(2020, 11, 12)))

    def test_duration_field(self):
        class SomeForm(Form):
            duration = HMDurationField()

        form = SomeForm({'duration': timedelta(days=1, hours=2, minutes=21, seconds=4)})
        self.assertInHTML(
            '<input type="text" name="duration" value="26:21" placeholder="hh:mm" required id="id_duration">',
            form.as_div()
        )


class TestExporter(ExportReporting):
    """A fake exporter class that just collect data in lists to be able to assert the contents."""
    def __init__(self):
        super().__init__()
        self.sheets = {}
        self._current_sh = None

    def __call__(self):
        # This allows to provide an instance to mock
        return self

    def setup_sheet(self, title):
        self.sheets[title] = []
        self._current_sh = self.sheets[title]

    def write_line(self, values, **kwargs):
        self._current_sh.append(values)


class StatTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        group_aemo = Group.objects.create(name='aemo')
        cls.user = Utilisateur.objects.create(
            username='me', first_name='Jean', last_name='Valjean',
        )
        cls.user_admin = Utilisateur.objects.create(username='admin')
        cls.user_admin.user_permissions.add(Permission.objects.get(codename='export_stats'))
        user_haut = Utilisateur.objects.create(username='ld', prenom='Lise', nom="Duhaut")
        user_haut.groups.add(group_aemo)
        user_bas = Utilisateur.objects.create(username='jd', prenom='Jean', nom="Dubas")
        user_bas.groups.add(group_aemo)

        role_referent = Role.objects.create(nom='Référent', est_famille=False)
        cls.enf_suivi = Role.objects.create(nom='Enfant suivi', est_famille=True)
        cls.role_pere = Role.objects.create(nom='Père', est_famille=True)
        cls.role_mere = Role.objects.create(nom='Mère', est_famille=True)
        cls.famille_litt = Famille.objects.create_famille(
            equipe='littoral',
            nom='Haddock', rue='Château1', npa=2000, localite='Moulinsart',
        )
        Personne.objects.create_personne(
            famille=cls.famille_litt, role=cls.enf_suivi,
            nom='Haddock', prenom='Archibald', genre='M', date_naissance=date(1996, 2, 16)
        )
        Personne.objects.create_personne(
            famille=cls.famille_litt, role=cls.enf_suivi,
            nom='Haddock', prenom='Honorine', genre='F', date_naissance=date(1999, 11, 2)
        )
        cls.famille_litt.suivi.date_demande = '2019-01-01'
        cls.famille_litt.suivi.intervenants.add(user_bas, through_defaults={'role': role_referent})
        cls.famille_litt.suivi.save()

        cls.famille_mont = Famille.objects.create_famille(
            equipe='montagnes',
            nom='Tournesol', rue='Château1', npa=2000, localite='Moulinsart',
        )
        Personne.objects.create_personne(
            famille=cls.famille_mont, role=cls.enf_suivi,
            nom='Tournesol', prenom='Tryphon', genre='M', date_naissance=date(1991, 1, 3)
        )
        cls.famille_mont.suivi.date_demande = '2019-01-01'
        cls.famille_mont.suivi.intervenants.add(user_haut, through_defaults={'role': role_referent})
        cls.famille_mont.suivi.save()
        LibellePrestation.objects.bulk_create([
            LibellePrestation(code='aemo01', nom='Évaluation AEMO'),
            LibellePrestation( code='aemo04', nom='Activités ASE'),
        ])

    def test_accueil_statistiques(self):
        self.client.force_login(self.user_admin)
        response = self.client.get(reverse('stats'))
        self.assertContains(response, 'Statistiques du 1<sup>er</sup>')

    @freeze_time("2020-12-04")
    def test_accueil_statistiques_decembre(self):
        self.client.force_login(self.user_admin)
        response = self.client.get(reverse('stats'))
        self.assertEqual(response.context['date_form'].data['end_month'], 1)
        self.assertEqual(response.context['date_form'].data['end_year'], 2021)

    def test_temps_total_prestations(self):
        """
        Test Famille.temps_total_prestations()/temps_total_prestations_reparti(),
        Test Utilisateur.temps_total_prestations()
        """
        self.assertEqual(self.famille_litt.temps_total_prestations(), timedelta(0))
        auj = date.today()
        mois_sui1 = date(auj.year, auj.month, 3)
        mois_sui2 = date(auj.year, auj.month, 4)
        user2 = Utilisateur.objects.create(
            username='you', first_name='Hans', last_name='Zwei',
        )
        user2.groups.add(Group.objects.get(name='aemo'))
        p1 = Prestation.objects.create(
            auteur=self.user,
            famille=self.famille_litt,
            date_prestation=auj,
            duree='0:45'
        )
        p1.intervenants.set([self.user])
        p2 = Prestation.objects.create(
            auteur=self.user,
            famille=self.famille_litt,
            date_prestation=mois_sui1,
            duree='1:0'
        )
        #  Chaque intervenant saisit ses propres prestations
        p2.intervenants.set([self.user])
        p3 = Prestation.objects.create(
            auteur=self.user,
            famille=self.famille_litt,
            date_prestation=mois_sui2,
            duree='1:05'
        )
        p3.intervenants.set([user2])

        # Avec ce processus, la même prestation peut avoir des durées différentes!!!

        # p1 (00:45) + 2 interv. x p2 (1:00) >> p1(00:45) + p2(1:00) + p3(1:05) = 2:50
        self.assertEqual(self.famille_litt.temps_total_prestations(), timedelta(hours=2, minutes=50))
        self.assertEqual(self.famille_litt.temps_total_prestations_reparti(), timedelta(hours=1, minutes=25))
        # self.user = p1 (00:45) + p2 (1:00) = 1:45
        # user2 = p3(1:05)
        self.assertEqual(format_duree(self.user.temps_total_prestations('aemo')), '01:45')
        self.assertEqual(format_duree(user2.temps_total_prestations('aemo')), '01:05')

    def test_stats(self):
        auj = date.today()
        mois_sui1 = date(auj.year, auj.month, 3)
        user_bas = Utilisateur.objects.get(nom='Dubas')
        p = Prestation.objects.create(
            auteur=self.user,
            famille=self.famille_litt,
            date_prestation=auj,
            duree='0:45'
        )
        p.intervenants.set([user_bas])
        p = Prestation.objects.create(
            auteur=self.user,
            famille=self.famille_litt,
            date_prestation=mois_sui1,
            duree='1:00'
        )
        p.intervenants.set([user_bas])
        p = Prestation.objects.create(
            auteur=self.user,
            famille=self.famille_litt,
            date_prestation=auj,
            duree='0:00', manque=True,
        )
        # Cette famille est du littoral, mais n'a pas Lise Dubas comme référente
        Famille.objects.create_famille(
            equipe='littoral',
            nom='Tintin', rue='Château1', npa=2000, localite='Moulinsart',
        )
        months = [Month(date.today().year, date.today().month)]
        stats = StatistiquesView(
            date_start=date.today().replace(day=1),
            date_end=(date.today().replace(day=1) + timedelta(days=35)).replace(day=1),
        ).get_stats(months)
        self.assertEqual(stats['familles']['familles_evaluees']['total'], 3)
        self.assertEqual(stats['familles']['enfants_evalues']['total'], 3)
        self.assertEqual(stats['familles']['rdv_manques']['total'], 1)

    def test_total_mensuel(self):
        """Test Famille.total_mensuel() method."""
        famille = Famille.objects.get(nom='Haddock')
        auj = date.today()
        mois_suiv = auj + timedelta(days=31)
        self.assertEqual(famille.total_mensuel(auj), timedelta(0))
        Prestation.objects.bulk_create([
            Prestation(
                auteur=self.user, famille=famille, date_prestation=date(auj.year, auj.month, 1), duree='1:30'
            ),
            Prestation(
                auteur=self.user, famille=famille, date_prestation=date(auj.year, auj.month, 25), duree='0:15'
            ),
            # Not included in this month
            Prestation(
                auteur=self.user, famille=famille, date_prestation=date(mois_suiv.year, mois_suiv.month, 1),
                duree='0:15'
            ),
        ])
        self.assertEqual(famille.total_mensuel(auj), timedelta(minutes=105))

    def test_total_prestations(self):
        famille = Famille.objects.get(nom='Haddock')
        user = Utilisateur.objects.get(nom='Duhaut')
        my_group = Group.objects.get(name='aemo')
        my_group.user_set.add(user)
        prestation_data = {
            'auteur': self.user, 'famille': famille, 'date_prestation': date(2019, 3, 1),
            'lib_prestation': LibellePrestation.objects.get(code='aemo01'),
        }
        Prestation.objects.bulk_create([
            Prestation(**{**prestation_data, 'duree': '3:40'}),
            Prestation(**{**prestation_data, 'duree': '6:40'}),
            # Not included in this month
            Prestation(**{**prestation_data, 'date_prestation': date(2019, 4, 1), 'duree': '2:00'}),
        ])
        for p in Prestation.objects.filter(famille=famille):
            p.intervenants.set([user])

        self.assertEqual(format_duree(user.total_mensuel('aemo', 3, 2019)), '10:20')
        self.assertEqual(format_duree(user.totaux_mensuels('aemo', 2019)[3]), '02:00')
        self.assertEqual(format_duree(user.total_annuel('aemo', 2019)), '12:20')
        totaux_2019 = Prestation.temps_totaux_mensuels(2019)
        self.assertEqual(totaux_2019[0], timedelta())
        self.assertEqual(format_duree(totaux_2019[2]), '10:20')
        self.assertEqual(format_duree(totaux_2019[3]), '02:00')
        self.assertEqual(format_duree(Prestation.temps_total_general(2019)), '12:20')

        user.user_permissions.add(Permission.objects.get(codename='export_stats'))
        self.client.force_login(user)
        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), {'mois': '3', 'annee': '2019'})
        self.assertEqual(format_duree(exp._total_spe['eval']), '10:20')

    def test_export_prestations_decembre(self):
        self.client.force_login(self.user_admin)
        response = self.client.post(reverse('export-prestation'), {'mois': '12', 'annee': '2019'})
        self.assertEqual(response.status_code, 200)

    @freeze_time("2020-12-14")
    def test_affichage_prestation(self):
        famille = Famille.objects.get(nom='Tournesol')
        auj = date.today()
        self.assertEqual(famille.total_mensuel(auj), timedelta(0))
        user_haut = Utilisateur.objects.get(nom='Duhaut')
        user_haut.taux_activite = 100.0
        user_haut.save()
        Prestation.objects.bulk_create([
            Prestation(auteur=user_haut, famille=famille,
                          date_prestation=date(auj.year, 3, 1), duree='1:45'),
            Prestation(auteur=user_haut, famille=famille,
                          date_prestation=date(auj.year, 5, 25), duree='0:15'),
            # Not included in this month
            Prestation(auteur=user_haut, famille=famille,
                          date_prestation=date(auj.year, 7, 1), duree='0:15'),
        ])
        for p in Prestation.objects.filter(famille=famille):
            p.intervenants.set([user_haut])

        self.user.user_permissions.add(Permission.objects.get(codename='export_stats'))
        self.client.force_login(self.user)
        response = self.client.get(reverse('stats-prestations'))
        data = response.context['intervenants'][user_haut]
        self.assertEqual(format_duree(data['heures_prestees'][2]), '01:45')
        self.assertEqual(format_duree(data['heures_prestees'][4]), '00:15')
        self.assertEqual(format_duree(data['heures_prestees'][6]), '00:15')

        self.assertEqual(format_duree(data['tot_prestees']), '02:15')
        self.assertEqual(format_duree(response.context['totaux_prest_mensuels'][Month(auj.year, 3)]['total']), '01:45')
        self.assertEqual(format_duree(response.context['total_gen']), '02:15')

    @skip("This statistic is currently deactivated")
    def test_stats_interv(self):
        last_year = date.today().year - 1
        famille = Famille.objects.get(nom='Haddock')
        famille.suivi.date_debut_suivi = date(last_year, 2, 1)
        famille.suivi.save()
        user_bas = Utilisateur.objects.get(nom='Dubas')
        self.client.force_login(self.user_admin)
        params = '&'.join(
            f'{key}={val}' for key, val in {
                'start_year': last_year, 'start_month': '3', 'end_year': last_year, 'end_month': '5'
            }.items()
        )
        response = self.client.get(reverse('stats-interv') + '?' + params)
        self.assertEqual(response.context['interv_spe'][user_bas]['num_familles']['total'], 1)

    def test_stats_par_niveau(self):
        auj = date.today()
        Niveau.objects.create(
            famille=self.famille_litt, niveau_interv=2, date_debut=auj - timedelta(days=1)
        )
        # Niveau obsolète
        Niveau.objects.create(
            famille=self.famille_litt, niveau_interv=1,
            date_debut=auj - timedelta(days=360), date_fin=auj - timedelta(days=100)
        )
        Prestation.objects.create(
            famille=self.famille_litt, auteur=self.user, date_prestation=auj,
            lib_prestation=LibellePrestation.objects.get(code='aemo04'),
            duree=timedelta(minutes=90)
        )

        ilya2mois = auj - timedelta(days=60)
        params = '&'.join(
            f'{key}={val}' for key, val in {
                'start_year': ilya2mois.year, 'start_month': ilya2mois.month,
                'end_year': auj.year, 'end_month': auj.month,
            }.items()
        )

        self.client.force_login(self.user_admin)
        response = self.client.get(reverse('stats-niveaux') + '?' + params)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            response.context['stats'][2]['aemo04'][Month.from_date(auj)], timedelta(minutes=90)
        )
        self.assertEqual(
            response.context['stats'][2]['aemo04']['total'], timedelta(minutes=90)
        )


class ExportTests(InitialDataMixin, TestCase):

    def setUp(self):
        self.client.force_login(self.user_admin)
        self.cfg_date = date(2019, 10, 3)
        self.cfg_sheet = 'En_cours_{}.{}'.format(self.cfg_date.month, self.cfg_date.year)
        self.cfg_export_month = {'mois': self.cfg_date.month, 'annee': self.cfg_date.year}

        self.entetes_communs = [
            'Institution', 'Prestation', 'Nom', 'Prenom', 'Genre', 'Date de naissance', 'Adresse', 'NPA', 'Localité',
            'Canton', 'OPE', 'Nom mère', 'Prénom mère', 'Nom père', 'Prénom père', 'Autorité parentale',
            'Statut marital', 'Statut financier', 'Fam. monopar.', 'Nbre enfants',
            'Date demande', 'Provenance', 'Motif demande', 'Début suivi'
        ]

        self.entetes_nouveaux = self.entetes_communs
        self.entetes_en_cours = self.entetes_communs + ['H. Évaluation', 'H. Suivi', 'H. Prest. gén.']
        self.entetes_termines = (self.entetes_communs +
                                 ['Date fin suivi', 'Motif fin suivi', 'Destination', 'Total heures'])

        self.enf_suivi = Role.objects.get(nom='Enfant suivi', est_famille=True)
        self.role_pere = Role.objects.get(nom='Père', est_famille=True)
        self.role_mere = Role.objects.get(nom='Mère', est_famille=True)

    def test_entete_nouveux_suivis(self):
        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), {'mois': '10', 'annee': '2019'})
        sheet_name = 'Nouveaux_10.2019'
        self.assertEqual(len(exp.sheets[sheet_name]), 1)
        self.assertEqual(exp.sheets[sheet_name][0], self.entetes_nouveaux)

    def test_entete_suivis_en_cours(self):
        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), {'mois': '10', 'annee': '2019'})
        sheet_name = 'En_cours_10.2019'
        self.assertEqual(len(exp.sheets[sheet_name]), 1)
        self.assertEqual(exp.sheets[sheet_name][0], self.entetes_en_cours)

    def test_entete_suivis_termines(self):
        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), {'mois': '10', 'annee': '2019'})
        sheet_name = 'Terminés_10.2019'
        self.assertEqual(len(exp.sheets[sheet_name]), 1)
        self.assertEqual(exp.sheets[sheet_name][0], self.entetes_termines)

    def test_export_prestations_decembre(self):
        self.client.force_login(self.user_admin)
        response = self.client.post(reverse('export-prestation'), {'mois': '12', 'annee': '2019'})
        self.assertEqual(response.status_code, 200)

    def test_date_nouveau_suivi(self):
        fam1 = self.create_famille(name='Haddock')
        fam1.suivi.date_demande = '2019-09-20'
        fam1.suivi.save()

        fam2 = self.create_famille(name='Tournesol')
        fam2.suivi.date_demande = '2019-10-20'
        fam2.suivi.save()

        fam3 = self.create_famille(name='Castafiore')
        fam3.suivi.date_demande = '2019-11-20'
        fam3.suivi.save()

        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), {'mois': '10', 'annee': '2019'})
        sheet_name = 'Nouveaux_10.2019'
        self.assertEqual(len(exp.sheets[sheet_name]), 2)
        self.assertEqual([line[2] for line in exp.sheets[sheet_name]], ['Nom', 'Tournesol'])

    def test_date_suivi_en_cours(self):
        fam1 = self.create_famille(name='Haddock')
        fam1.suivi.date_demande = '2019-09-20'
        fam1.suivi.save()

        fam2 = self.create_famille(name='Tournesol')
        fam2.suivi.date_demande = '2019-10-20'
        fam2.suivi.save()

        fam3 = self.create_famille(name='Castafiore')
        fam3.suivi.date_demande = '2019-11-20'
        fam3.suivi.save()

        self.benef1 = dict(nom='Haddock', date_admission='2019-10-02')
        self.benef2 = dict(nom='Tournesol', date_admission='2019-09-30')

    def test_date_suivi_termine(self):
        fam1 = self.create_famille(name='Haddock')
        fam1.suivi.date_demande = '2019-09-20'
        fam1.suivi.date_fin_suivi = '2019-10-31'
        fam1.suivi.save()

        fam2 = self.create_famille(name='Tournesol')
        fam2.suivi.date_demande = '2019-10-20'
        fam2.suivi.save()

        fam3 = self.create_famille(name='Castafiore')
        fam3.suivi.date_demande = '2019-11-20'
        fam3.suivi.date_fin_suivi = '2019-12-31'
        fam3.suivi.save()

        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), {'mois': '10', 'annee': '2019'})
        sheet_name = 'Terminés_10.2019'
        self.assertEqual(len(exp.sheets[sheet_name]), 2)
        self.assertEqual([line[2] for line in exp.sheets[sheet_name]], ['Nom', 'Haddock'])

    def test_un_enfant_par_ligne(self):
        fam = self.create_famille()
        fam.suivi.date_demande = '2019-10-20'
        fam.suivi.save()
        Personne.objects.create_personne(
            famille=fam, role=Role.objects.get(nom='Enfant suivi'),
            nom='Haddock', prenom='Ursule', genre='M', date_naissance=date(2008, 11, 6),
        )

        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), {'mois': '10', 'annee': '2019'})
        sheet_name = 'Nouveaux_10.2019'
        self.assertEqual(len(exp.sheets[sheet_name]), 3)
        self.assertEqual(
            [(line[2], line[3]) for line in exp.sheets['Nouveaux_10.2019']],
            [('Nom', 'Prenom'), ('Haddock', 'Ursule'), ('Haddock', 'Toto')]
        )

    def test_repartition_temps_total_prestation_mensuel_entre_enfants(self):
        fam = self.create_famille()
        fam.suivi.date_demande = '2019-10-20'
        fam.suivi.save()
        Personne.objects.create_personne(
            famille=fam, role=Role.objects.get(nom='Enfant suivi'),
            nom='Haddock', prenom='Ursule', genre='M', date_naissance=date(2008, 11, 6),
        )
        aemo01 = LibellePrestation.objects.get(code='aemo01')
        Prestation.objects.bulk_create([
            Prestation(
                auteur=self.user_aemo, famille=fam, date_prestation=date(2019, 10, 21), lib_prestation=aemo01, duree='3:40'
            ),
            Prestation(
                auteur=self.user_aemo, famille=fam, date_prestation=date(2019, 10, 22), lib_prestation=aemo01, duree='6:40'
            ),
            Prestation(
                auteur=self.user_aemo, famille=fam, date_prestation=date(2019, 10, 30), lib_prestation=aemo01, duree='2:00'
            ),
        ])
        for prest in Prestation.objects.all():
            prest.intervenants.add(self.user_aemo)

        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), {'mois': '10', 'annee': '2019'})
        sheet_name = 'En_cours_10.2019'
        self.assertEqual(len(exp.sheets[sheet_name]), 3)
        self.assertEqual(
            [(line[2], line[3], line[24]) for line in exp.sheets[sheet_name]],
            [('Nom', 'Prenom', 'H. Évaluation'),
             ('Haddock', 'Ursule', timedelta(hours=6, minutes=10)),
             ('Haddock', 'Toto', timedelta(hours=6, minutes=10)),
             ]
        )

    def test_export_nouvelle_famille(self):
        fam = self.create_famille()
        fam.suivi.date_demande = '2019-10-20'
        fam.suivi.date_debut_suivi = '2019-10-21'
        fam.suivi.motif_demande = ['integration']
        fam.suivi.save()
        Prestation.objects.bulk_create([
            Prestation(auteur=self.user_aemo, famille=fam, date_prestation=date(2019, 10, 21), duree='3:40'),
            Prestation(auteur=self.user_aemo, famille=fam, date_prestation=date(2019, 10, 22), duree='6:40'),
            # Not included in this month
            Prestation(auteur=self.user_aemo, famille=fam, date_prestation=date(2019, 11, 1), duree='2:00'),
        ])
        self.assertEqual(Prestation.objects.filter(famille=fam).count(), 3)
        for p in Prestation.objects.filter(famille=fam):
            p.intervenants.set([self.user_aemo])

        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), {'mois': '10', 'annee': '2019'})
        sheet_name = 'Nouveaux_10.2019'
        self.assertEqual(len(exp.sheets[sheet_name]), 2)
        self.assertEqual(
            exp.sheets[sheet_name][1],
            ['Fondation Transit', 'AEMO', 'Haddock', 'Toto', 'M', '16.02.2010', 'Château1', '2000', 'Moulinsart', 'NE',
             'Sybarnez Tina', '', '', 'Haddock', 'Archibald', 'Conjointe', 'Divorcé', '', 'NON', 1,
             '20.10.2019', '', 'Aide à l’intégration', '21.10.2019']
        )

    def test_export_total_prestation_mois_courant(self):
        fam = self.create_famille()
        fam.suivi.date_demande = '2019-10-20'
        fam.suivi.save()
        aemo01 = LibellePrestation.objects.get(code='aemo01')
        Prestation.objects.bulk_create([
            Prestation(
                auteur=self.user_aemo, famille=fam, date_prestation=date(2019, 10, 21), lib_prestation=aemo01, duree='3:40'
            ),
            Prestation(
                auteur=self.user_aemo, famille=fam, date_prestation=date(2019, 10, 22), lib_prestation=aemo01, duree='6:40'
            ),
            # Not included in this month
            Prestation(
                auteur=self.user_aemo, famille=fam, date_prestation=date(2019, 11, 1), lib_prestation=aemo01, duree='2:00'
            ),
        ])
        for p in Prestation.objects.filter(famille=fam):
            p.intervenants.set([self.user_aemo])

        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), {'mois': '10', 'annee': '2019'})
        self.assertEqual(len(exp.sheets['En_cours_10.2019']), 2)
        self.assertEqual(exp.sheets['En_cours_10.2019'][1][24], timedelta(hours=10, minutes=20))

    def test_export_prest_gen(self):
        familles = [self.create_famille('Fam_{}'.format(i)) for i in range(5)]
        for famille in familles:
            famille.suivi.date_demande = date(2019, 10, 1)
            famille.suivi.save()
        prestation_data = {
            'auteur': self.user_aemo, 'famille': None, 'date_prestation': date(2019, 10, 21),
            'lib_prestation': LibellePrestation.objects.get(code='aemo03')
        }
        Prestation.objects.create(**{**prestation_data, 'duree': '6:50'})
        Prestation.objects.create(**{**prestation_data, 'duree': '3:50'})

        for prest in Prestation.objects.all():
            prest.intervenants.add(self.user_aemo)

        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), self.cfg_export_month)
        self.assertEqual(len(exp.sheets[self.cfg_sheet]), 6)
        self.assertEqual(format_duree(exp._total_spe['gen']), '10:40')
        # Les 10h40 sont divisées par le nombre d'enfants suivis (= 5 * 2h08):
        self.assertEqual(exp.sheets[self.cfg_sheet][1][26], timedelta(hours=2, minutes=8))
        self.assertEqual(exp.sheets[self.cfg_sheet][2][26], timedelta(hours=2, minutes=8))
        self.assertEqual(exp.sheets[self.cfg_sheet][3][26], timedelta(hours=2, minutes=8))
        self.assertEqual(exp.sheets[self.cfg_sheet][4][26], timedelta(hours=2, minutes=8))
        self.assertEqual(exp.sheets[self.cfg_sheet][5][26], timedelta(hours=2, minutes=8))

    def test_export_evaluation(self):
        familles = [self.create_famille('Fam_{}'.format(i)) for i in range(3)]
        for famille in familles:
            famille.suivi.date_demande = date(2019, 10, 1)
            famille.suivi.save()
        prestation_data = {
            'auteur': self.user_aemo, 'date_prestation': date(2019, 10, 21),
            'lib_prestation': LibellePrestation.objects.get(code='aemo01'),
        }
        Prestation.objects.bulk_create([
            Prestation(**{**prestation_data, 'famille': familles[0], 'duree': '6:50'}),
            Prestation(**{**prestation_data, 'famille': familles[0], 'duree': '1:00'}),
            Prestation(**{**prestation_data, 'famille': familles[1], 'duree': '1:25'}),
            Prestation(**{**prestation_data, 'famille': familles[2], 'duree': '4:40'}),
        ])
        for prest in Prestation.objects.all():
            prest.intervenants.add(self.user_aemo)
        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), self.cfg_export_month)
        sheet_name = exp.sheets[self.cfg_sheet]
        self.assertEqual(len(sheet_name), 4)
        self.assertEqual(sheet_name[1][24], timedelta(hours=7, minutes=50))
        self.assertEqual(sheet_name[2][24], timedelta(hours=1, minutes=25))
        self.assertEqual(sheet_name[3][24], timedelta(hours=4, minutes=40))
        self.assertEqual(format_duree(exp._total_spe['eval']), '13:55')

    def test_export_suivi(self):
        familles = [self.create_famille('Fam_{}'.format(i)) for i in range(3)]
        for famille in familles:
            famille.suivi.date_demande = date(2019, 10, 1)
            famille.suivi.save()
        prestation_data = dict(
            {'auteur': self.user_aemo, 'date_prestation': date(2019, 10, 21),
             'lib_prestation': LibellePrestation.objects.get(code='aemo02')}
        )
        Prestation.objects.bulk_create([
            Prestation(**{**prestation_data, 'famille': familles[0], 'duree': '1:10'}),
            Prestation(**{**prestation_data, 'famille': familles[0], 'duree': '2:20'}),
            Prestation(**{**prestation_data, 'famille': familles[1], 'duree': '3:30'}),
            Prestation(**{**prestation_data, 'famille': familles[2], 'duree': '4:45'}),
        ])
        for prest in Prestation.objects.all():
            prest.intervenants.add(self.user_aemo)
        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), self.cfg_export_month)
        sheet_name = exp.sheets[self.cfg_sheet]
        self.assertEqual(len(sheet_name), 4)
        self.assertEqual(sheet_name[1][25], timedelta(hours=3, minutes=30))
        self.assertEqual(sheet_name[2][25], timedelta(hours=3, minutes=30))
        self.assertEqual(sheet_name[3][25], timedelta(hours=4, minutes=45))
        self.assertEqual(format_duree(exp._total_spe['suivi']), '11:45')

    def test_export_authorization(self):
        self.client.force_login(self.user_externe)
        # Test denied access without export_stats permission
        with self.assertLogs('django.request', level='WARNING'):
            response = self.client.get(reverse('export-prestation'))
        self.assertEqual(response.status_code, 403)

        self.user_externe.user_permissions.add(Permission.objects.get(codename='export_stats'))
        # Test default selected options in export form
        with patch('aemo.views.date') as mock_date:
            mock_date.today.return_value = date(2019, 7, 3)
            response = self.client.get(reverse('export-prestation'))
            self.assertContains(
                response,
                '<option value="6" selected>juin</option>'
            )
            self.assertContains(
                response,
                '<option value="2019" selected>2019</option>'
            )

            mock_date.today.return_value = date(2020, 1, 30)
            response = self.client.get(reverse('export-prestation'))
            self.assertContains(
                response,
                '<option value="12" selected>décembre</option>'
            )
            self.assertContains(
                response,
                '<option value="2019" selected>2019</option>'
            )

        response = self.client.post(reverse('export-prestation'), data={'mois': '2', 'annee': '2019'})
        self.assertEqual(response['Content-Type'], openxml_contenttype)
        self.assertEqual(
            response['Content-Disposition'],
            'attachment; filename="aemo_reporting_02_2019.xlsx"'
        )

    def test_openxml_export_sheets(self):
        # "Nouvelle" famille
        famille = Famille.objects.create_famille(
            equipe='littoral',
            nom='NouvelleFamille', rue='Château 4', npa=2000, localite='Moulinsart',
        )
        famille.suivi.date_demande = '2019-02-10'
        # famille.suivi.date_fin_suivi = '2019-05-30'
        famille.suivi.save()
        Personne.objects.create_personne(
            famille=famille, role=Role.objects.get(nom='Enfant suivi'),
            nom='NouvelleFamille', prenom='Hégésipe', genre='M', date_naissance=date(1996, 2, 16)
        )

        # Famille "en cours"
        famille = Famille.objects.create_famille(
            equipe='littoral',
            nom='FamilleEnCours', rue='Château 5', npa=2000, localite='Moulinsart',
            autorite_parentale='conjointe', statut_marital='divorce', monoparentale=False,
        )
        famille.suivi.date_demande = '2019-01-01'
        famille.suivi.date_debut_suivi = '2019-01-28'
        ope_service = Service.objects.create(sigle="OPEC")
        famille.suivi.ope_referent = Contact.objects.create(
            nom="Duplain", prenom="Irma", service=ope_service
        )
        famille.suivi.save()
        Personne.objects.create_personne(
            famille=famille, role=Role.objects.get(nom='Enfant suivi'),
            nom='FamilleEnCours', prenom='Alain', genre='M', date_naissance=date(2003, 4, 23),
            localite='Moulinsart',
        )
        Personne.objects.create_personne(
            famille=famille, role=self.role_mere,
            nom='FamilleEnCours', prenom='Judith', genre='F', date_naissance=date(1974, 11, 2)
        )
        Personne.objects.create_personne(
            famille=famille, role=self.role_pere,
            nom='NomDuPere', prenom='Hans', genre='M', date_naissance=date(1968, 3, 13)
        )

        # Famille "terminée"
        famille = Famille.objects.create_famille(
            nom='FamilleTerminée', rue='Château 6', npa=2000, localite='Moulinsart',
        )
        famille.suivi.date_demande = '2018-10-02'
        famille.suivi.date_debut_suivi = '2018-11-11'
        famille.suivi.date_fin_suivi = '2019-02-20'
        famille.suivi.motif_fin_suivi = 'relai'
        famille.suivi.save()
        Personne.objects.create_personne(
            famille=famille, role=self.enf_suivi,
            nom='FamilleTerminée', prenom='Jeanne', genre='F', date_naissance=date(1998, 12, 14)
        )

        self.client.force_login(self.user_admin)
        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), {'mois': '2', 'annee': '2019'})

        self.assertEqual(len(exp.sheets['En_cours_02.2019']), 4)
        self.assertEqual(exp.sheets['En_cours_02.2019'][0], self.entetes_en_cours)
        self.assertEqual(
            exp.sheets['En_cours_02.2019'][1],
            ['Fondation Transit', 'AEMO', 'FamilleEnCours', 'Alain', 'M', '23.04.2003', '', '', 'Moulinsart', 'NE',
             'Duplain Irma', 'FamilleEnCours', 'Judith', 'NomDuPere', 'Hans', 'Conjointe', 'Divorcé', '', 'NON', 1,
             '01.01.2019', '', '', '28.01.2019',
             timedelta(0), timedelta(0), timedelta(0)]
        )
        self.assertEqual(
            [(line[2], line[3]) for line in exp.sheets['En_cours_02.2019'][1:]],
            [('FamilleEnCours', 'Alain'), ('FamilleTerminée', 'Jeanne'), ('NouvelleFamille', 'Hégésipe')]
        )

        self.assertEqual(len(exp.sheets['Nouveaux_02.2019']), 2)
        self.assertEqual(exp.sheets['Nouveaux_02.2019'][0], self.entetes_nouveaux)
        self.assertEqual(
            exp.sheets['Nouveaux_02.2019'][1],
            ['Fondation Transit', 'AEMO', 'NouvelleFamille', 'Hégésipe', 'M', '16.02.1996', '', '', '', 'NE',
             '', '', '', '', '', '', '', '', '', 1,
             '10.02.2019', '', '', ''
             ]
        )
        self.assertEqual(len(exp.sheets['Terminés_02.2019']), 2)
        self.assertEqual(exp.sheets['Terminés_02.2019'][0], self.entetes_termines)
        self.assertEqual(
            exp.sheets['Terminés_02.2019'][1],
            ['Fondation Transit', 'AEMO', 'FamilleTerminée', 'Jeanne', 'F', '14.12.1998', '', '', '', 'NE',
             '', '', '', '', '', '', '', '', '', 1,
             '02.10.2018', '', '', '11.11.2018',
             '20.02.2019', 'Relai vers autre service', '', timedelta(0)
             ]
        )

        famille = Famille.objects.get(nom='NouvelleFamille')
        famille.suivi.motif_fin_suivi = 'erreur'
        famille.suivi.date_fin_suivi = date(2019, 2, 17)
        famille.suivi.save()

        famille = Famille.objects.get(nom='FamilleTerminée')
        famille.suivi.motif_fin_suivi = 'erreur'
        famille.suivi.date_fin_suivi = date(2019, 2, 17)
        famille.suivi.save()

        exp = TestExporter()
        with patch('aemo.views.ExportReporting', new=exp):
            self.client.post(reverse('export-prestation'), {'mois': '2', 'annee': '2019'})
        # Plus de famille, puisque les motifs de fin de suivi "erreur" ne sont pas pris en compte
        self.assertEqual(len(exp.sheets['Nouveaux_02.2019']), 1)
        self.assertEqual(len(exp.sheets['Terminés_02.2019']), 1)
        # 'NouvelleFamille' plus exportée
        self.assertEqual(len(exp.sheets['En_cours_02.2019']), 2)
