Project

General

Profile

Download (37.8 KB) Statistics
| Branch: | Tag: | Revision:

calebasse / calebasse / dossiers / models.py @ 21fb4def

1
# -*- coding: utf-8 -*-
2

    
3
import logging
4
import os
5

    
6
from datetime import datetime, date
7
from dateutil.relativedelta import relativedelta
8
from cPickle import loads, dumps
9

    
10
from django import forms
11
from django.conf import settings
12
from django.db import models
13
from django.db.models import Min, Max, Q
14
from django.contrib.auth.models import User
15
from django.core.validators import MinValueValidator
16

    
17
import reversion
18

    
19
from calebasse.choices import LARGE_REGIME_CHOICES, TYPE_OF_CONTRACT_CHOICES
20
from calebasse.models import PhoneNumberField, ZipCodeField
21
from calebasse.personnes.models import People
22
from calebasse.ressources.models import (ServiceLinkedAbstractModel,
23
        NamedAbstractModel, Service)
24
from calebasse.actes.validation import are_all_acts_of_the_day_locked
25
from calebasse.actes.models import Act
26

    
27
DEFAULT_ACT_NUMBER_DIAGNOSTIC = 6
28
DEFAULT_ACT_NUMBER_TREATMENT = 30
29
DEFAULT_ACT_NUMBER_PROLONGATION = 10
30
VALIDITY_PERIOD_TREATMENT_HEALTHCARE_DAYS = 0
31
VALIDITY_PERIOD_TREATMENT_HEALTHCARE_MONTHS = 0
32
VALIDITY_PERIOD_TREATMENT_HEALTHCARE_YEARS = 1
33

    
34
logger = logging.getLogger('calebasse.dossiers')
35

    
36

    
37
class TransportPrescriptionLog(models.Model):
38
    patient = models.ForeignKey('dossiers.PatientRecord',
39
        verbose_name=u'Dossier patient')
40
    created = models.DateTimeField(u'Création', auto_now_add=True)
41
    choices = models.CharField(max_length = 4096, null=True, blank=True)
42

    
43
    def get_choices(self):
44
        if not self.choices:
45
            return dict()
46
        return loads(str(self.choices), protocol = 1)
47

    
48
    def set_choices(self, choices=None):
49
        if choices and isinstance(choices, dict):
50
            self.choices = dumps(choices, protocol = 1)
51

    
52

    
53
class HealthCare(models.Model):
54

    
55
    class Meta:
56
        app_label = 'dossiers'
57

    
58
    start_date = models.DateField(verbose_name=u"Date de début")
59
    request_date = models.DateField(verbose_name=u"Date de demande",
60
        blank=True, null=True)
61
    agree_date = models.DateField(verbose_name=u"Date d'accord",
62
        blank=True, null=True)
63
    insist_date = models.DateField(verbose_name=u"Date de relance",
64
        blank=True, null=True)
65
    patient = models.ForeignKey('dossiers.PatientRecord',
66
        verbose_name=u'Dossier patient')
67
    created = models.DateTimeField(u'Création', auto_now_add=True)
68
    author = \
69
        models.ForeignKey(User,
70
        verbose_name=u'Auteur', blank=True, null=True)
71
    comment = models.TextField(max_length=3000, blank=True, null=True, verbose_name=u"Commentaire")
72

    
73
    def get_nb_acts_cared(self):
74
        return len(self.act_set.all())
75

    
76

    
77
class CmppHealthCareDiagnostic(HealthCare):
78

    
79
    class Meta:
80
        app_label = 'dossiers'
81

    
82
    act_number = models.IntegerField(default=DEFAULT_ACT_NUMBER_DIAGNOSTIC, verbose_name=u"Nombre d'actes couverts")
83
    end_date = models.DateField(verbose_name=u"Date de fin",
84
        blank=True, null=True)
85

    
86
    def get_act_number(self):
87
        return self.act_number
88

    
89
    def set_act_number(self, value):
90
        if value < self.get_nb_acts_cared():
91
            raise Exception("La valeur doit être supérieur au "
92
                "nombre d'actes déjà pris en charge")
93
        self.act_number = value
94
        self.save()
95

    
96
    def save(self, **kwargs):
97
        self.start_date = \
98
            datetime(self.start_date.year, self.start_date.month,
99
                self.start_date.day)
100
        super(CmppHealthCareDiagnostic, self).save(**kwargs)
101

    
102

    
103
class CmppHealthCareTreatment(HealthCare):
104

    
105
    class Meta:
106
        app_label = 'dossiers'
107

    
108
    act_number = models.IntegerField(default=DEFAULT_ACT_NUMBER_TREATMENT,
109
            verbose_name=u"Nombre d'actes couverts")
110
    end_date = models.DateField(verbose_name=u"Date de fin",
111
        blank=True, null=True)
112
    prolongation = models.IntegerField(default=0,
113
            verbose_name=u'Prolongation')
114
    prolongation_date = models.DateField(verbose_name=u"Date de prolongation",
115
        blank=True, null=True)
116

    
117
    def get_act_number(self):
