Project

General

Profile

Download (23.4 KB) Statistics
| Branch: | Tag: | Revision:
8254b552 Benjamin Dauvergne
# -*- coding: utf-8 -*-

3ac2302c Mikaël Ates
from datetime import date, datetime
8254b552 Benjamin Dauvergne
from dateutil.relativedelta import relativedelta

fd0f45e5 Benjamin Dauvergne
from django.db import models
8254b552 Benjamin Dauvergne
from django.db.models import Max

from model_utils import Choices

from calebasse.ressources.models import ServiceLinkedManager

import list_acts

def quarter_start_and_end_dates(today=None):
'''Returns the first and last day of the current quarter'''
if today is None:
today = date.today()
a5ae3c51 Mikaël Ates
quarter = (today.month - 1) / 3
start_date = date(day=1, month=((quarter*3) + 1), year=today.year)
777d5c42 Benjamin Dauvergne
end_date = start_date + relativedelta(months=3) + relativedelta(days=-1)
8254b552 Benjamin Dauvergne
return start_date, end_date

class InvoicingManager(ServiceLinkedManager):
def current_for_service(self, service):
'''Return the currently open invoicing'''
if service.name != 'CMPP':
start_date, end_date = quarter_start_and_end_dates()
a1a9815a Mikaël Ates
invoicing, created = \
self.get_or_create(start_date=start_date,
11c4e940 Mikaël Ates
end_date=end_date, service=service)
if created:
invoicing.status = Invoicing.STATUS.closed
invoicing.save()
8254b552 Benjamin Dauvergne
else:
try:
invoicing = self.get(service=service,
82c98061 Mikaël Ates
status=Invoicing.STATUS.open)
8254b552 Benjamin Dauvergne
except Invoicing.DoesNotExist:
82c98061 Mikaël Ates
today = date.today()
start_date = date(day=today.day, month=today.month, year=today.year)
8254b552 Benjamin Dauvergne
invoicing, created = self.get_or_create(service=service,
3ac2302c Mikaël Ates
start_date=start_date,
82c98061 Mikaël Ates
status=Invoicing.STATUS.open)
8254b552 Benjamin Dauvergne
return invoicing

82c98061 Mikaël Ates
def last_for_service(self, service):
current = self.current_for_service(service)
last_seq_id = current.seq_id - 1
try:
return self.get(service=service,
seq_id=last_seq_id)
except:
return None

