Project

General

Profile

Download (42.4 KB) Statistics
| Branch: | Tag: | Revision:
96f613c6 Benjamin Dauvergne
# -*- coding: utf-8 -*-

7bdb9ebf Mikaël Ates
import logging
68d5a646 Frédéric Péters
import os
7bdb9ebf Mikaël Ates
3665f853 Mikaël Ates
from datetime import datetime, date
fb4195eb Mikaël Ates
from dateutil.relativedelta import relativedelta
005f1264 Mikaël Ates
from cPickle import loads, dumps
70b65c8c Mikaël Ates
68d5a646 Frédéric Péters
from django.conf import settings
1097fd0a Benjamin Dauvergne
from django.db import models
5539237c Benjamin Dauvergne
from django.db.models import Min, Max, Q
6163f2ff Mikaël Ates
from django.contrib.auth.models import User
39ddd165 Jérôme Schneider
from django.core.validators import MinValueValidator
1097fd0a Benjamin Dauvergne
366eb65a Benjamin Dauvergne
import reversion

3c5df84d Jérôme Schneider
from calebasse.choices import TYPE_OF_CONTRACT_CHOICES, DEFICIENCY_CHOICES
3cab63be Jérôme Schneider
from calebasse.models import PhoneNumberField, ZipCodeField
96f613c6 Benjamin Dauvergne
from calebasse.personnes.models import People
7948e4a4 Jérôme Schneider
from calebasse.ressources.models import (ServiceLinkedAbstractModel,
3c5df84d Jérôme Schneider
NamedAbstractModel)
81916a5f Mikaël Ates
from calebasse.actes.models import Act
1097fd0a Benjamin Dauvergne
d489bf41 Mikaël Ates
DEFAULT_ACT_NUMBER_DIAGNOSTIC = 6
DEFAULT_ACT_NUMBER_TREATMENT = 30
DEFAULT_ACT_NUMBER_PROLONGATION = 10
fb4195eb Mikaël Ates
VALIDITY_PERIOD_TREATMENT_HEALTHCARE_DAYS = 0
VALIDITY_PERIOD_TREATMENT_HEALTHCARE_MONTHS = 0
VALIDITY_PERIOD_TREATMENT_HEALTHCARE_YEARS = 1
d489bf41 Mikaël Ates
7bdb9ebf Mikaël Ates
logger = logging.getLogger('calebasse.dossiers')


005f1264 Mikaël Ates
class TransportPrescriptionLog(models.Model):
patient = models.ForeignKey('dossiers.PatientRecord',
verbose_name=u'Dossier patient')
created = models.DateTimeField(u'Création', auto_now_add=True)
choices = models.CharField(max_length = 4096, null=True, blank=True)

def get_choices(self):
if not self.choices:
return dict()
6fd15526 Serghei MIHAI
return loads(str(self.choices), protocol = 1)
005f1264 Mikaël Ates
def set_choices(self, choices=None):
if choices and isinstance(choices, dict):
6fd15526 Serghei MIHAI
self.choices = dumps(choices, protocol = 1)
005f1264 Mikaël Ates

d489bf41 Mikaël Ates
class HealthCare(models.Model):

class Meta:
app_label = 'dossiers'

2da0976b Jérôme Schneider
start_date = models.DateField(verbose_name=u"Date de début")
15f8dbaa Mikaël Ates
request_date = models.DateField(verbose_name=u"Date de demande",
d72af982 Mikaël Ates
blank=True, null=True)
15f8dbaa Mikaël Ates
agree_date = models.DateField(verbose_name=u"Date d'accord",
d72af982 Mikaël Ates
blank=True, null=True)
insist_date = models.DateField(verbose_name=u"Date de relance",
blank=True, null=True)
d489bf41 Mikaël Ates
patient = models.ForeignKey('dossiers.PatientRecord',
2da0976b Jérôme Schneider
verbose_name=u'Dossier patient')
d489bf41 Mikaël Ates
created = models.DateTimeField(u'Création', auto_now_add=True)
author = \
models.ForeignKey(User,
d72af982 Mikaël Ates
verbose_name=u'Auteur', blank=True, null=True)
b464657d Mikaël Ates
comment = models.TextField(max_length=3000, blank=True, null=True, verbose_name=u"Commentaire")
d489bf41 Mikaël Ates
c66c6fcd Mikaël Ates
def get_nb_acts_cared(self):
return len(self.act_set.all())

d489bf41 Mikaël Ates
class CmppHealthCareDiagnostic(HealthCare):

class Meta:
app_label = 'dossiers'

72219903 Mikaël Ates
act_number = models.IntegerField(default=DEFAULT_ACT_NUMBER_DIAGNOSTIC, verbose_name=u"Nombre d'actes couverts")
3a72b7ac Mikaël Ates
end_date = models.DateField(verbose_name=u"Date de fin",
blank=True, null=True)
d489bf41 Mikaël Ates
def get_act_number(self):
72219903 Mikaël Ates
return self.act_number
d489bf41 Mikaël Ates
71a9768c Mikaël Ates
def set_act_number(self, value):
if value < self.get_nb_acts_cared():
raise Exception("La valeur doit être supérieur au "
"nombre d'actes déjà pris en charge")
72219903 Mikaël Ates
self.act_number = value
71a9768c Mikaël Ates
self.save()

d489bf41 Mikaël Ates
def save(self, **kwargs):
self.start_date = \
datetime(self.start_date.year, self.start_date.month,
self.start_date.day)
super(CmppHealthCareDiagnostic, self).save(**kwargs)


class CmppHealthCareTreatment(HealthCare):

class Meta:
app_label = 'dossiers'