118
        if self.is_extended():
119
            return self.act_number + self.prolongation
120
        return self.act_number
121

    
122
    def set_act_number(self, value):
123
        if value < self.get_nb_acts_cared():
124
            raise Exception("La valeur doit être supérieur au "
125
                "nombre d'actes déjà pris en charge")
126
        self.act_number = value
127
        self.save()
128

    
129
    def is_extended(self):
130
        if self.prolongation > 0:
131
            return True
132
        return False
133

    
134
    def add_prolongation(self, value=None):
135
        if not value:
136
            value = DEFAULT_ACT_NUMBER_PROLONGATION
137
        if self.is_extended():
138
            raise Exception(u'Prise en charge déja prolongée')
139
        self.prolongation = value
140
        self.save()
141

    
142
    def del_prolongation(self):
143
        pass
144

    
145
    def save(self, **kwargs):
146
        self.start_date = \
147
            datetime(self.start_date.year, self.start_date.month,
148
                self.start_date.day)
149
        if not self.end_date:
150
            self.end_date = self.start_date + \
151
                relativedelta(years=VALIDITY_PERIOD_TREATMENT_HEALTHCARE_YEARS) + \
152
                relativedelta(months=VALIDITY_PERIOD_TREATMENT_HEALTHCARE_MONTHS) + \
153
                relativedelta(days=VALIDITY_PERIOD_TREATMENT_HEALTHCARE_DAYS-1)
154
        super(CmppHealthCareTreatment, self).save(**kwargs)
155

    
156

    
157
class SessadHealthCareNotification(HealthCare):
158

    
159
    class Meta:
160
        app_label = 'dossiers'
161

    
162
    end_date = models.DateField(verbose_name=u"Date de fin",
163
        blank=True, null=True)
164

    
165
    def save(self, **kwargs):
166
        self.start_date = \
167
            datetime(self.start_date.year, self.start_date.month,
168
                self.start_date.day)
169
        if self.end_date:
170
            self.end_date = \
171
                datetime(self.end_date.year, self.end_date.month,
172
                    self.end_date.day)
173
        super(SessadHealthCareNotification, self).save(**kwargs)
174

    
175
reversion.register(CmppHealthCareDiagnostic, follow=['healthcare_ptr'])
176
reversion.register(CmppHealthCareTreatment, follow=['healthcare_ptr'])
177
reversion.register(SessadHealthCareNotification, follow=['healthcare_ptr'])
178

    
179
class Status(NamedAbstractModel):
180

    
181
    class Meta:
182
        app_label = 'dossiers'
183
        verbose_name = u"Statut d'un état"
184
        verbose_name_plural = u"Statuts d'un état"
185

    
186
    type = models.CharField(max_length=80)
187
    services = models.ManyToManyField('ressources.Service')
188

    
189

    
190
class FileState(models.Model):
191

    
192
    class Meta:
193
        app_label = 'dossiers'
194
        verbose_name = u'Etat du dossier patient'
195
        verbose_name_plural = u'Etats du dossier patient'
196

    
197
    patient = models.ForeignKey('dossiers.PatientRecord',
198
        verbose_name=u'Dossier patient')
199
    status = models.ForeignKey('dossiers.Status', verbose_name=u'Statut')
200
    created = models.DateTimeField(u'Création', auto_now_add=True)
201
    date_selected = models.DateTimeField()
202
    author = \
203
        models.ForeignKey(User,
204
        verbose_name=u'Auteur')
205
    comment = models.TextField(max_length=3000, blank=True, null=True)
206
    previous_state = models.ForeignKey('FileState',
207
            on_delete=models.SET_NULL,
208
            verbose_name=u'Etat précédent',
209
            blank=True, null=True)
210

    
211
    def get_next_state(self):
212
        try:
213
            return FileState.objects.get(previous_state=self)
214
        except:
215
            return None
216

    
217
    def save(self, **kwargs):
218
        self.date_selected = \
219
                datetime(self.date_selected.year,
220
                        self.date_selected.month, self.date_selected.day)
221
        super(FileState, self).save(**kwargs)
222

    
223
    def __unicode__(self):
224
        return self.status.name + ' ' + str(self.date_selected)
225

    
226
    def delete(self, *args, **kwargs):
227
        next_state = self.get_next_state()
228
        if next_state and self.previous_state:
229
            next_state.previous_state = self.previous_state
230
            next_state.save()
231
        if self.patient.last_state == self:
232
            self.patient.last_state = self.previous_state
233
            self.patient.save()
234
        super(FileState, self).delete(*args, **kwargs)
235

    
236
class PatientAddress(models.Model):
237

    
238
    display_name = models.CharField(max_length=276,
239
            verbose_name=u'Adresse complète', editable=False)
240
    phone = PhoneNumberField(verbose_name=u"Téléphone", blank=True, null=True)
241
    fax = PhoneNumberField(verbose_name=u"Fax", blank=True, null=True)
242
    place_of_life = models.BooleanField(verbose_name=u"Lieu de vie")
243
    number = models.CharField(max_length=12,
244
            verbose_name=u"Numéro", blank=True, null=True)