3ac2302c Mikaël Ates
def build_invoices_from_acts(acts_diagnostic, acts_treatment):
invoices = {}
len_invoices = 0
len_invoices_hors_pause = 0
len_acts_invoiced = 0
len_acts_invoiced_hors_pause = 0
for patient, acts in acts_diagnostic.items():
invoices[patient] = []
act, hc = acts[0]
invoice = {}
invoice['ppa'] = PricePerAct.get_price(act.date)
invoice['year'] = act.date.year
invoice['acts'] = [(act, hc)]
for act, hc in acts[1:]:
if invoice['ppa'] != PricePerAct.get_price(act.date) or \
invoice['year'] != act.date.year:
invoices[patient].append(invoice)
len_invoices = len_invoices + 1
len_acts_invoiced = len_acts_invoiced + len(invoice['acts'])
if not patient.pause:
len_invoices_hors_pause = len_invoices_hors_pause + 1
len_acts_invoiced_hors_pause = len_acts_invoiced_hors_pause + len(invoice['acts'])
invoice['ppa'] = PricePerAct.get_price(act.date)
invoice['year'] = act.date.year
invoice['acts'] = [(act, hc)]
else:
invoice['acts'].append((act, hc))
invoices[patient].append(invoice)
len_invoices = len_invoices + 1
len_acts_invoiced = len_acts_invoiced + len(invoice['acts'])
if not patient.pause:
len_invoices_hors_pause = len_invoices_hors_pause + 1
len_acts_invoiced_hors_pause = len_acts_invoiced_hors_pause + len(invoice['acts'])
for patient, acts in acts_treatment.items():
if not patient in invoices:
invoices[patient] = []
act, hc = acts[0]
invoice = {}
invoice['ppa'] = PricePerAct.get_price(act.date)
invoice['year'] = act.date.year
invoice['acts'] = [(act, hc)]
for act, hc in acts[1:]:
if invoice['ppa'] != PricePerAct.get_price(act.date) or \
invoice['year'] != act.date.year:
invoices[patient].append(invoice)
len_invoices = len_invoices + 1
len_acts_invoiced = len_acts_invoiced + len(invoice['acts'])
if not patient.pause:
len_invoices_hors_pause = len_invoices_hors_pause + 1
len_acts_invoiced_hors_pause = len_acts_invoiced_hors_pause + len(invoice['acts'])
invoice['price'] = PricePerAct.get_price(act.date)
invoice['year'] = act.date.year
invoice['acts'] = [(act, hc)]
else:
invoice['acts'].append((act, hc))
invoices[patient].append(invoice)
len_invoices = len_invoices + 1
len_acts_invoiced = len_acts_invoiced + len(invoice['acts'])
if not patient.pause:
len_invoices_hors_pause = len_invoices_hors_pause + 1
len_acts_invoiced_hors_pause = len_acts_invoiced_hors_pause + len(invoice['acts'])
return (invoices, len_invoices, len_invoices_hors_pause,
len_acts_invoiced, len_acts_invoiced_hors_pause)

b0222841 Mikaël Ates
# The firts cmpp invoicing with calebasse
INVOICING_OFFSET = 134
3ac2302c Mikaël Ates
8254b552 Benjamin Dauvergne
class Invoicing(models.Model):
'''Represent a batch of invoices:

end_date - only acts before this date will be considered
status - current status of the invoicing
acts - acts bounded to this invoicing when the invoicing is validated

STATUS - the possible status:
- open, the invoicing is open for new acts
- closed, invoicing has been closed, no new acts will be accepted after
the end_date,
82c98061 Mikaël Ates
- validated,
8254b552 Benjamin Dauvergne
'''
STATUS = Choices('open', 'closed', 'validated', 'sent')

seq_id = models.IntegerField(blank=True, null=True)

service = models.ForeignKey('ressources.Service', on_delete='PROTECT')

start_date = models.DateField(
verbose_name=u'Ouverture de la facturation')

end_date = models.DateField(
verbose_name=u'Clôturation de la facturation',
blank=True,
null=True)

status = models.CharField(
verbose_name=u'Statut',
choices=STATUS,
default=STATUS.open,
max_length=20)

acts = models.ManyToManyField('actes.Act')

objects = InvoicingManager()

class Meta:
fe1b46f3 Benjamin Dauvergne
unique_together = (('seq_id', 'service'),)
8254b552 Benjamin Dauvergne
def allocate_seq_id(self):
'''Allocate a new sequence id for a new invoicing.'''
82c98061 Mikaël Ates
seq_id = 1
max_seq_id = Invoicing.objects.for_service(self.service) \
.aggregate(Max('seq_id'))['seq_id__max']
ff8d6077 Mikaël Ates
if not max_seq_id:
if self.service.name == 'CMPP':
seq_id = INVOICING_OFFSET
else:
seq_id = max_seq_id + 1
82c98061 Mikaël Ates
return seq_id
8254b552 Benjamin Dauvergne
def list_for_billing(self):
3ac2302c Mikaël Ates
'''Return the acts candidates for billing'''
8254b552 Benjamin Dauvergne
if self.service.name == 'CMPP':
82c98061 Mikaël Ates
end_date = self.end_date
if not end_date:
today = date.today()
end_date = date(day=today.day, month=today.month, year=today.year)
3ac2302c Mikaël Ates
return list_acts.list_acts_for_billing_CMPP_2(end_date,
service=self.service)
8254b552 Benjamin Dauvergne
elif self.service.name == 'CAMSP':
return list_acts.list_acts_for_billing_CAMSP(self.start_date,
self.end_date, service=self.service)
1368bb27 Mikaël Ates
elif 'SESSAD' in self.service.name:
8254b552 Benjamin Dauvergne
return list_acts.list_acts_for_billing_SESSAD(self.start_date,
self.end_date, service=self.service)
else:
raise RuntimeError('Unknown service', self.service)