72219903 Mikaël Ates
act_number = models.IntegerField(default=DEFAULT_ACT_NUMBER_TREATMENT,
2da0976b Jérôme Schneider
verbose_name=u"Nombre d'actes couverts")
44a0fb10 Mikaël Ates
end_date = models.DateField(verbose_name=u"Date de fin",
blank=True, null=True)
d489bf41 Mikaël Ates
prolongation = models.IntegerField(default=0,
verbose_name=u'Prolongation')
61405b31 Mikaël Ates
prolongation_date = models.DateField(verbose_name=u"Date de prolongation",
blank=True, null=True)
d489bf41 Mikaël Ates
def get_act_number(self):
if self.is_extended():
72219903 Mikaël Ates
return self.act_number + self.prolongation
return self.act_number
d489bf41 Mikaël Ates
71a9768c Mikaël Ates
def set_act_number(self, value):
if value < self.get_nb_acts_cared():
raise Exception("La valeur doit être supérieur au "
"nombre d'actes déjà pris en charge")
72219903 Mikaël Ates
self.act_number = value
71a9768c Mikaël Ates
self.save()

d489bf41 Mikaël Ates
def is_extended(self):
if self.prolongation > 0:
return True
return False

def add_prolongation(self, value=None):
if not value:
value = DEFAULT_ACT_NUMBER_PROLONGATION
if self.is_extended():
raise Exception(u'Prise en charge déja prolongée')
self.prolongation = value
self.save()

81916a5f Mikaël Ates
def del_prolongation(self):
pass

d489bf41 Mikaël Ates
def save(self, **kwargs):
self.start_date = \
datetime(self.start_date.year, self.start_date.month,
self.start_date.day)
44a0fb10 Mikaël Ates
if not self.end_date:
self.end_date = self.start_date + \
relativedelta(years=VALIDITY_PERIOD_TREATMENT_HEALTHCARE_YEARS) + \
relativedelta(months=VALIDITY_PERIOD_TREATMENT_HEALTHCARE_MONTHS) + \
relativedelta(days=VALIDITY_PERIOD_TREATMENT_HEALTHCARE_DAYS-1)
d489bf41 Mikaël Ates
super(CmppHealthCareTreatment, self).save(**kwargs)


class SessadHealthCareNotification(HealthCare):

class Meta:
app_label = 'dossiers'

27bc0d25 Mikaël Ates
end_date = models.DateField(verbose_name=u"Date de fin",
blank=True, null=True)
d489bf41 Mikaël Ates
def save(self, **kwargs):
self.start_date = \
datetime(self.start_date.year, self.start_date.month,
self.start_date.day)
27bc0d25 Mikaël Ates
if self.end_date:
self.end_date = \
datetime(self.end_date.year, self.end_date.month,
self.end_date.day)
d489bf41 Mikaël Ates
super(SessadHealthCareNotification, self).save(**kwargs)

366eb65a Benjamin Dauvergne
reversion.register(CmppHealthCareDiagnostic, follow=['healthcare_ptr'])
reversion.register(CmppHealthCareTreatment, follow=['healthcare_ptr'])
reversion.register(SessadHealthCareNotification, follow=['healthcare_ptr'])
d489bf41 Mikaël Ates
0f2849e4 Mikaël Ates
class ProtectionStatus(NamedAbstractModel):

class Meta:
app_label = 'dossiers'
verbose_name = u"Statut d'une mesure de protection"
verbose_name_plural = u"Statuts d'une mesure de protection"

class ProtectionState(models.Model):

class Meta:
app_label = 'dossiers'
verbose_name = u'Mesure de protection du dossier patient'
verbose_name_plural = u'Mesure de protections du dossier patient'
ordering = ['-start_date']

patient = models.ForeignKey('dossiers.PatientRecord',
verbose_name=u'Dossier patient')
status = models.ForeignKey('dossiers.ProtectionStatus', verbose_name=u'Statut de protection')
created = models.DateTimeField(u'Création', auto_now_add=True)
start_date = models.DateTimeField()
end_date = models.DateTimeField(blank=True, null=True)
comment = models.TextField(max_length=3000, blank=True, null=True)

def __unicode__(self):
return self.status.name + ' ' + str(self.start_date)

7328a577 Jérôme Schneider
class Status(NamedAbstractModel):

class Meta:
app_label = 'dossiers'
d994547b Jérôme Schneider
verbose_name = u"Statut d'un état"
verbose_name_plural = u"Statuts d'un état"
7328a577 Jérôme Schneider
type = models.CharField(max_length=80)
services = models.ManyToManyField('ressources.Service')

2773b4d4 Jérôme Schneider
2a928c61 Mikaël Ates
class FileState(models.Model):

class Meta:
app_label = 'dossiers'
7328a577 Jérôme Schneider
verbose_name = u'Etat du dossier patient'
verbose_name_plural = u'Etats du dossier patient'
2a928c61 Mikaël Ates
patient = models.ForeignKey('dossiers.PatientRecord',
536bab34 Mikaël Ates
verbose_name=u'Dossier patient')
4a0ff5ea Frédéric Péters
status = models.ForeignKey('dossiers.Status', verbose_name=u'Statut')
2a928c61 Mikaël Ates
created = models.DateTimeField(u'Création', auto_now_add=True)
date_selected = models.DateTimeField()
author = \
models.ForeignKey(User,
536bab34 Mikaël Ates
verbose_name=u'Auteur')
2a928c61 Mikaël Ates
comment = models.TextField(max_length=3000, blank=True, null=True)
previous_state = models.ForeignKey('FileState',
9335a096 Jérôme Schneider
on_delete=models.SET_NULL,
verbose_name=u'Etat précédent',
blank=True, null=True)
2a928c61 Mikaël Ates
def get_next_state(self):
try:
return FileState.objects.get(previous_state=self)
except:
return None

def save(self, **kwargs):
f3407c83 Jérôme Schneider
self.date_selected = \
datetime(self.date_selected.year,
self.date_selected.month, self.date_selected.day)
2a928c61 Mikaël Ates
super(FileState, self).save(**kwargs)

def __unicode__(self):
2d4b9891 Jérôme Schneider
return self.status.name + ' ' + str(self.date_selected)
2a928c61 Mikaël Ates
4a0ff5ea Frédéric Péters
def delete(self, *args, **kwargs):
next_state = self.get_next_state()
if next_state and self.previous_state:
next_state.previous_state = self.previous_state
next_state.save()
if self.patient.last_state == self:
self.patient.last_state = self.previous_state
self.patient.save()
super(FileState, self).delete(*args, **kwargs)