245
    street = models.CharField(max_length=100,
246
            verbose_name=u"Rue", blank=True, null=True)
247
    address_complement = models.CharField(max_length=100,
248
            blank=True, null=True,
249
            verbose_name=u"Complément d'adresse")
250
    zip_code = ZipCodeField(verbose_name=u"Code postal", blank=True, null=True)
251
    city = models.CharField(max_length=60,
252
            verbose_name=u"Ville", blank=True, null=True)
253
    comment = models.TextField(verbose_name=u"Commentaire",
254
            null=True, blank=True)
255

    
256
    def __unicode__(self):
257
        return self.display_name
258

    
259
    def save(self, **kwargs):
260
        self.display_name = ''
261
        if self.number:
262
            self.display_name += self.number + ' '
263
        if self.street:
264
            self.display_name += self.street + ' '
265
        if self.address_complement:
266
            self.display_name += self.address_complement + ' '
267
        if self.zip_code:
268
            self.display_name += self.zip_code + ' '
269
        if self.city:
270
            self.display_name += self.city + ' '
271
        super(PatientAddress, self).save(**kwargs)
272

    
273

    
274
class PatientContact(People):
275
    class Meta:
276
        verbose_name = u'Contact patient'
277
        verbose_name_plural = u'Contacts patient'
278

    
279
    mobile = PhoneNumberField(verbose_name=u"Téléphone mobile", blank=True, null=True)
280
    # carte vitale
281
    social_security_id = models.CharField(max_length=13, verbose_name=u"NIR",
282
            null=True, blank=True)
283
    birthdate = models.DateField(verbose_name=u"Date de naissance",
284
            null=True, blank=True)
285
    birthplace = models.CharField(max_length=100, verbose_name=u"Lieu de naissance",
286
            null=True, blank=True)
287
    twinning_rank = models.IntegerField(verbose_name=u"Rang (gémellité)", default=1,
288
            validators=[MinValueValidator(1)])
289
    thirdparty_payer = models.BooleanField(verbose_name=u'Tiers-payant',
290
            default=False)
291
    begin_rights = models.DateField(verbose_name=u"Début de droits",
292
            null=True, blank=True)
293
    end_rights = models.DateField(verbose_name=u"Fin de droits",
294
            null=True, blank=True)
295
    health_center = models.ForeignKey('ressources.HealthCenter',
296
            verbose_name=u"Centre d'assurance maladie",
297
            null=True, blank=True)
298
    other_health_center = models.CharField(verbose_name=u"Centre spécifique",
299
            max_length=4,
300
            null=True, blank=True)
301
    type_of_contract = models.CharField(max_length=2,
302
            verbose_name=u"Type de contrat spécifique",
303
            choices=TYPE_OF_CONTRACT_CHOICES,
304
            null=True, blank=True)
305
    management_code = models.ForeignKey('ressources.ManagementCode',
306
            verbose_name=u"Code de gestion",
307
            null=True, blank=True)
308
    job = models.ForeignKey('ressources.Job',
309
            related_name="job",
310
            verbose_name=u"Profession",
311
            null=True, blank=True, default=None)
312
    parente = models.ForeignKey('ressources.PatientRelatedLink',
313
            verbose_name=u"Lien avec le patient (Parenté)",
314
            null=True, blank=True, default=None)
315
    ame = models.BooleanField(verbose_name=u"AME", default=False)
316

    
317
    addresses = models.ManyToManyField('PatientAddress', verbose_name=u"Adresses")
318
    contact_comment = models.TextField(verbose_name=u"Commentaire",
319
            null=True, blank=True)
320

    
321
    old_contact_id = models.CharField(max_length=256,
322
            verbose_name=u'Ancien ID du contact', blank=True, null=True)
323

    
324
    def get_control_key(self):
325
        if self.social_security_id:
326
            nir = self.social_security_id
327
            try:
328
                # Corse dpt 2A et 2B
329
                minus = 0
330
                if nir[6] in ('A', 'a'):
331
                    nir = [c for c in nir]
332
                    nir[6] = '0'
333
                    nir = ''.join(nir)
334
                    minus = 1000000
335
                elif nir[6] in ('B', 'b'):
336
                    nir = [c for c in nir]
337
                    nir[6] = '0'
338
                    nir = ''.join(nir)
339
                    minus = 2000000
340
                nir = int(nir) - minus
341
                return (97 - (nir % 97))
342
            except Exception, e:
343
                print str(e)
344
                return None
345
        return None
346

    
347

    
348
class PatientRecordManager(models.Manager):
349
    def for_service(self, service):
350
        return self.filter(service=service)
351

    
352
class PatientRecord(ServiceLinkedAbstractModel, PatientContact):
353
    objects = PatientRecordManager()
354

    
355
    class Meta:
356
        verbose_name = u'Dossier'
357
        verbose_name_plural = u'Dossiers'
358

    
359
    created = models.DateTimeField(u'création', auto_now_add=True)
360
    creator = \
361
        models.ForeignKey(User,
362
        verbose_name=u'Créateur dossier patient',
363
        editable=True)