3ac2302c Mikaël Ates
def get_stats_or_validate(self, commit=False):
'''
If the invoicing is in state open or closed and commit is False
Return the stats of the billing
If the invoicing is in state open or closed and commit is True
60af3866 Mikaël Ates
Proceed to invoices creation, healthcare charging, acts as billed
3ac2302c Mikaël Ates
Return the stats of the billing
If the invoicing is in state validated or sent
Return the stats of the billing
'''
a49f011d Mikaël Ates
days_not_locked = 0
3ac2302c Mikaël Ates
if self.service.name == 'CMPP':
if self.status in (Invoicing.STATUS.open,
Invoicing.STATUS.closed):
'''
The stats are build dynamiccaly according to the
acts billable and the healthcares
'''
(acts_not_locked, days_not_locked, acts_not_valide,
bc7e290e Mikaël Ates
acts_not_billable, acts_pause, acts_diagnostic,
acts_treatment, acts_losts) = \
self.list_for_billing()
3ac2302c Mikaël Ates
(invoices, len_invoices, len_invoices_hors_pause,
len_acts_invoiced, len_acts_invoiced_hors_pause) = \
build_invoices_from_acts(acts_diagnostic, acts_treatment)
len_patient_invoiced = len(invoices.keys())
len_patient_invoiced_hors_pause = 0
for patient in invoices.keys():
if not patient.pause:
len_patient_invoiced_hors_pause = len_patient_invoiced_hors_pause + 1

patients = set(acts_not_locked.keys() + acts_not_valide.keys() + \
acts_not_billable.keys() + acts_diagnostic.keys() + acts_treatment.keys() + \
bc7e290e Mikaël Ates
acts_losts.keys() + acts_pause.keys())
3ac2302c Mikaël Ates
patients_stats = {}
len_patient_with_lost_acts = 0
len_acts_lost = 0
bc7e290e Mikaël Ates
len_patient_acts_paused = 0
len_acts_paused = 0
3ac2302c Mikaël Ates
for patient in patients:
patients_stats[patient] = {}
if patient in invoices.keys():
patients_stats[patient]['invoices'] = invoices[patient]
if commit and not patient.pause:
for invoice in invoices[patient]:
ppa = invoice['ppa']
acts = invoice['acts']
amount = ppa * len(acts)
in_o = Invoice(patient=patient,
invoicing=self,
ppa=invoice['ppa'],
amount=amount)
in_o.save()
for act, hc in acts:
act.is_billed = True
act.healthcare = hc
act.save()
in_o.acts.add(act)
pass
if patient in acts_losts.keys():
# TODO: More details about healthcare
c94d6cba Mikaël Ates
patients_stats[patient]['losts'] = acts_losts[patient]
3ac2302c Mikaël Ates
len_patient_with_lost_acts = len_patient_with_lost_acts + 1
len_acts_lost = len_acts_lost + len(acts_losts[patient])
bc7e290e Mikaël Ates
if patient in acts_pause.keys():
patients_stats[patient]['acts_paused'] = acts_pause[patient]
len_patient_acts_paused = len_patient_acts_paused + 1
len_acts_paused = len_acts_paused + len(acts_pause[patient])


3ac2302c Mikaël Ates
len_patients = len(patients_stats.keys())

if commit:
self.status = Invoicing.STATUS.validated
self.save()

