Project

General

Profile

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

calebasse / calebasse / facturation / models.py @ d694f19f

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

    
3
from datetime import date, datetime
4
from dateutil.relativedelta import relativedelta
5

    
6
from django.db import models
7
from django.db.models import Max
8

    
9
from model_utils import Choices
10

    
11
from calebasse.ressources.models import ServiceLinkedManager
12

    
13
import list_acts
14

    
15
def quarter_start_and_end_dates(today=None):
16
    '''Returns the first and last day of the current quarter'''
17
    if today is None:
18
        today = date.today()
19
    quarter = (today.month - 1) / 3
20
    start_date = date(day=1, month=((quarter*3) + 1), year=today.year)
21
    end_date = start_date + relativedelta(months=3) + relativedelta(days=-1)
22
    return start_date, end_date
23

    
24
class InvoicingManager(ServiceLinkedManager):
25
    def current_for_service(self, service):
26
        '''Return the currently open invoicing'''
27
        if service.name != 'CMPP':
28
            start_date, end_date = quarter_start_and_end_dates()
29
            invoicing, created = \
30
                self.get_or_create(start_date=start_date,
31
                end_date=end_date, service=service)
32
            if created:
33
                invoicing.status = Invoicing.STATUS.closed
34
                invoicing.save()
35
        else:
36
            try:
37
                invoicing = self.get(service=service,
38
                        status=Invoicing.STATUS.open)
39
            except Invoicing.DoesNotExist:
40
                today = date.today()
41
                start_date = date(day=today.day, month=today.month, year=today.year)
42
                invoicing, created = self.get_or_create(service=service,
43
                    start_date=start_date,
44
                    status=Invoicing.STATUS.open)
45
        return invoicing
46

    
47
    def last_for_service(self, service):
48
        current = self.current_for_service(service)
49
        last_seq_id = current.seq_id - 1
50
        try:
51
            return self.get(service=service,
52
                seq_id=last_seq_id)
53
        except:
54
            return None
55

    
56

    
57
def build_invoices_from_acts(acts_diagnostic, acts_treatment):
58
    invoices = {}
59
    len_invoices = 0
60
    len_invoices_hors_pause = 0
61
    len_acts_invoiced = 0
62
    len_acts_invoiced_hors_pause = 0
63
    for patient, acts in acts_diagnostic.items():
64
        invoices[patient] = []
65
        act, hc = acts[0]
66
        invoice = {}
67
        invoice['ppa'] = PricePerAct.get_price(act.date)
68
        invoice['year'] = act.date.year
69
        invoice['acts'] = [(act, hc)]
70
        for act, hc in acts[1:]:
71
            if invoice['ppa'] != PricePerAct.get_price(act.date) or \
72
                    invoice['year'] != act.date.year:
73
                invoices[patient].append(invoice)
74
                len_invoices = len_invoices + 1
75
                len_acts_invoiced = len_acts_invoiced + len(invoice['acts'])
76
                if not patient.pause:
77
                    len_invoices_hors_pause = len_invoices_hors_pause + 1
78
                    len_acts_invoiced_hors_pause = len_acts_invoiced_hors_pause + len(invoice['acts'])
79
                invoice['ppa'] = PricePerAct.get_price(act.date)
80
                invoice['year'] = act.date.year
81
                invoice['acts'] = [(act, hc)]
82
            else:
83
                invoice['acts'].append((act, hc))
84
        invoices[patient].append(invoice)
85
        len_invoices = len_invoices + 1
86
        len_acts_invoiced = len_acts_invoiced + len(invoice['acts'])
87
        if not patient.pause:
88
            len_invoices_hors_pause = len_invoices_hors_pause + 1
89
            len_acts_invoiced_hors_pause = len_acts_invoiced_hors_pause + len(invoice['acts'])
90
    for patient, acts in acts_treatment.items():
91
        if not patient in invoices:
92
            invoices[patient] = []
93
        act, hc = acts[0]
94
        invoice = {}
95
        invoice['ppa'] = PricePerAct.get_price(act.date)
96
        invoice['year'] = act.date.year
97
        invoice['acts'] = [(act, hc)]
98
        for act, hc in acts[1:]:
99
            if invoice['ppa'] != PricePerAct.get_price(act.date) or \
100
                    invoice['year'] != act.date.year:
101
                invoices[patient].append(invoice)
102
                len_invoices = len_invoices + 1
103
                len_acts_invoiced = len_acts_invoiced + len(invoice['acts'])
104
                if not patient.pause:
105
                    len_invoices_hors_pause = len_invoices_hors_pause + 1