364
    policyholder = models.ForeignKey('PatientContact',
365
            null=True, blank=True,
366
            verbose_name="Assuré", related_name="+",
367
            on_delete=models.SET_NULL)
368
    contacts = models.ManyToManyField('PatientContact',
369
            related_name='contact_of')
370
    nationality = models.CharField(verbose_name=u"Nationalité",
371
            max_length=70, null=True, blank=True)
372
    paper_id = models.CharField(max_length=6,
373
            verbose_name=u"N° dossier papier",
374
            null=True, blank=True)
375
    last_state = models.ForeignKey(FileState, related_name='+',
376
            null=True, on_delete=models.SET_NULL)
377
    comment = models.TextField(verbose_name=u"Commentaire",
378
            null=True, blank=True, default=None)
379
    pause = models.BooleanField(verbose_name=u"Pause facturation",
380
            default=False)
381
    pause_comment = models.TextField(verbose_name=u"Commentaire sur la pause facturation",
382
            null=True, blank=True, default=None)
383
    confidential = models.BooleanField(verbose_name=u"Confidentiel",
384
            default=False)
385
    socialisation_durations = models.ManyToManyField('ressources.SocialisationDuration',
386
            related_name='socialisation_duration_of')
387
    mdph_requests = models.ManyToManyField('ressources.MDPHRequest',
388
            related_name='mdph_requests_of')
389
    mdph_responses = models.ManyToManyField('ressources.MDPHResponse',
390
            related_name='mdph_responses_of')
391

    
392
    # Physiology and health data
393
    size = models.DecimalField(verbose_name=u"Taille (cm)", max_digits=5, decimal_places=1,
394
            null=True, blank=True, default=None)
395
    weight = models.IntegerField(verbose_name=u"Poids (g)",
396
            null=True, blank=True, default=None)
397
    pregnancy_term = models.IntegerField(verbose_name=u"Terme en semaines",
398
            null=True, blank=True, default=None)
399
    cranium_perimeter = models.DecimalField(verbose_name=u"Périmètre cranien", max_digits=5, decimal_places=2,
400
            null=True, blank=True, default=None)
401
    chest_perimeter = models.DecimalField(verbose_name=u"Périmètre thoracique", max_digits=5, decimal_places=2,
402
            null=True, blank=True, default=None)
403
    apgar_score_one = models.IntegerField(verbose_name=u"Test d'Apgar (1)",
404
            null=True, blank=True, default=None)
405
    apgar_score_two = models.IntegerField(verbose_name=u"Test d'Apgar (5)",
406
            null=True, blank=True, default=None)
407
    mises_1 = models.ManyToManyField('ressources.CodeCFTMEA', related_name="mises1",
408
            verbose_name=u"Axe I : catégories cliniques",
409
            null=True, blank=True, default=None)
410
    mises_2 = models.ManyToManyField('ressources.CodeCFTMEA', related_name="mises2",
411
            verbose_name=u"Axe II : facteurs organiques",
412
            null=True, blank=True, default=None)
413
    mises_3 = models.ManyToManyField('ressources.CodeCFTMEA', related_name="mises3",
414
            verbose_name=u"Axe II : facteurs environnementaux",
415
            null=True, blank=True, default=None)
416

    
417
    # Inscription motive
418
    analysemotive = models.ForeignKey('ressources.AnalyseMotive',
419
            verbose_name=u"Motif (analysé)",
420
            null=True, blank=True, default=None)
421
    familymotive = models.ForeignKey('ressources.FamilyMotive',
422
            verbose_name=u"Motif (famille)",
423
            null=True, blank=True, default=None)
424
    provenance = models.ForeignKey('ressources.Provenance',
425
            verbose_name=u"Conseilleur",
426
            null=True, blank=True, default=None)
427
    advicegiver = models.ForeignKey('ressources.AdviceGiver',
428
            verbose_name=u"Demandeur",
429
            null=True, blank=True, default=None)
430

    
431
    # Out motive
432
    outmotive = models.ForeignKey('ressources.OutMotive',
433
            verbose_name=u"Motif de sortie",
434
            null=True, blank=True, default=None)
435
    outto = models.ForeignKey('ressources.OutTo',
436
            verbose_name=u"Orientation",
437
            null=True, blank=True, default=None)
438

    
439
    # Family
440
    sibship_place = models.IntegerField(verbose_name=u"Place dans la fratrie",
441
            null=True, blank=True, default=None)
442
    nb_children_family = models.IntegerField(verbose_name=u"Nombre d'enfants dans la fratrie",
443
            null=True, blank=True, default=None)
444
    parental_authority = models.ForeignKey('ressources.ParentalAuthorityType',
445
            verbose_name=u"Autorité parentale",
446
            null=True, blank=True, default=None)
447
    family_situation = models.ForeignKey('ressources.FamilySituationType',
448
            verbose_name=u"Situation familiale",
449
            null=True, blank=True, default=None)
450
    child_custody = models.ForeignKey('ressources.ParentalCustodyType',
451
            verbose_name=u"Garde parentale",
452
            null=True, blank=True, default=None)