3cab63be Jérôme Schneider
class PatientAddress(models.Model):

29168b44 Jérôme Schneider
display_name = models.CharField(max_length=276,
verbose_name=u'Adresse complète', editable=False)
2d4b9891 Jérôme Schneider
phone = PhoneNumberField(verbose_name=u"Téléphone", blank=True, null=True)
fax = PhoneNumberField(verbose_name=u"Fax", blank=True, null=True)
29168b44 Jérôme Schneider
place_of_life = models.BooleanField(verbose_name=u"Lieu de vie")
number = models.CharField(max_length=12,
verbose_name=u"Numéro", blank=True, null=True)
61491834 Jérôme Schneider
recipient = models.CharField(max_length=100,
verbose_name=u"Destinataire", blank=True, null=True)
29168b44 Jérôme Schneider
street = models.CharField(max_length=100,
49e4c841 Mikaël Ates
verbose_name=u"Rue", blank=True, null=True)
29168b44 Jérôme Schneider
address_complement = models.CharField(max_length=100,
blank=True, null=True,
d0353961 Mikaël Ates
verbose_name=u"Complément d'adresse")
49e4c841 Mikaël Ates
zip_code = ZipCodeField(verbose_name=u"Code postal", blank=True, null=True)
29168b44 Jérôme Schneider
city = models.CharField(max_length=60,
49e4c841 Mikaël Ates
verbose_name=u"Ville", blank=True, null=True)
317bcb4c Jérôme Schneider
comment = models.TextField(verbose_name=u"Commentaire",
null=True, blank=True)
3cab63be Jérôme Schneider
29168b44 Jérôme Schneider
def __unicode__(self):
724b950f Mikaël Ates
return self.display_name or u"Non renseigné"
29168b44 Jérôme Schneider
def save(self, **kwargs):
1b310da1 Mikaël Ates
self.display_name = ''
61491834 Jérôme Schneider
if self.recipient:
self.display_name += self.recipient + ' '
1b310da1 Mikaël Ates
if self.number:
self.display_name += self.number + ' '
if self.street:
self.display_name += self.street + ' '
if self.address_complement:
self.display_name += self.address_complement + ' '
if self.zip_code:
self.display_name += self.zip_code + ' '
if self.city:
self.display_name += self.city + ' '
29168b44 Jérôme Schneider
super(PatientAddress, self).save(**kwargs)

3cab63be Jérôme Schneider
class PatientContact(People):
class Meta:
verbose_name = u'Contact patient'
verbose_name_plural = u'Contacts patient'

2d4b9891 Jérôme Schneider
mobile = PhoneNumberField(verbose_name=u"Téléphone mobile", blank=True, null=True)
a8dd5599 Jérôme Schneider
# carte vitale
bb57a8fe Mikaël Ates
social_security_id = models.CharField(max_length=13, verbose_name=u"NIR",
2237043c Mikaël Ates
null=True, blank=True)
a8dd5599 Jérôme Schneider
birthdate = models.DateField(verbose_name=u"Date de naissance",
null=True, blank=True)
afc55bb9 Mikaël Ates
birthplace = models.CharField(max_length=100, verbose_name=u"Lieu de naissance",
28ff59f7 Mikaël Ates
null=True, blank=True)
39ddd165 Jérôme Schneider
twinning_rank = models.IntegerField(verbose_name=u"Rang (gémellité)", default=1,
validators=[MinValueValidator(1)])
a8dd5599 Jérôme Schneider
thirdparty_payer = models.BooleanField(verbose_name=u'Tiers-payant',
default=False)
begin_rights = models.DateField(verbose_name=u"Début de droits",
null=True, blank=True)
end_rights = models.DateField(verbose_name=u"Fin de droits",
null=True, blank=True)
2237043c Mikaël Ates
health_center = models.ForeignKey('ressources.HealthCenter',
0f4c0029 Jérôme Schneider
verbose_name=u"Centre d'assurance maladie",
a8dd5599 Jérôme Schneider
null=True, blank=True)
e949a412 Mikaël Ates
other_health_center = models.CharField(verbose_name=u"Centre spécifique",
max_length=4,
null=True, blank=True)
158b10a6 Thomas NOEL
type_of_contract = models.CharField(max_length=2,
verbose_name=u"Type de contrat spécifique",
choices=TYPE_OF_CONTRACT_CHOICES,
null=True, blank=True)
a4bee9e6 Mikaël Ates
management_code = models.ForeignKey('ressources.ManagementCode',
verbose_name=u"Code de gestion",
null=True, blank=True)
43b6f7ee Mikaël Ates
job = models.ForeignKey('ressources.Job',
related_name="job",
verbose_name=u"Profession",
null=True, blank=True, default=None)
parente = models.ForeignKey('ressources.PatientRelatedLink',
verbose_name=u"Lien avec le patient (Parenté)",
null=True, blank=True, default=None)
c2e0cf72 Mikaël Ates
ame = models.BooleanField(verbose_name=u"AME", default=False)
a8dd5599 Jérôme Schneider
317bcb4c Jérôme Schneider
addresses = models.ManyToManyField('PatientAddress', verbose_name=u"Adresses")
contact_comment = models.TextField(verbose_name=u"Commentaire",
null=True, blank=True)
2a928c61 Mikaël Ates
e949a412 Mikaël Ates
old_contact_id = models.CharField(max_length=256,
verbose_name=u'Ancien ID du contact', blank=True, null=True)

97546feb Mikaël Ates
def get_control_key(self):
if self.social_security_id:
ddd531b6 Mikaël Ates
nir = self.social_security_id
try:
# Corse dpt 2A et 2B
minus = 0
if nir[6] in ('A', 'a'):
nir = [c for c in nir]
nir[6] = '0'
nir = ''.join(nir)
minus = 1000000
elif nir[6] in ('B', 'b'):
nir = [c for c in nir]
nir[6] = '0'
nir = ''.join(nir)
minus = 2000000
nir = int(nir) - minus
return (97 - (nir % 97))
except Exception, e:
7c83c026 Jérôme Schneider
logger.warning("%s" % str(e))
ddd531b6 Mikaël Ates
return None
97546feb Mikaël Ates
return None