106
                    len_acts_invoiced_hors_pause = len_acts_invoiced_hors_pause + len(invoice['acts'])
107
                invoice['price'] = PricePerAct.get_price(act.date)
108
                invoice['year'] = act.date.year
109
                invoice['acts'] = [(act, hc)]
110
            else:
111
                invoice['acts'].append((act, hc))
112
        invoices[patient].append(invoice)
113
        len_invoices = len_invoices + 1
114
        len_acts_invoiced = len_acts_invoiced + len(invoice['acts'])
115
        if not patient.pause:
116
            len_invoices_hors_pause = len_invoices_hors_pause + 1
117
            len_acts_invoiced_hors_pause = len_acts_invoiced_hors_pause + len(invoice['acts'])
118
    return (invoices, len_invoices, len_invoices_hors_pause,
119
        len_acts_invoiced, len_acts_invoiced_hors_pause)
120

    
121
INVOICING_OFFSET = 666
122

    
123
class Invoicing(models.Model):
124
    '''Represent a batch of invoices:
125

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

    
130
       STATUS - the possible status:
131
        - open, the invoicing is open for new acts
132
        - closed, invoicing has been closed, no new acts will be accepted after
133
          the end_date,
134
        - validated,
135
    '''
136
    STATUS = Choices('open', 'closed', 'validated', 'sent')
137

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

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

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

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

    
150
    status = models.CharField(
151
            verbose_name=u'Statut',
152
            choices=STATUS,
153
            default=STATUS.open,
154
            max_length=20)
155

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

    
158
    objects = InvoicingManager()
159

    
160
    class Meta:
161
        unique_together = (('seq_id', 'service'),)
162

    
163
    def allocate_seq_id(self):
164
        '''Allocate a new sequence id for a new invoicing.'''
165
        seq_id = 1
166
        max_seq_id = Invoicing.objects.for_service(self.service) \
167
                .aggregate(Max('seq_id'))['seq_id__max']
168
        if not max_seq_id:
169
            if self.service.name == 'CMPP':
170
                seq_id = INVOICING_OFFSET
171
        else:
172
            seq_id = max_seq_id + 1
173
        return seq_id
174

    
175
    def list_for_billing(self):
176
        '''Return the acts candidates for billing'''
177
        if self.service.name == 'CMPP':
178
            end_date = self.end_date
179
            if not end_date:
180
                today = date.today()
181
                end_date = date(day=today.day, month=today.month, year=today.year)
182
            return list_acts.list_acts_for_billing_CMPP_2(end_date,
183
                    service=self.service)
184
        elif self.service.name == 'CAMSP':
185
            return list_acts.list_acts_for_billing_CAMSP(self.start_date,
186
                    self.end_date, service=self.service)
187
        elif 'SESSAD' in self.service.name:
188
            return list_acts.list_acts_for_billing_SESSAD(self.start_date,
189
                    self.end_date, service=self.service)
190
        else:
191
            raise RuntimeError('Unknown service', self.service)
192

    
193
    def get_stats_or_validate(self, commit=False):
194
        '''
195
            If the invoicing is in state open or closed and commit is False
196
                Return the stats of the billing
197
            If the invoicing is in state open or closed and commit is True
198
                Proceed to invoices creation, healthcare charging, acts as billed
199
                Return the stats of the billing
200
            If the invoicing is in state validated or sent
201
                Return the stats of the billing
202
        '''
203
        days_not_locked = 0
204
        if self.service.name == 'CMPP':
205
            if self.status in (Invoicing.STATUS.open,
206
                    Invoicing.STATUS.closed):
207
                '''
208
                    The stats are build dynamiccaly according to the
209
                    acts billable and the healthcares
210
                '''
211
                (acts_not_locked, days_not_locked, acts_not_valide,
212
                    acts_not_billable, acts_pause, acts_diagnostic,
213
                    acts_treatment, acts_losts) = \
214
                        self.list_for_billing()
215

    
216
                (invoices, len_invoices, len_invoices_hors_pause,
217
                    len_acts_invoiced, len_acts_invoiced_hors_pause) = \
218
                        build_invoices_from_acts(acts_diagnostic, acts_treatment)
219
                len_patient_invoiced = len(invoices.keys())
220
                len_patient_invoiced_hors_pause = 0
221
                for patient in invoices.keys():
222
                    if not patient.pause:
223
                        len_patient_invoiced_hors_pause = len_patient_invoiced_hors_pause + 1
224

    
225
                patients = set(acts_not_locked.keys() + acts_not_valide.keys() + \
226
                    acts_not_billable.keys() + acts_diagnostic.keys() + acts_treatment.keys() + \
227
                    acts_losts.keys() + acts_pause.keys())