453
    job_mother = models.ForeignKey('ressources.Job',
454
            related_name="job_mother",
455
            verbose_name=u"Profession de la mère",
456
            null=True, blank=True, default=None)
457
    job_father = models.ForeignKey('ressources.Job',
458
            related_name="job_father",
459
            verbose_name=u"Profession du père",
460
            null=True, blank=True, default=None)
461
    rm_mother = models.ForeignKey('ressources.MaritalStatusType',
462
            related_name="rm_mother",
463
            verbose_name=u"Régime matrimonial de la mère",
464
            null=True, blank=True, default=None)
465
    rm_father = models.ForeignKey('ressources.MaritalStatusType',
466
            related_name="rm_father",
467
            verbose_name=u"Régime matrimonial du père",
468
            null=True, blank=True, default=None)
469
    family_comment = models.TextField(verbose_name=u"Commentaire",
470
            null=True, blank=True, default=None)
471

    
472
    # Transport
473
    transporttype = models.ForeignKey('ressources.TransportType',
474
            verbose_name=u"Type de transport",
475
            null=True, blank=True, default=None)
476
    transportcompany = models.ForeignKey('ressources.TransportCompany',
477
            verbose_name=u"Compagnie de transport",
478
            null=True, blank=True, default=None)
479

    
480
    # FollowUp
481
    coordinators = models.ManyToManyField('personnes.Worker',
482
            verbose_name=u"Coordinateurs",
483
            null=True, blank=True, default=None)
484
    externaldoctor = models.ForeignKey('personnes.ExternalTherapist',
485
            verbose_name=u"Médecin extérieur",
486
            null=True, blank=True, default=None)
487
    externalintervener = models.ForeignKey('personnes.ExternalWorker',
488
            verbose_name=u"Intervenant extérieur",
489
            null=True, blank=True, default=None)
490

    
491
    old_id = models.CharField(max_length=256,
492
            verbose_name=u'Ancien ID', blank=True, null=True)
493
    old_old_id = models.CharField(max_length=256,
494
            verbose_name=u'Ancien ancien ID', blank=True, null=True)
495

    
496
    def save(self, *args, **kwargs):
497
        if not getattr(self, 'service', None):
498
            raise Exception('The field service is mandatory.')
499
        super(PatientRecord, self).save(*args, **kwargs)
500

    
501
    def get_state(self):
502
        return self.last_state
503

    
504
    def get_initial_state(self):
505
        return self.filestate_set.order_by('date_selected')[0]
506

    
507
    def get_current_state(self):
508
        today = date.today()
509
        return self.get_state_at_day(today)
510

    
511
    def get_state_at_day(self, date):
512
        state = self.get_state()
513
        while(state):
514
            if datetime(state.date_selected.year,
515
                    state.date_selected.month, state.date_selected.day) <= \
516
                    datetime(date.year, date.month, date.day):
517
                return state
518
            state = state.previous_state
519
        return self.get_state()
520

    
521
    def was_in_state_at_day(self, date, status_type):
522
        state_at_day = self.get_state_at_day(date)
523
        if state_at_day and state_at_day.status.type == status_type:
524
            return True
525
        return False
526

    
527
    def get_states_history(self):
528
        return self.filestate_set.order_by('date_selected')
529

    
530
    def get_states_history_with_duration(self):
531
        '''
532
        Return the state history with for each state its duration.
533
        If the last state is in the past, the duration is counted until today.
534
        If the last state is in the future, the duration is not set.
535
        '''
536
        history = self.get_states_history()
537
        history_with_duration = list()
538
        today = datetime.today()
539
        i = 0
540
        for state in history:
541
            history_with_duration.append([state, None])
542
            if i != 0:
543
                history_with_duration[i-1][1] = state.date_selected - history_with_duration[i-1][0].date_selected
544
            if i == len(history)-1 and state.date_selected <= today:
545
                history_with_duration[i][1] = today - history_with_duration[i][0].date_selected
546
            i += 1
547
        return history_with_duration
548

    
549
    def can_be_deleted(self):
550
        for act in self.act_set.all():
551
            if act.is_state('VALIDE'):
552
                return False
553
        return True
554

    
555
    def delete(self, *args, **kwargs):
556
        if self.can_be_deleted():
557
            super(PatientRecord, self).delete(*args, **kwargs)
558

    
559
    def get_ondisk_directory(self, service):
560
        if not settings.PATIENT_FILES_BASE_DIRECTORY:
561
            return None
562

    
563
        dirnames = []
564
        dirname = self.last_name.upper()
565
        dirnames.append(dirname)
566
        if self.first_name:
567
            dirname = '%s %s' % (dirname, self.first_name)
568
            dirnames.append(dirname)
569
        if self.paper_id:
570
            dirname = '%s %s' % (dirname, self.paper_id)
571
            dirnames.append(dirname)
572

    
573
        for i, dirname in enumerate(dirnames):
574
            fullpath = os.path.join(settings.PATIENT_FILES_BASE_DIRECTORY, service, dirname)