3cab63be Jérôme Schneider
3e915eb3 Jérôme Schneider
class PatientRecordManager(models.Manager):
def for_service(self, service):
return self.filter(service=service)

3cab63be Jérôme Schneider
class PatientRecord(ServiceLinkedAbstractModel, PatientContact):
3e915eb3 Jérôme Schneider
objects = PatientRecordManager()

96f613c6 Benjamin Dauvergne
class Meta:
verbose_name = u'Dossier'
verbose_name_plural = u'Dossiers'
1097fd0a Benjamin Dauvergne
6163f2ff Mikaël Ates
created = models.DateTimeField(u'création', auto_now_add=True)
creator = \
models.ForeignKey(User,
verbose_name=u'Créateur dossier patient',
8e6fe119 Jérôme Schneider
editable=True)
c9db0256 Jérôme Schneider
policyholder = models.ForeignKey('PatientContact',
null=True, blank=True,
057e2737 Frédéric Péters
verbose_name="Assuré", related_name="+",
on_delete=models.SET_NULL)
a8dd5599 Jérôme Schneider
contacts = models.ManyToManyField('PatientContact',
96f613c6 Benjamin Dauvergne
related_name='contact_of')
6c5b2d74 Jérôme Schneider
nationality = models.CharField(verbose_name=u"Nationalité",
max_length=70, null=True, blank=True)
1ff91280 Mikaël Ates
paper_id = models.CharField(max_length=6,
729480ef Jérôme Schneider
verbose_name=u"N° dossier papier",
e37c8bd1 Jérôme Schneider
null=True, blank=True)
586af040 Jérôme Schneider
last_state = models.ForeignKey(FileState, related_name='+',
4a0ff5ea Frédéric Péters
null=True, on_delete=models.SET_NULL)
67f7a6a5 Jérôme Schneider
comment = models.TextField(verbose_name=u"Commentaire",
null=True, blank=True, default=None)
e772c770 Jérôme Schneider
pause = models.BooleanField(verbose_name=u"Pause facturation",
default=False)
21fb4def Mikaël Ates
pause_comment = models.TextField(verbose_name=u"Commentaire sur la pause facturation",
null=True, blank=True, default=None)
08391712 Mikaël Ates
confidential = models.BooleanField(verbose_name=u"Confidentiel",
default=False)
af391557 Mikaël Ates
socialisation_durations = models.ManyToManyField('ressources.SocialisationDuration',
related_name='socialisation_duration_of')
mdph_requests = models.ManyToManyField('ressources.MDPHRequest',
related_name='mdph_requests_of')
mdph_responses = models.ManyToManyField('ressources.MDPHResponse',
related_name='mdph_responses_of')
d714d23e Mikaël Ates
1117d039 Mikaël Ates
# Physiology and health data
c01be6e5 Mikaël Ates
size = models.DecimalField(verbose_name=u"Taille (cm)", max_digits=5, decimal_places=1,
6c5b2d74 Jérôme Schneider
null=True, blank=True, default=None)
437531fc Mikaël Ates
weight = models.IntegerField(verbose_name=u"Poids (g)",
6c5b2d74 Jérôme Schneider
null=True, blank=True, default=None)
pregnancy_term = models.IntegerField(verbose_name=u"Terme en semaines",
null=True, blank=True, default=None)
1117d039 Mikaël Ates
cranium_perimeter = models.DecimalField(verbose_name=u"Périmètre cranien", max_digits=5, decimal_places=2,
null=True, blank=True, default=None)
chest_perimeter = models.DecimalField(verbose_name=u"Périmètre thoracique", max_digits=5, decimal_places=2,
null=True, blank=True, default=None)
437531fc Mikaël Ates
apgar_score_one = models.IntegerField(verbose_name=u"Test d'Apgar (1)",
1117d039 Mikaël Ates
null=True, blank=True, default=None)
437531fc Mikaël Ates
apgar_score_two = models.IntegerField(verbose_name=u"Test d'Apgar (5)",
1117d039 Mikaël Ates
null=True, blank=True, default=None)
mises_1 = models.ManyToManyField('ressources.CodeCFTMEA', related_name="mises1",
verbose_name=u"Axe I : catégories cliniques",
null=True, blank=True, default=None)
mises_2 = models.ManyToManyField('ressources.CodeCFTMEA', related_name="mises2",
verbose_name=u"Axe II : facteurs organiques",
null=True, blank=True, default=None)
mises_3 = models.ManyToManyField('ressources.CodeCFTMEA', related_name="mises3",
e0e4ab56 Frédéric Péters
verbose_name=u"Axe II : facteurs environnementaux",
1117d039 Mikaël Ates
null=True, blank=True, default=None)
fea9beeb Mikaël Ates
deficiency_intellectual = models.IntegerField(max_length=1,
verbose_name=u"Déficiences intellectuelles",
choices=DEFICIENCY_CHOICES,
default=0)
deficiency_autism_and_other_ted = models.IntegerField(max_length=1,
verbose_name=u"Autisme et autres TED",
choices=DEFICIENCY_CHOICES,
default=0)
deficiency_mental_disorder = models.IntegerField(max_length=1,
verbose_name=u"Troubles psychiques",
choices=DEFICIENCY_CHOICES,
default=0)
deficiency_learning_disorder = models.IntegerField(max_length=1,
verbose_name=u"Troubles du langage et des apprentissages",
choices=DEFICIENCY_CHOICES,
default=0)
deficiency_auditory = models.IntegerField(max_length=1,
verbose_name=u"Déficiences auditives",
choices=DEFICIENCY_CHOICES,
default=0)
deficiency_visual = models.IntegerField(max_length=1,
verbose_name=u"Déficiences visuelles",
choices=DEFICIENCY_CHOICES,
default=0)
deficiency_motor = models.IntegerField(max_length=1,
verbose_name=u"Déficiences motrices",
choices=DEFICIENCY_CHOICES,
default=0)
deficiency_metabolic_disorder = models.IntegerField(max_length=1,
verbose_name=u"Déficiences métaboliques",
choices=DEFICIENCY_CHOICES,
default=0)
deficiency_brain_damage = models.IntegerField(max_length=1,
verbose_name=u"Cérébro-lésions",
choices=DEFICIENCY_CHOICES,
default=0)
deficiency_polyhandicap = models.BooleanField(verbose_name=u'Polyhandicap',
default=False)
deficiency_behavioral_disorder = models.IntegerField(max_length=1,
82ef5f72 Mikaël Ates
verbose_name=u"Troubles de la conduite et du comportement",
fea9beeb Mikaël Ates
choices=DEFICIENCY_CHOICES,
default=0)
deficiency_in_diagnostic = models.BooleanField(verbose_name=u'En diagnostic',
default=False)
deficiency_other_disorder = models.IntegerField(max_length=1,
verbose_name=u"Autres types de déficience",
choices=DEFICIENCY_CHOICES,
default=0)
6c5b2d74 Jérôme Schneider
2773b4d4 Jérôme Schneider
# Inscription motive
f5b8071c Jérôme Schneider
analysemotive = models.ForeignKey('ressources.AnalyseMotive',
2773b4d4 Jérôme Schneider
verbose_name=u"Motif (analysé)",
null=True, blank=True, default=None)
428081e0 Jérôme Schneider
familymotive = models.ForeignKey('ressources.FamilyMotive',
2773b4d4 Jérôme Schneider
verbose_name=u"Motif (famille)",
null=True, blank=True, default=None)
bcb16ce5 Mikaël Ates
provenance = models.ForeignKey('ressources.Provenance',
4e0fb78c Mikaël Ates
verbose_name=u"Conseilleur",
bcb16ce5 Mikaël Ates
null=True, blank=True, default=None)
f5b8071c Jérôme Schneider
advicegiver = models.ForeignKey('ressources.AdviceGiver',
4e0fb78c Mikaël Ates
verbose_name=u"Demandeur",
2773b4d4 Jérôme Schneider
null=True, blank=True, default=None)
a37b47e6 Mikaël Ates
provenanceplace = models.ForeignKey('ressources.ProvenancePlace',
verbose_name=u"Lieu de provenance",
null=True, blank=True, default=None)
2773b4d4 Jérôme Schneider
dc1389b8 Mikaël Ates
# Out motive
outmotive = models.ForeignKey('ressources.OutMotive',
verbose_name=u"Motif de sortie",
null=True, blank=True, default=None)
outto = models.ForeignKey('ressources.OutTo',
verbose_name=u"Orientation",
null=True, blank=True, default=None)