228

    
229
                patients_stats = {}
230
                len_patient_with_lost_acts = 0
231
                len_acts_lost = 0
232
                len_patient_acts_paused = 0
233
                len_acts_paused = 0
234
                for patient in patients:
235
                    patients_stats[patient] = {}
236
                    if patient in invoices.keys():
237
                        patients_stats[patient]['invoices'] = invoices[patient]
238
                        if commit and not patient.pause:
239
                            for invoice in invoices[patient]:
240
                                ppa = invoice['ppa']
241
                                acts = invoice['acts']
242
                                amount = ppa * len(acts)
243
                                in_o = Invoice(patient=patient,
244
                                    invoicing=self,
245
                                    ppa=invoice['ppa'],
246
                                    amount=amount)
247
                                in_o.save()
248
                                for act, hc in acts:
249
                                    act.is_billed = True
250
                                    act.healthcare = hc
251
                                    act.save()
252
                                    in_o.acts.add(act)
253
                            pass
254
                    if patient in acts_losts.keys():
255
                        # TODO: More details about healthcare
256
                        patients_stats[patient]['losts'] = acts_losts[patient]
257
                        len_patient_with_lost_acts = len_patient_with_lost_acts + 1
258
                        len_acts_lost = len_acts_lost + len(acts_losts[patient])
259
                    if patient in acts_pause.keys():
260
                        patients_stats[patient]['acts_paused'] = acts_pause[patient]
261
                        len_patient_acts_paused = len_patient_acts_paused + 1
262
                        len_acts_paused = len_acts_paused + len(acts_pause[patient])
263

    
264

    
265
                len_patients = len(patients_stats.keys())
266

    
267
                if commit:
268
                    self.status = Invoicing.STATUS.validated
269
                    self.save()
270

    
271
            else:
272
                '''
273
                    Grab stats from the invoices
274
                '''
275
                len_patients = 0
276
                len_invoices = 0
277
                len_acts_invoiced = 0
278
                patients_stats = {}
279
                days_not_locked = []
280
                invoices = self.invoice_set.all()
281
                for invoice in invoices:
282
                    len_invoices = len_invoices + 1
283
                    len_acts_invoiced = len_acts_invoiced + len(invoice.acts.all())
284
                    if not invoice.patient in patients_stats:
285
                        len_patients = len_patients + 1
286
                        patients_stats[invoice.patient] = {}
287
                        patients_stats[invoice.patient]['invoices'] = [invoice]
288
                    else:
289
                        patients_stats[invoice.patient]['invoices'].append(invoice)
290
                # all patients in the invoicing are invoiced
291
                len_patient_invoiced = 0
292
                # These stats are not necessary because excluded form the validated invoicing
293
                len_invoices_hors_pause = 0
294
                len_acts_invoiced_hors_pause = 0
295
                len_patient_invoiced_hors_pause = 0
296
                len_acts_lost = 0
297
                len_patient_with_lost_acts = 0
298
                len_patient_acts_paused = 0
299
                len_acts_paused = 0
300
            return (len_patients, len_invoices, len_invoices_hors_pause,
301
                len_acts_invoiced, len_acts_invoiced_hors_pause,
302
                len_patient_invoiced, len_patient_invoiced_hors_pause,
303
                len_acts_lost, len_patient_with_lost_acts, patients_stats,
304
                days_not_locked, len_patient_acts_paused,
305
                len_acts_paused)
306
        elif self.service.name == 'CAMSP':
307
            if self.status in Invoicing.STATUS.closed:
308
                (acts_not_locked, days_not_locked, acts_not_valide,
309
                acts_not_billable, acts_pause, acts_bad_state,
310
                acts_accepted) = self.list_for_billing()
311
                len_patient_pause = 0
312
                len_patient_hors_pause = 0
313
                len_acts_pause = 0
314
                len_acts_hors_pause = 0
315
                len_patient_acts_paused = 0
316
                len_acts_paused = 0
317
                patients = set(acts_accepted.keys() + acts_pause.keys())
318
                patients_stats = {}
319
                for patient in patients:
320
                    patients_stats[patient] = {}
321
                    if patient in acts_accepted.keys():
322
                        acts = acts_accepted[patient]
323
                        patients_stats[patient]['accepted'] = acts
324
                        if patient.pause:
325
                            len_patient_pause = len_patient_pause + 1
326
                            len_acts_pause = len_acts_pause + len(acts)
327
                        else:
328
                            len_patient_hors_pause = len_patient_hors_pause + 1