575
            try:
576
                next_fullpath = os.path.join(settings.PATIENT_FILES_BASE_DIRECTORY, service, dirnames[i+1])
577
            except IndexError:
578
                pass
579
            else:
580
                 if os.path.exists(fullpath) and not os.path.exists(next_fullpath):
581
                     os.rename(fullpath, next_fullpath)
582
                 continue
583
            if not os.path.exists(fullpath):
584
                os.makedirs(fullpath)
585
            for subdir in settings.PATIENT_SUBDIRECTORIES:
586
                subdir_fullpath = os.path.join(fullpath, subdir)
587
                if not os.path.exists(subdir_fullpath):
588
                    os.makedirs(subdir_fullpath)
589
        return fullpath
590

    
591
    def get_client_side_directory(self, service):
592
        directory = self.get_ondisk_directory(service)
593
        if not directory:
594
            return None
595
        if not settings.CLIENT_SIDE_PATIENT_FILES_BASE_DIRECTORY:
596
            return None
597
        return os.path.join(settings.CLIENT_SIDE_PATIENT_FILES_BASE_DIRECTORY,
598
                            directory[len(settings.PATIENT_FILES_BASE_DIRECTORY)+1:])
599

    
600
    def set_state(self, status, author, date_selected=None, comment=None):
601
        if not author:
602
            raise Exception('Missing author to set state')
603
        if not date_selected:
604
            date_selected = datetime.now()
605
        current_state = self.get_state()
606
        if not current_state:
607
            raise Exception('Invalid patient record. '
608
                'Missing current state.')
609
        if isinstance(date_selected, date):
610
            date_selected = datetime(year=date_selected.year,
611
                month=date_selected.month, day=date_selected.day)
612
        if date_selected < current_state.date_selected:
613
            raise Exception('You cannot set a state starting the %s that '
614
                'is before the previous state starting at day %s.' % \
615
                (str(date_selected), str(current_state.date_selected)))
616
        filestate = FileState.objects.create(patient=self, status=status,
617
            date_selected=date_selected, author=author, comment=comment,
618
            previous_state=current_state)
619
        self.last_state = filestate
620
        self.save()
621

    
622
    def change_day_selected_of_state(self, state, new_date):
623
        if state.previous_state:
624
            if new_date < state.previous_state.date_selected:
625
                raise Exception('You cannot set a state starting the %s '
626
                    'before the previous state starting at day %s.' % \
627
                    (str(new_date), str(state.previous_state.date_selected)))
628
        next_state = state.get_next_state()
629
        if next_state:
630
            if new_date > next_state.date_selected:
631
                raise Exception('You cannot set a state starting the %s '
632
                    'after the following state starting at day %s.' % \
633
                    (str(new_date), str(next_state.date_selected)))
634
        state.date_selected = new_date
635
        state.save()
636

    
637
    def remove_state(self, state):
638
        if state.patient.id != self.id:
639
            raise Exception('The state given is not about this patient '
640
                'record but about %s' % state.patient)
641
        next_state = state.get_next_state()
642
        if not next_state:
643
            self.remove_last_state()
644
        else:
645
            next_state.previous_state = state.previous_state
646
            next_state.save()
647
            state.delete()
648

    
649
    def remove_last_state(self):
650
        try:
651
            self.get_state().delete()
652
        except:
653
            pass
654

    
655
    # START Specific to sessad healthcare
656
    def get_last_notification(self):
657
        return SessadHealthCareNotification.objects.filter(patient=self, ).\
658
            latest('end_date')
659

    
660
    def days_before_notification_expiration(self):
661
        today = datetime.today()
662
        notification = self.get_last_notification(self)
663
        if not notification:
664
            return 0
665
        if notification.end_date < today:
666
            return 0
667
        else:
668
            return notification.end_date - today
669
    # END Specific to sessad healthcare
670

    
671
    # START Specific to cmpp healthcare
672
    def create_diag_healthcare(self, modifier):
673
        """
674
            Gestion de l'inscription automatique.
675

    
676
            Si un premier acte est validé alors une prise en charge
677
            diagnostique est ajoutée. Cela fera basculer le dossier dans l'état
678
            en diagnostic.
679

    
680
            A voir si auto ou manuel :
681
            Si ce n'est pas le premier acte validé mais que l'acte précédement
682
            facturé a plus d'un an, on peut créer une prise en charge
683
            diagnostique. Même s'il y a une prise en charge de traitement
684
            expirée depuis moins d'un an donc renouvelable.
685

    
686
        """
687
        acts = Act.objects.filter(validation_locked=False,
688
            patient__service=self.service)
689
        days_not_locked = sorted(set(acts.values_list('date', flat=True)))
690
        acts = self.act_set.filter(validation_locked=True,
691
            valide=True, is_lost=False, is_billed=False)
692
        acts = acts.exclude(date__in=days_not_locked)
693
        acts = acts.order_by('date')
694
        pause_query = Q(pause=True)
695
        billable_query = Q(act_type__billable=True, switch_billable=False) | \
696
                Q(act_type__billable=False, switch_billable=True)