428081e0 Jérôme Schneider
# Family
2773b4d4 Jérôme Schneider
sibship_place = models.IntegerField(verbose_name=u"Place dans la fratrie",
null=True, blank=True, default=None)
nb_children_family = models.IntegerField(verbose_name=u"Nombre d'enfants dans la fratrie",
null=True, blank=True, default=None)
cea6ec34 Jérôme Schneider
parental_authority = models.ForeignKey('ressources.ParentalAuthorityType',
2773b4d4 Jérôme Schneider
verbose_name=u"Autorité parentale",
null=True, blank=True, default=None)
428081e0 Jérôme Schneider
family_situation = models.ForeignKey('ressources.FamilySituationType',
2773b4d4 Jérôme Schneider
verbose_name=u"Situation familiale",
null=True, blank=True, default=None)
cea6ec34 Jérôme Schneider
child_custody = models.ForeignKey('ressources.ParentalCustodyType',
2773b4d4 Jérôme Schneider
verbose_name=u"Garde parentale",
null=True, blank=True, default=None)
08391712 Mikaël Ates
job_mother = models.ForeignKey('ressources.Job',
related_name="job_mother",
verbose_name=u"Profession de la mère",
null=True, blank=True, default=None)
job_father = models.ForeignKey('ressources.Job',
related_name="job_father",
verbose_name=u"Profession du père",
null=True, blank=True, default=None)
rm_mother = models.ForeignKey('ressources.MaritalStatusType',
related_name="rm_mother",
verbose_name=u"Régime matrimonial de la mère",
null=True, blank=True, default=None)
rm_father = models.ForeignKey('ressources.MaritalStatusType',
related_name="rm_father",
verbose_name=u"Régime matrimonial du père",
null=True, blank=True, default=None)
family_comment = models.TextField(verbose_name=u"Commentaire",
null=True, blank=True, default=None)
6c5b2d74 Jérôme Schneider
cea6ec34 Jérôme Schneider
# Transport
428081e0 Jérôme Schneider
transporttype = models.ForeignKey('ressources.TransportType',
cea6ec34 Jérôme Schneider
verbose_name=u"Type de transport",
null=True, blank=True, default=None)
428081e0 Jérôme Schneider
transportcompany = models.ForeignKey('ressources.TransportCompany',
cea6ec34 Jérôme Schneider
verbose_name=u"Compagnie de transport",
null=True, blank=True, default=None)

428081e0 Jérôme Schneider
# FollowUp
coordinators = models.ManyToManyField('personnes.Worker',
verbose_name=u"Coordinateurs",
null=True, blank=True, default=None)
688c7fbe Mikaël Ates
externaldoctor = models.ForeignKey('personnes.ExternalTherapist',
428081e0 Jérôme Schneider
verbose_name=u"Médecin extérieur",
null=True, blank=True, default=None)
688c7fbe Mikaël Ates
externalintervener = models.ForeignKey('personnes.ExternalWorker',
428081e0 Jérôme Schneider
verbose_name=u"Intervenant extérieur",
null=True, blank=True, default=None)

437531fc Mikaël Ates
old_id = models.CharField(max_length=256,
verbose_name=u'Ancien ID', blank=True, null=True)
old_old_id = models.CharField(max_length=256,
verbose_name=u'Ancien ancien ID', blank=True, null=True)

