Project

General

Profile

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

calebasse / calebasse / dossiers / models.py @ 61405b31

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

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

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

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

    
346

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

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

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

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

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

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

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

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

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

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

    
488
    old_id = models.CharField(max_length=256,
489
            verbose_name=u'Ancien ID', blank=True, null=True)
490
    old_old_id = models.CharField(max_length=256,
491
            verbose_name=u'Ancien ancien ID', blank=True, null=True)
492

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

    
498
    def get_state(self):
499
        return self.last_state
500

    
501
    def get_initial_state(self):
502
        return self.filestate_set.order_by('date_selected')[0]
503

    
504
    def get_current_state(self):
505
        today = date.today()
506
        return self.get_state_at_day(today)
507

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

    
518
    def was_in_state_at_day(self, date, status_type):
519
        state_at_day = self.get_state_at_day(date)
520
        if state_at_day and state_at_day.status.type == status_type:
521
            return True
522
        return False
523

    
524
    def get_states_history(self):
525
        return self.filestate_set.order_by('date_selected')
526

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

    
546
    def can_be_deleted(self):
547
        for act in self.act_set.all():
548
            if act.is_state('VALIDE'):
549
                return False
550
        return True
551

    
552
    def delete(self, *args, **kwargs):
553
        if self.can_be_deleted():
554
            super(PatientRecord, self).delete(*args, **kwargs)
555

    
556
    def get_ondisk_directory(self, service):
557
        if not settings.PATIENT_FILES_BASE_DIRECTORY:
558
            return None
559

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

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

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

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

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

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

    
646
    def remove_last_state(self):
647
        try:
648
            self.get_state().delete()
649
        except:
650
            pass
651

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

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

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

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

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

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

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

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

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

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

    
832

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

    
842

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

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

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

    
890
reversion.register(PatientRecord, follow=['people_ptr'])
891

    
892

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