697
        billable_acts = acts.filter(~pause_query & billable_query)
698

    
699
        if not CmppHealthCareDiagnostic.objects.filter(patient=self).exists() \
700
                and billable_acts:
701
            # Pas de prise en charge, on recherche l'acte facturable le plus
702
            # ancien, on crée une pc diag à la même date.
703
            CmppHealthCareDiagnostic(patient=self, author=modifier,
704
                start_date=billable_acts[0].date).save()
705
        else:
706
            # On recherche l'acte facturable non facturé le plus ancien après
707
            # le dernier acte facturé et on regarde s'il a plus d'un an
708
            try:
709
                last_billed_act = self.act_set.filter(is_billed=True).\
710
                    latest('date')
711
                if last_billed_act and billable_acts:
712
                    billable_acts = billable_acts.filter(date__gte=last_billed_act.date)
713
                    if billable_acts and (billable_acts[0].date - last_billed_act.date).days >= 365:
714
                        return True
715
                    return False
716
            except:
717
                pass
718
        return False
719

    
720
    def automated_switch_state(self, modifier):
721
        def state_switcher(diag, act):
722
            if diag and (self.last_state.status.type == "ACCUEIL" or
723
                    self.last_state.status.type == "TRAITEMENT"):
724
                status = Status.objects.get(type="DIAGNOSTIC",
725
                    services__name='CMPP')
726
                try:
727
                    self.set_state(status, modifier, date_selected=act.date)
728
                except:
729
                    pass
730
            elif not diag and (self.last_state.status.type == "ACCUEIL" or
731
                    self.last_state.status.type == "DIAGNOSTIC"):
732
                status = Status.objects.get(type="TRAITEMENT",
733
                    services__name='CMPP')
734
                try:
735
                    self.set_state(status, modifier, date_selected=act.date)
736
                except:
737
                    pass
738
        # Only for CMPP and open files
739
        if not self.service.name == 'CMPP' or \
740
                self.last_state.status.type == "CLOS":
741
            return
742
        # Nothing to do if no act after the last state date
743
        last_acts = self.act_set.filter(date__gt=self.last_state.date_selected)
744
        if not last_acts:
745
            return
746
        # If the last act is billed, look at the healthcare type
747
        last_act = last_acts.latest('date')
748
        if last_act.is_billed:
749
            if not last_act.healthcare:
750
                # Billed but no healthcare, coming from imported billed acts
751
                return
752
            diag = False
753
            if hasattr(last_act.healthcare, 'cmpphealthcarediagnostic'):
754
                diag = True
755
            return state_switcher(diag, last_act)
756
        # Last act not billed, let's look if it is billable
757
        from calebasse.facturation import list_acts
758
        (acts_not_locked, days_not_locked, acts_not_valide,
759
        acts_not_billable, acts_pause, acts_per_hc, acts_losts) = \
760
            list_acts.list_acts_for_billing_CMPP_per_patient(self,
761
                datetime.today(), self.service)
762
        last_hc = None
763
        last_act_hc = None
764
        for hc, acts in acts_per_hc.iteritems():
765
            if len(acts) and (not last_act_hc or
766
                    acts[-1].date > last_act_hc.date):
767
                last_hc = hc
768
                last_act_hc = acts[-1]
769
        # There is a billable act after the last state so either it is diag
770
        # or it is treament
771
        if last_act_hc and last_hc and \
772
                last_act_hc.date > self.last_state.date_selected.date():
773
            if hasattr(last_hc, 'cmpphealthcarediagnostic'):
774
                state_switcher(True, last_act_hc)
775
            else:
776
                state_switcher(False, last_act_hc)
777

    
778
    def get_healthcare_status(self):
779
        today = date.today()
780
        current_hc_trait = None
781
        try:
782
            current_hc_trait = CmppHealthCareTreatment.objects.filter(patient=self,start_date__lte=today, end_date__gte=today).latest('start_date')
783
        except:
784
            pass
785
        if not current_hc_trait:
786
            current_hc_diag = None
787
            try:
788
                current_hc_diag = CmppHealthCareDiagnostic.objects.filter(patient=self, start_date__lte=today).latest('start_date')
789
            except:
790
                pass
791
            if current_hc_diag and current_hc_diag.get_act_number() > len(current_hc_diag.act_set.all()):
792

    
793
                #Plus simple et a changer dans la facturation, s'il y une pc de traitemant avec une start date > a la pc diag alors cette pc de diag est finie
794
                # Non parce que je peux ajouter une pc de traitement alors que je veux encore facturer sur la diag precedente.
795
                # Donc si j'ai un acte facturer en traitement alors la diag ne fonctionne plus.
796
                # Dans le fonctionnement normal, si j'ai encore une diag dispo et que je veux fact duirectement en trait, je reduit le nombre d'acte pris en charge.
797
                lasts_billed = Act.objects.filter(patient=self, is_billed = True, healthcare__isnull=False).order_by('-date')
798
                last_hc_date = None
799
                if lasts_billed:
800
                    last_hc_date = lasts_billed[0].healthcare.start_date