2671f8ab Benjamin Dauvergne
def save(self, *args, **kwargs):
if not getattr(self, 'service', None):
d714d23e Mikaël Ates
raise Exception('The field service is mandatory.')
2671f8ab Benjamin Dauvergne
super(PatientRecord, self).save(*args, **kwargs)
d714d23e Mikaël Ates
def get_state(self):
7328a577 Jérôme Schneider
return self.last_state
d714d23e Mikaël Ates
d867d175 Mikaël Ates
def get_initial_state(self):
return self.filestate_set.order_by('date_selected')[0]

3665f853 Mikaël Ates
def get_current_state(self):
today = date.today()
return self.get_state_at_day(today)

d714d23e Mikaël Ates
def get_state_at_day(self, date):
state = self.get_state()
while(state):
if datetime(state.date_selected.year,
state.date_selected.month, state.date_selected.day) <= \
datetime(date.year, date.month, date.day):
return state
state = state.previous_state
09970a47 Mikaël Ates
return None
d714d23e Mikaël Ates
fc20476a Mikaël Ates
def was_in_state_at_day(self, date, status_type):
d714d23e Mikaël Ates
state_at_day = self.get_state_at_day(date)
fc20476a Mikaël Ates
if state_at_day and state_at_day.status.type == status_type:
d714d23e Mikaël Ates
return True
return False

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

573c0b66 Mikaël Ates
def get_states_history_with_duration(self):
'''
Return the state history with for each state its duration.
If the last state is in the past, the duration is counted until today.
If the last state is in the future, the duration is not set.
'''
history = self.get_states_history()
history_with_duration = list()
today = datetime.today()
i = 0
for state in history:
history_with_duration.append([state, None])
if i != 0:
history_with_duration[i-1][1] = state.date_selected - history_with_duration[i-1][0].date_selected
if i == len(history)-1 and state.date_selected <= today:
history_with_duration[i][1] = today - history_with_duration[i][0].date_selected
i += 1
return history_with_duration

c777d441 Mikaël Ates
def can_be_deleted(self):
for act in self.act_set.all():
if act.is_state('VALIDE'):
return False
return True

055f8b05 Frédéric Péters
def delete(self, *args, **kwargs):
if self.can_be_deleted():
super(PatientRecord, self).delete(*args, **kwargs)

0c33f9fd Frédéric Péters
def get_ondisk_directory(self, service):
ef2de305 Frédéric Péters
if not settings.PATIENT_FILES_BASE_DIRECTORY:
return None

dirnames = []
dirname = self.last_name.upper()
dirnames.append(dirname)
if self.first_name:
dirname = '%s %s' % (dirname, self.first_name)
dirnames.append(dirname)
if self.paper_id:
dirname = '%s %s' % (dirname, self.paper_id)
dirnames.append(dirname)

for i, dirname in enumerate(dirnames):
0c33f9fd Frédéric Péters
fullpath = os.path.join(settings.PATIENT_FILES_BASE_DIRECTORY, service, dirname)
ef2de305 Frédéric Péters
try:
0c33f9fd Frédéric Péters
next_fullpath = os.path.join(settings.PATIENT_FILES_BASE_DIRECTORY, service, dirnames[i+1])
ef2de305 Frédéric Péters
except IndexError:
pass
else:
3c5df84d Jérôme Schneider
if os.path.exists(fullpath) and not os.path.exists(next_fullpath):
os.rename(fullpath, next_fullpath)
continue
ef2de305 Frédéric Péters
if not os.path.exists(fullpath):
os.makedirs(fullpath)
for subdir in settings.PATIENT_SUBDIRECTORIES:
subdir_fullpath = os.path.join(fullpath, subdir)
if not os.path.exists(subdir_fullpath):
os.makedirs(subdir_fullpath)
return fullpath
23937808 Frédéric Péters
ac0cd457 Frédéric Péters
def get_client_side_directory(self, service):
directory = self.get_ondisk_directory(service)
if not directory:
return None
if not settings.CLIENT_SIDE_PATIENT_FILES_BASE_DIRECTORY:
return None
return os.path.join(settings.CLIENT_SIDE_PATIENT_FILES_BASE_DIRECTORY,
directory[len(settings.PATIENT_FILES_BASE_DIRECTORY)+1:])

7328a577 Jérôme Schneider
def set_state(self, status, author, date_selected=None, comment=None):
d714d23e Mikaël Ates
if not author:
raise Exception('Missing author to set state')
if not date_selected:
date_selected = datetime.now()
current_state = self.get_state()
if not current_state:
raise Exception('Invalid patient record. '
'Missing current state.')
5030f816 Mikaël Ates
if isinstance(date_selected, date):
date_selected = datetime(year=date_selected.year,
month=date_selected.month, day=date_selected.day)
d714d23e Mikaël Ates
if date_selected < current_state.date_selected:
raise Exception('You cannot set a state starting the %s that '
'is before the previous state starting at day %s.' % \
(str(date_selected), str(current_state.date_selected)))
7328a577 Jérôme Schneider
filestate = FileState.objects.create(patient=self, status=status,
d714d23e Mikaël Ates
date_selected=date_selected, author=author, comment=comment,
7328a577 Jérôme Schneider
previous_state=current_state)
self.last_state = filestate
self.save()
d714d23e Mikaël Ates
def change_day_selected_of_state(self, state, new_date):
if state.previous_state:
if new_date < state.previous_state.date_selected:
raise Exception('You cannot set a state starting the %s '
'before the previous state starting at day %s.' % \
(str(new_date), str(state.previous_state.date_selected)))
next_state = state.get_next_state()
if next_state:
if new_date > next_state.date_selected:
raise Exception('You cannot set a state starting the %s '
'after the following state starting at day %s.' % \
(str(new_date), str(next_state.date_selected)))
state.date_selected = new_date
state.save()

def remove_state(self, state):
if state.patient.id != self.id:
raise Exception('The state given is not about this patient '
'record but about %s' % state.patient)
next_state = state.get_next_state()
if not next_state:
self.remove_last_state()
else:
next_state.previous_state = state.previous_state
next_state.save()
state.delete()