else:
'''
Grab stats from the invoices
'''
len_patients = 0
len_invoices = 0
len_acts_invoiced = 0
patients_stats = {}
09de66b4 Mikaël Ates
days_not_locked = []
3ac2302c Mikaël Ates
invoices = self.invoice_set.all()
for invoice in invoices:
len_invoices = len_invoices + 1
len_acts_invoiced = len_acts_invoiced + len(invoice.acts.all())
if not invoice.patient in patients_stats:
len_patients = len_patients + 1
patients_stats[invoice.patient] = {}
patients_stats[invoice.patient]['invoices'] = [invoice]
else:
patients_stats[invoice.patient]['invoices'].append(invoice)
# all patients in the invoicing are invoiced
len_patient_invoiced = 0
# These stats are not necessary because excluded form the validated invoicing
len_invoices_hors_pause = 0
len_acts_invoiced_hors_pause = 0
len_patient_invoiced_hors_pause = 0
len_acts_lost = 0
len_patient_with_lost_acts = 0
bc7e290e Mikaël Ates
len_patient_acts_paused = 0
len_acts_paused = 0
3ac2302c Mikaël Ates
return (len_patients, len_invoices, len_invoices_hors_pause,
len_acts_invoiced, len_acts_invoiced_hors_pause,
len_patient_invoiced, len_patient_invoiced_hors_pause,
bc7e290e Mikaël Ates
len_acts_lost, len_patient_with_lost_acts, patients_stats,
days_not_locked, len_patient_acts_paused,
len_acts_paused)
09de66b4 Mikaël Ates
elif self.service.name == 'CAMSP':
if self.status in Invoicing.STATUS.closed:
(acts_not_locked, days_not_locked, acts_not_valide,
bc7e290e Mikaël Ates
acts_not_billable, acts_pause, acts_bad_state,
09de66b4 Mikaël Ates
acts_accepted) = self.list_for_billing()
len_patient_pause = 0
len_patient_hors_pause = 0
len_acts_pause = 0
len_acts_hors_pause = 0
bc7e290e Mikaël Ates
len_patient_acts_paused = 0
len_acts_paused = 0
patients = set(acts_accepted.keys() + acts_pause.keys())
patients_stats = {}
for patient in patients:
patients_stats[patient] = {}
if patient in acts_accepted.keys():
acts = acts_accepted[patient]
patients_stats[patient]['accepted'] = acts
if patient.pause:
len_patient_pause = len_patient_pause + 1
len_acts_pause = len_acts_pause + len(acts)
else:
len_patient_hors_pause = len_patient_hors_pause + 1
len_acts_hors_pause = len_acts_hors_pause + len(acts)
if commit:
for act in acts:
self.acts.add(act)
if patient in acts_pause.keys():
patients_stats[patient]['acts_paused'] = acts_pause[patient]
len_patient_acts_paused = len_patient_acts_paused + 1
len_acts_paused = len_acts_paused + len(acts_pause[patient])
09de66b4 Mikaël Ates
if commit:
self.status = Invoicing.STATUS.validated
self.save()
else:
bc7e290e Mikaël Ates
patients_stats = {}
09de66b4 Mikaël Ates
len_patient_pause = 0
len_patient_hors_pause = 0
len_acts_pause = 0
len_acts_hors_pause = 0
bc7e290e Mikaël Ates
len_patient_acts_paused = 0
len_acts_paused = 0
09de66b4 Mikaël Ates
days_not_locked = []
for act in self.acts.all():
bc7e290e Mikaël Ates
if act.patient in patients_stats.keys():
patients_stats[act.patient]['accepted'].append(act)
09de66b4 Mikaël Ates
len_acts_hors_pause = len_acts_hors_pause + 1
else:
len_patient_hors_pause = len_patient_hors_pause + 1
len_acts_hors_pause = len_acts_hors_pause + 1
bc7e290e Mikaël Ates
patients_stats[act.patient] = {}
patients_stats[act.patient]['accepted'] = [act]
09de66b4 Mikaël Ates
return (len_patient_pause, len_patient_hors_pause,
bc7e290e Mikaël Ates
len_acts_pause, len_acts_hors_pause, patients_stats,
days_not_locked, len_patient_acts_paused,
len_acts_paused)
3ac2302c Mikaël Ates
else:
1368bb27 Mikaël Ates
if self.status in Invoicing.STATUS.closed:
(acts_not_locked, days_not_locked, acts_not_valide,
bc7e290e Mikaël Ates
acts_not_billable, acts_pause, acts_bad_state,
acts_missing_valid_notification, acts_accepted) = \
self.list_for_billing()