801
                if not last_hc_date or last_hc_date <= current_hc_diag.start_date:
802
                    # Prise en charge disponible
803
                    return (0, len(current_hc_diag.act_set.all()), current_hc_diag.get_act_number())
804
            last_hc_trait = None
805
            try:
806
                last_hc_trait = CmppHealthCareTreatment.objects.filter(patient=self).latest('start_date')
807
            except:
808
                pass
809
            if not last_hc_trait:
810
                if not current_hc_diag:
811
                    # Aucune PC
812
                    return (1, None)
813
                else:
814
                    # PC diag full, demander PC trait
815
                    return (2, current_hc_diag.get_act_number())
816
            if last_hc_trait.end_date < today:
817
                # Expirée
818
                #Test if rediagable
819
                return (3, last_hc_trait.end_date)
820
            if last_hc_trait.start_date > today:
821
                # N'a pas encore pris effet
822
                return (4, last_hc_trait.start_date)
823
            return (-1,)
824
        if current_hc_trait.get_act_number() > len(current_hc_trait.act_set.all()):
825
            # Pris en charge disponible
826
            return (5, len(current_hc_trait.act_set.all()), current_hc_trait.get_act_number())
827
        # Prise en charge au quota
828
        if not current_hc_trait.is_extended():
829
            # Peut être prolongée
830
            return (6, current_hc_trait.get_act_number())
831
        # Prise en charge saturée
832
        return (7, current_hc_trait.get_act_number(), current_hc_trait.end_date)
833
    # END Specific to cmpp healthcare
834

    
835

    
836
    @property
837
    def entry_date(self):
838
        d = self.filestate_set.filter(
839
                Q(status__type='DIAGNOSTIC') |
840
                Q(status__type='TRAITEMENT') |
841
                Q(status__type='SUIVI')). \
842
                        aggregate(Min('date_selected'))['date_selected__min']
843
        return d and d.date()
844

    
845

    
846
    @property
847
    def exit_date(self):
848
        if self.last_state.status.type != 'CLOS':
849
            return None
850
        d = self.filestate_set.filter(status__type='CLOS'). \
851
                    aggregate(Max('date_selected'))['date_selected__max']
852
        return d and d.date()
853

    
854
    @property
855
    def care_duration(self):
856
        # Duration between the first act present and the closing date.
857
        # If no closing date, end_date is the date of tha last act
858
        first_act_date = None
859
        try:
860
            first_act_date = self.act_set.filter(valide=True).order_by('date')[0].date
861
        except:
862
            return 0
863
        exit_date = self.exit_date
864
        if not exit_date:
865
            exit_date = self.act_set.filter(valide=True).order_by('-date')[0].date
866
        return (exit_date - first_act_date).days
867

    
868
    @property
869
    def care_duration_since_last_contact_or_first_act(self):
870
        # Duration between the first act present and the closing date.
871
        # If no closing date, end_date is the date of tha last act
872
        contacts = FileState.objects.filter(patient=self, status__type='ACCUEIL').order_by('date_selected')
873
        last_contact = None
874
        first_act_after_last_contact = None
875
        if len(contacts) == 1:
876
            last_contact = contacts[0]
877
        elif len(contacts) > 1:
878
            last_contact = contacts[len(contacts)-1]
879
        if last_contact:
880
            # inscription act
881
            first_acts_after_last_contact = Act.objects.filter(patient=self, date__gte=last_contact.date_selected, valide=True).order_by('date')
882
            if first_acts_after_last_contact:
883
                first_act_after_last_contact = first_acts_after_last_contact[0]
884
        if not contacts:
885
            return self.care_duration
886
        if not first_act_after_last_contact:
887
            return 0
888
        exit_date = self.exit_date
889
        if not exit_date or exit_date < first_act_after_last_contact.date:
890
            exit_date = self.act_set.filter(valide=True).order_by('-date')[0].date
891
        return (exit_date - first_act_after_last_contact.date).days
892

    
893
reversion.register(PatientRecord, follow=['people_ptr'])
894

    
895

    
896
def create_patient(first_name, last_name, service, creator,
897
        date_selected=None):
898
    logger.debug('create_patient: creation for patient %s %s in service %s '
899
        'by %s' % (first_name, last_name, service, creator))
900
    if not (first_name and last_name and service and creator):
901
        raise Exception('Missing parameter to create a patient record.')
902
    status = Status.objects.filter(type="ACCUEIL").filter(services=service)
903
    if not status:
904
        raise Exception('%s has no ACCEUIL status' % service.name)
905
    patient = PatientRecord.objects.create(first_name=first_name,
906
            last_name=last_name, service=service,
907
            creator=creator)
908
    fs = FileState(status=status[0], author=creator, previous_state=None)
909
    if not date_selected:
910
        date_selected = patient.created
911
    fs.patient = patient
912
    fs.date_selected = date_selected
913
    fs.save()
914
    patient.last_state = fs
915
    patient.save()
916
    patient.policyholder = patient.patientcontact
917
    patient.save()
918
    return patient
(5-5/11)