def remove_last_state(self):
try:
self.get_state().delete()
except:
pass
7b9948f9 Mikaël Ates
0f2849e4 Mikaël Ates
def get_protection_state_at_date(self, date):
try:
return self.protectionstate_set.exclude(end_date__lt=date). \
exclude(start_date__gt=date).latest('start_date')
except:
return None

e6a910a6 Mikaël Ates
# START Specific to sessad healthcare
def get_last_notification(self):
return SessadHealthCareNotification.objects.filter(patient=self, ).\
latest('end_date')

def days_before_notification_expiration(self):
today = datetime.today()
10294684 Benjamin Dauvergne
notification = self.get_last_notification(self)
e6a910a6 Mikaël Ates
if not notification:
return 0
if notification.end_date < today:
return 0
else:
return notification.end_date - today
# END Specific to sessad healthcare

0eec7968 Mikaël Ates
# START Specific to cmpp healthcare
def create_diag_healthcare(self, modifier):
"""
Gestion de l'inscription automatique.

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

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

"""
80fcd3e2 Mikaël Ates
acts = Act.objects.filter(validation_locked=False,
patient__service=self.service)
days_not_locked = sorted(set(acts.values_list('date', flat=True)))
acts = self.act_set.filter(validation_locked=True,
valide=True, is_lost=False, is_billed=False)
acts = acts.exclude(date__in=days_not_locked)
acts = acts.order_by('date')
pause_query = Q(pause=True)
billable_query = Q(act_type__billable=True, switch_billable=False) | \
Q(act_type__billable=False, switch_billable=True)
billable_acts = acts.filter(~pause_query & billable_query)

if not CmppHealthCareDiagnostic.objects.filter(patient=self).exists() \
and billable_acts:
0eec7968 Mikaël Ates
# Pas de prise en charge, on recherche l'acte facturable le plus
# ancien, on crée une pc diag à la même date.
80fcd3e2 Mikaël Ates
CmppHealthCareDiagnostic(patient=self, author=modifier,
start_date=billable_acts[0].date).save()
0eec7968 Mikaël Ates
else:
80fcd3e2 Mikaël Ates
# On recherche l'acte facturable non facturé le plus ancien après
# le dernier acte facturé et on regarde s'il a plus d'un an
0eec7968 Mikaël Ates
try:
last_billed_act = self.act_set.filter(is_billed=True).\
latest('date')
80fcd3e2 Mikaël Ates
if last_billed_act and billable_acts:
billable_acts = billable_acts.filter(date__gte=last_billed_act.date)
if billable_acts and (billable_acts[0].date - last_billed_act.date).days >= 365:
return True
return False
0eec7968 Mikaël Ates
except:
pass
72219903 Mikaël Ates
return False
0eec7968 Mikaël Ates
def automated_switch_state(self, modifier):
3a72b7ac Mikaël Ates
def state_switcher(diag, act):
if diag and (self.last_state.status.type == "ACCUEIL" or
self.last_state.status.type == "TRAITEMENT"):
status = Status.objects.get(type="DIAGNOSTIC",
services__name='CMPP')
try:
self.set_state(status, modifier, date_selected=act.date)
except:
pass
elif not diag and (self.last_state.status.type == "ACCUEIL" or
self.last_state.status.type == "DIAGNOSTIC"):
status = Status.objects.get(type="TRAITEMENT",
services__name='CMPP')
try:
self.set_state(status, modifier, date_selected=act.date)
except:
pass
# Only for CMPP and open files
if not self.service.name == 'CMPP' or \
self.last_state.status.type == "CLOS":
return
# Nothing to do if no act after the last state date
last_acts = self.act_set.filter(date__gt=self.last_state.date_selected)
if not last_acts:
return
# If the last act is billed, look at the healthcare type
last_act = last_acts.latest('date')
if last_act.is_billed:
if not last_act.healthcare:
# Billed but no healthcare, coming from imported billed acts
return
diag = False
if hasattr(last_act.healthcare, 'cmpphealthcarediagnostic'):
diag = True
return state_switcher(diag, last_act)
# Last act not billed, let's look if it is billable
from calebasse.facturation import list_acts
(acts_not_locked, days_not_locked, acts_not_valide,
acts_not_billable, acts_pause, acts_per_hc, acts_losts) = \
list_acts.list_acts_for_billing_CMPP_per_patient(self,
datetime.today(), self.service)
last_hc = None
last_act_hc = None
for hc, acts in acts_per_hc.iteritems():
if len(acts) and (not last_act_hc or
acts[-1].date > last_act_hc.date):
last_hc = hc
last_act_hc = acts[-1]
3b629e7a Mikaël Ates
# There is a billable act after the last state so either it is diag
3a72b7ac Mikaël Ates
# or it is treament
3b629e7a Mikaël Ates
if last_act_hc and last_hc and \
8b425432 Mikaël Ates
last_act_hc.date > self.last_state.date_selected.date():
3b629e7a Mikaël Ates
if hasattr(last_hc, 'cmpphealthcarediagnostic'):
state_switcher(True, last_act_hc)
else:
state_switcher(False, last_act_hc)
81916a5f Mikaël Ates
def get_healthcare_status(self):
today = date.today()
current_hc_trait = None
try:
3c5df84d Jérôme Schneider
current_hc_trait = CmppHealthCareTreatment.objects.filter(
patient=self, start_date__lte=today, end_date__gte=today
).latest('start_date')
81916a5f Mikaël Ates
except:
pass
if not current_hc_trait:
current_hc_diag = None
try:
current_hc_diag = CmppHealthCareDiagnostic.objects.filter(patient=self, start_date__lte=today).latest('start_date')
except:
pass
if current_hc_diag and current_hc_diag.get_act_number() > len(current_hc_diag.act_set.all()):