1368bb27 Mikaël Ates
len_patient_pause = 0
len_patient_hors_pause = 0
len_acts_pause = 0
len_acts_hors_pause = 0
bc7e290e Mikaël Ates
len_patient_acts_paused = 0
len_acts_paused = 0
len_patient_missing_notif = 0
len_acts_missing_notif = 0
patients = set(acts_accepted.keys() + acts_pause.keys())
patients_stats = {}
for patient in patients:
patients_stats[patient] = {}
if patient in acts_accepted.keys():
acts = acts_accepted[patient]
patients_stats[patient]['accepted'] = acts
if patient.pause:
len_patient_pause = len_patient_pause + 1
len_acts_pause = len_acts_pause + len(acts)
else:
len_patient_hors_pause = len_patient_hors_pause + 1
len_acts_hors_pause = len_acts_hors_pause + len(acts)
if commit:
for act in acts:
self.acts.add(act)
if patient in acts_missing_valid_notification.keys():
acts = acts_missing_valid_notification[patient]
patients_stats[patient]['missings'] = acts
len_patient_missing_notif = len_patient_missing_notif + 1
len_acts_missing_notif = len_acts_missing_notif + len(acts)
if not 'accepted' in patients_stats[patient]:
len_patient_hors_pause = len_patient_hors_pause + 1
1368bb27 Mikaël Ates
if commit:
for act in acts:
self.acts.add(act)
bc7e290e Mikaël Ates
if patient in acts_pause.keys():
patients_stats[patient]['acts_paused'] = acts_pause[patient]
len_patient_acts_paused = len_patient_acts_paused + 1
len_acts_paused = len_acts_paused + len(acts_pause[patient])
len_acts_hors_pause = len_acts_hors_pause + len_acts_missing_notif
1368bb27 Mikaël Ates
if commit:
self.status = Invoicing.STATUS.validated
self.save()
else:
bc7e290e Mikaël Ates
patients_stats = {}
1368bb27 Mikaël Ates
len_patient_pause = 0
len_patient_hors_pause = 0
len_acts_pause = 0
len_acts_hors_pause = 0
bc7e290e Mikaël Ates
len_patient_acts_paused = 0
len_acts_paused = 0
1368bb27 Mikaël Ates
len_patient_missing_notif = 0
len_acts_missing_notif = 0
days_not_locked = []
for act in self.acts.all():
bc7e290e Mikaël Ates
if act.patient in patients_stats.keys():
patients_stats[act.patient]['accepted'].append(act)
1368bb27 Mikaël Ates
len_acts_hors_pause = len_acts_hors_pause + 1
else:
len_patient_hors_pause = len_patient_hors_pause + 1
len_acts_hors_pause = len_acts_hors_pause + 1
bc7e290e Mikaël Ates
patients_stats[act.patient] = {}
patients_stats[act.patient]['accepted'] = [act]
1368bb27 Mikaël Ates
return (len_patient_pause, len_patient_hors_pause,
len_acts_pause, len_acts_hors_pause,
len_patient_missing_notif, len_acts_missing_notif,
bc7e290e Mikaël Ates
patients_stats, days_not_locked,
len_patient_acts_paused, len_acts_paused)
3ac2302c Mikaël Ates
8254b552 Benjamin Dauvergne
def save(self, *args, **kwargs):
if not self.seq_id:
self.seq_id = self.allocate_seq_id()
super(Invoicing, self).save(*args, **kwargs)