329
                            len_acts_hors_pause = len_acts_hors_pause + len(acts)
330
                            if commit:
331
                                for act in acts:
332
                                    self.acts.add(act)
333
                    if patient in acts_pause.keys():
334
                        patients_stats[patient]['acts_paused'] = acts_pause[patient]
335
                        len_patient_acts_paused = len_patient_acts_paused + 1
336
                        len_acts_paused = len_acts_paused + len(acts_pause[patient])
337
                if commit:
338
                    self.status = Invoicing.STATUS.validated
339
                    self.save()
340
            else:
341
                patients_stats = {}
342
                len_patient_pause = 0
343
                len_patient_hors_pause = 0
344
                len_acts_pause = 0
345
                len_acts_hors_pause = 0
346
                len_patient_acts_paused = 0
347
                len_acts_paused = 0
348
                days_not_locked = []
349
                for act in self.acts.all():
350
                    if act.patient in patients_stats.keys():
351
                        patients_stats[act.patient]['accepted'].append(act)
352
                        len_acts_hors_pause = len_acts_hors_pause + 1
353
                    else:
354
                        len_patient_hors_pause = len_patient_hors_pause + 1
355
                        len_acts_hors_pause = len_acts_hors_pause + 1
356
                        patients_stats[act.patient] = {}
357
                        patients_stats[act.patient]['accepted'] = [act]
358
            return (len_patient_pause, len_patient_hors_pause,
359
                len_acts_pause, len_acts_hors_pause, patients_stats,
360
                days_not_locked, len_patient_acts_paused,
361
                len_acts_paused)
362
        else:
363
            if self.status in Invoicing.STATUS.closed:
364
                (acts_not_locked, days_not_locked, acts_not_valide,
365
                acts_not_billable, acts_pause, acts_bad_state,
366
                acts_missing_valid_notification, acts_accepted) = \
367
                    self.list_for_billing()
368

    
369
                len_patient_pause = 0
370
                len_patient_hors_pause = 0
371
                len_acts_pause = 0
372
                len_acts_hors_pause = 0
373
                len_patient_acts_paused = 0
374
                len_acts_paused = 0
375
                len_patient_missing_notif = 0
376
                len_acts_missing_notif = 0
377
                patients = set(acts_accepted.keys() + acts_pause.keys())
378
                patients_stats = {}
379
                for patient in patients:
380
                    patients_stats[patient] = {}
381
                    if patient in acts_accepted.keys():
382
                        acts = acts_accepted[patient]
383
                        patients_stats[patient]['accepted'] = acts
384
                        if patient.pause:
385
                            len_patient_pause = len_patient_pause + 1
386
                            len_acts_pause = len_acts_pause + len(acts)
387
                        else:
388
                            len_patient_hors_pause = len_patient_hors_pause + 1
389
                            len_acts_hors_pause = len_acts_hors_pause + len(acts)
390
                            if commit:
391
                                for act in acts:
392
                                    self.acts.add(act)
393
                    if patient in acts_missing_valid_notification.keys():
394
                        acts = acts_missing_valid_notification[patient]
395
                        patients_stats[patient]['missings'] = acts
396
                        len_patient_missing_notif = len_patient_missing_notif + 1
397
                        len_acts_missing_notif = len_acts_missing_notif + len(acts)
398
                        if not 'accepted' in patients_stats[patient]:
399
                            len_patient_hors_pause = len_patient_hors_pause + 1
400
                        if commit:
401
                            for act in acts:
402
                                self.acts.add(act)
403
                    if patient in acts_pause.keys():
404
                        patients_stats[patient]['acts_paused'] = acts_pause[patient]
405
                        len_patient_acts_paused = len_patient_acts_paused + 1
406
                        len_acts_paused = len_acts_paused + len(acts_pause[patient])
407
                len_acts_hors_pause = len_acts_hors_pause + len_acts_missing_notif
408
                if commit:
409
                    self.status = Invoicing.STATUS.validated
410
                    self.save()
411
            else:
412
                patients_stats = {}
413
                len_patient_pause = 0
414
                len_patient_hors_pause = 0
415
                len_acts_pause = 0
416
                len_acts_hors_pause = 0
417
                len_patient_acts_paused = 0
418
                len_acts_paused = 0
419
                len_patient_missing_notif = 0
420
                len_acts_missing_notif = 0
421
                days_not_locked = []
422
                for act in self.acts.all():
423
                    if act.patient in patients_stats.keys():
424
                        patients_stats[act.patient]['accepted'].append(act)
425
                        len_acts_hors_pause = len_acts_hors_pause + 1
426
                    else:
427
                        len_patient_hors_pause = len_patient_hors_pause + 1
428
                        len_acts_hors_pause = len_acts_hors_pause + 1
429
                        patients_stats[act.patient] = {}
430
                        patients_stats[act.patient]['accepted'] = [act]
431
            return (len_patient_pause, len_patient_hors_pause,
432
                len_acts_pause, len_acts_hors_pause,
433
                len_patient_missing_notif, len_acts_missing_notif,
434
                patients_stats, days_not_locked,
435
                len_patient_acts_paused, len_acts_paused)
436

    
437
    def save(self, *args, **kwargs):
438
        if not self.seq_id:
439
            self.seq_id = self.allocate_seq_id()
440
        super(Invoicing, self).save(*args, **kwargs)
441

    
442
    def close(self, end_date=None):
443
        '''Close an open invoicing'''
444
        if self.service.name != 'CMPP':
445
            raise RuntimeError('closing Invoicing is only for the CMPP')
446
        if self.status != Invoicing.STATUS.open:
447
            raise RuntimeError('closing an un-opened Invoicing')
448
        if not end_date:
449
            today = date.today()
450
            end_date = date(day=today.day, month=today.month, year=today.year)
451
            if end_date < self.start_date:
452
                end_date = self.start_date + relativedelta(days=1)
453
        self.status = Invoicing.STATUS.closed
454
        self.end_date = end_date
455
        self.save()
456
        start_date = self.end_date + relativedelta(days=1)
457
        invoicing = Invoicing(service=self.service,
458
            start_date=start_date,
459
            status=Invoicing.STATUS.open)
460
        invoicing.save()
461
        return invoicing
462

    
463

    
464
class PricePerAct(models.Model):
465
    price = models.IntegerField()
466
    start_date = models.DateField(
467
            verbose_name=u"Prise d'effet",
468
            default=date(day=5,month=1,year=1970))
469
    end_date = models.DateField(
470
            verbose_name=u"Fin d'effet",
471
            blank=True,
472
            null=True)
473

    
474
    @classmethod
475
    def get_price(cls, at_date=None):
476
        if not at_date:
477
            at_date = date.today()
478
        if isinstance(at_date, datetime):
479
            at_date = date(day=at_date.day, month=at_date.month,
480
                year=at_date.year)
481
        found = cls.objects.filter(start_date__lte = at_date).latest('start_date')
482
        if not found:
483
            raise Exception('No price to apply')
484
        return found.price
485

    
486
    def __unicode__(self):
487
        val = str(self.price) + ' from ' + str(self.start_date)
488
        if self.end_date:
489
            val = val + ' to ' + str(self.end_date)
490
        return val
491

    
492

    
493
def add_price(price, start_date=None):
494
    price_o = None
495
    ppas = PricePerAct.objects.all()
496
    if ppas:
497
        if not start_date:
498
            raise Exception('A start date is mandatory to add a new Price')
499
        last_ppa = PricePerAct.objects.latest('start_date')
500
        if last_ppa.start_date >= start_date:
501
            raise Exception('The new price cannot apply before the price that currently applies.')
502
        if last_ppa.end_date and last_ppa.end_date != (start_date + relativedelta(days=-1)):
503
            raise Exception('The new price should apply the day after the last price ends.')
504
        last_ppa.end_date = start_date + relativedelta(days=-1)
505
        last_ppa.save()
506
    if not start_date:
507
        price_o = PricePerAct(price=price)
508
    else:
509
        price_o = PricePerAct(price=price, start_date=start_date)
510
    price_o.save()
511
    return price_o
512

    
513

    
514
class Invoice(models.Model):
515
    number = models.IntegerField(blank=True, null=True)
516
    created = models.DateTimeField(u'Création', auto_now_add=True)
517
    patient = models.ForeignKey('dossiers.PatientRecord')
518
    invoicing = models.ForeignKey('facturation.Invoicing',
519
        on_delete='PROTECT')
520
    acts = models.ManyToManyField('actes.Act')
521
    amount = models.IntegerField()
522
    ppa = models.IntegerField()
523

    
524
    def save(self, *args, **kwargs):
525
        invoicing = self.invoicing
526
        self.number = invoicing.seq_id * 100000 + 1
527
        max_number = invoicing.invoice_set.aggregate(Max('number'))['number__max']
528
        if max_number:
529
            self.number = max_number + 1
530
        super(Invoice, self).save(*args, **kwargs)
531

    
532
    def __unicode__(self):
533
        return "Invoice n %d of %d euros for %d acts" % (self.number, self.amount, len(self.acts.all()))
(5-5/8)