#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
# Non parce que je peux ajouter une pc de traitement alors que je veux encore facturer sur la diag precedente.
# Donc si j'ai un acte facturer en traitement alors la diag ne fonctionne plus.
# 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.
lasts_billed = Act.objects.filter(patient=self, is_billed = True, healthcare__isnull=False).order_by('-date')
last_hc_date = None
if lasts_billed:
last_hc_date = lasts_billed[0].healthcare.start_date
if not last_hc_date or last_hc_date <= current_hc_diag.start_date:
# Prise en charge disponible
return (0, len(current_hc_diag.act_set.all()), current_hc_diag.get_act_number())
last_hc_trait = None
try:
last_hc_trait = CmppHealthCareTreatment.objects.filter(patient=self).latest('start_date')
except:
pass
if not last_hc_trait:
if not current_hc_diag:
# Aucune PC
return (1, None)
else:
# PC diag full, demander PC trait
return (2, current_hc_diag.get_act_number())
if last_hc_trait.end_date < today:
# Expirée
#Test if rediagable
return (3, last_hc_trait.end_date)
if last_hc_trait.start_date > today:
# N'a pas encore pris effet
return (4, last_hc_trait.start_date)
return (-1,)
if current_hc_trait.get_act_number() > len(current_hc_trait.act_set.all()):
# Pris en charge disponible
return (5, len(current_hc_trait.act_set.all()), current_hc_trait.get_act_number())
# Prise en charge au quota
if not current_hc_trait.is_extended():
# Peut être prolongée
return (6, current_hc_trait.get_act_number())
# Prise en charge saturée
d635f4ae Mikaël Ates
return (7, current_hc_trait.get_act_number(), current_hc_trait.end_date)
0eec7968 Mikaël Ates
# END Specific to cmpp healthcare

5539237c Benjamin Dauvergne
@property
def entry_date(self):
d = self.filestate_set.filter(
Q(status__type='DIAGNOSTIC') |
3cd1c742 Mikaël Ates
Q(status__type='TRAITEMENT') |
Q(status__type='SUIVI')). \
5539237c Benjamin Dauvergne
aggregate(Min('date_selected'))['date_selected__min']
return d and d.date()

c1cdf73f Benjamin Dauvergne
5539237c Benjamin Dauvergne
@property
def exit_date(self):
c1cdf73f Benjamin Dauvergne
if self.last_state.status.type != 'CLOS':
return None
5539237c Benjamin Dauvergne
d = self.filestate_set.filter(status__type='CLOS'). \
aggregate(Max('date_selected'))['date_selected__max']
return d and d.date()

42811c4c Mikaël Ates
@property
def care_duration(self):
# Duration between the first act present and the closing date.
0d846210 Mikaël Ates
# If no closing date, end_date is the date of tha last act
42811c4c Mikaël Ates
first_act_date = None
try:
first_act_date = self.act_set.filter(valide=True).order_by('date')[0].date
except:
return 0
exit_date = self.exit_date
if not exit_date:
0d846210 Mikaël Ates
exit_date = self.act_set.filter(valide=True).order_by('-date')[0].date
42811c4c Mikaël Ates
return (exit_date - first_act_date).days
5539237c Benjamin Dauvergne
573c0b66 Mikaël Ates
@property
def care_duration_since_last_contact_or_first_act(self):
# Duration between the first act present and the closing date.
09970a47 Mikaël Ates
# If no closing date, end_date is the date of the last act
573c0b66 Mikaël Ates
contacts = FileState.objects.filter(patient=self, status__type='ACCUEIL').order_by('date_selected')
last_contact = None
95e9acfe Mikaël Ates
first_act_after_last_contact = None
573c0b66 Mikaël Ates
if len(contacts) == 1:
last_contact = contacts[0]
elif len(contacts) > 1:
last_contact = contacts[len(contacts)-1]
if last_contact:
# inscription act
95e9acfe Mikaël Ates
first_acts_after_last_contact = Act.objects.filter(patient=self, date__gte=last_contact.date_selected, valide=True).order_by('date')
573c0b66 Mikaël Ates
if first_acts_after_last_contact:
first_act_after_last_contact = first_acts_after_last_contact[0]
if not contacts:
return self.care_duration
if not first_act_after_last_contact:
return 0
exit_date = self.exit_date
if not exit_date or exit_date < first_act_after_last_contact.date:
exit_date = self.act_set.filter(valide=True).order_by('-date')[0].date
return (exit_date - first_act_after_last_contact.date).days

9c69fad0 Mikaël Ates
def care_duration_before_close_state(self, end_date=None):
if not end_date:
end_date = datetime.now()
last_close = None
try:
last_close = FileState.objects.filter(status__type='CLOS',
patient=self, date_selected__lt=end_date).order_by('-date_selected')[0]
except:
pass
first_act = None
if last_close:
try:
first_act = Act.objects.filter(patient=self, valide=True, date__lte=end_date, date__gt=last_close.date_selected).order_by('date')[0]
except:
return 0
else:
try:
first_act = Act.objects.filter(patient=self, valide=True, date__lte=end_date).order_by('date')[0]
except:
return 0
return (end_date.date() - first_act.date).days + 1

366eb65a Benjamin Dauvergne
reversion.register(PatientRecord, follow=['people_ptr'])

7b9948f9 Mikaël Ates
def create_patient(first_name, last_name, service, creator,
date_selected=None):
logger.debug('create_patient: creation for patient %s %s in service %s '
'by %s' % (first_name, last_name, service, creator))
if not (first_name and last_name and service and creator):
raise Exception('Missing parameter to create a patient record.')
586af040 Jérôme Schneider
status = Status.objects.filter(type="ACCUEIL").filter(services=service)
if not status:
raise Exception('%s has no ACCEUIL status' % service.name)
dbf80810 Jérôme Schneider
patient = PatientRecord.objects.create(first_name=first_name,
586af040 Jérôme Schneider
last_name=last_name, service=service,
creator=creator)
fs = FileState(status=status[0], author=creator, previous_state=None)
7b9948f9 Mikaël Ates
if not date_selected:
date_selected = patient.created
586af040 Jérôme Schneider
fs.patient = patient
fs.date_selected = date_selected
fs.save()
dbf80810 Jérôme Schneider
patient.last_state = fs
patient.save()
86687095 Mikaël Ates
patient.policyholder = patient.patientcontact
patient.save()
7b9948f9 Mikaël Ates
return patient