82c98061 Mikaël Ates
def close(self, end_date=None):
8254b552 Benjamin Dauvergne
'''Close an open invoicing'''
82c98061 Mikaël Ates
if self.service.name != 'CMPP':
raise RuntimeError('closing Invoicing is only for the CMPP')
8254b552 Benjamin Dauvergne
if self.status != Invoicing.STATUS.open:
raise RuntimeError('closing an un-opened Invoicing')
82c98061 Mikaël Ates
if not end_date:
today = date.today()
end_date = date(day=today.day, month=today.month, year=today.year)
3ac2302c Mikaël Ates
if end_date < self.start_date:
end_date = self.start_date + relativedelta(days=1)
82c98061 Mikaël Ates
self.status = Invoicing.STATUS.closed
self.end_date = end_date
8254b552 Benjamin Dauvergne
self.save()
3ac2302c Mikaël Ates
start_date = self.end_date + relativedelta(days=1)
invoicing = Invoicing(service=self.service,
start_date=start_date,
status=Invoicing.STATUS.open)
invoicing.save()
return invoicing
8254b552 Benjamin Dauvergne
3ac2302c Mikaël Ates
class PricePerAct(models.Model):
price = models.IntegerField()
start_date = models.DateField(
verbose_name=u"Prise d'effet",
default=date(day=5,month=1,year=1970))
end_date = models.DateField(
verbose_name=u"Fin d'effet",
blank=True,
null=True)

@classmethod
def get_price(cls, at_date=None):
if not at_date:
at_date = date.today()
if isinstance(at_date, datetime):
at_date = date(day=at_date.day, month=at_date.month,
year=at_date.year)
found = cls.objects.filter(start_date__lte = at_date).latest('start_date')
if not found:
raise Exception('No price to apply')
return found.price

def __unicode__(self):
val = str(self.price) + ' from ' + str(self.start_date)
if self.end_date:
val = val + ' to ' + str(self.end_date)
return val


def add_price(price, start_date=None):
price_o = None
ppas = PricePerAct.objects.all()
if ppas:
if not start_date:
raise Exception('A start date is mandatory to add a new Price')
last_ppa = PricePerAct.objects.latest('start_date')
if last_ppa.start_date >= start_date:
raise Exception('The new price cannot apply before the price that currently applies.')
if last_ppa.end_date and last_ppa.end_date != (start_date + relativedelta(days=-1)):
raise Exception('The new price should apply the day after the last price ends.')
last_ppa.end_date = start_date + relativedelta(days=-1)
last_ppa.save()
if not start_date:
price_o = PricePerAct(price=price)
else:
price_o = PricePerAct(price=price, start_date=start_date)
price_o.save()
return price_o


class Invoice(models.Model):
number = models.IntegerField(blank=True, null=True)
created = models.DateTimeField(u'Création', auto_now_add=True)
patient = models.ForeignKey('dossiers.PatientRecord')
invoicing = models.ForeignKey('facturation.Invoicing',
on_delete='PROTECT')
acts = models.ManyToManyField('actes.Act')
amount = models.IntegerField()
ppa = models.IntegerField()

def save(self, *args, **kwargs):
ff8d6077 Mikaël Ates
invoicing = self.invoicing
self.number = invoicing.seq_id * 100000 + 1
max_number = invoicing.invoice_set.aggregate(Max('number'))['number__max']
if max_number:
self.number = max_number + 1
3ac2302c Mikaël Ates
super(Invoice, self).save(*args, **kwargs)

def __unicode__(self):
return "Invoice n %d of %d euros for %d acts" % (self.number, self.amount, len(self.acts.all()))