Project

General

Profile

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

calebasse / calebasse / facturation / models.py @ 76974b6f

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
# The firts cmpp invoicing with calebasse
122
INVOICING_OFFSET = 134
123

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

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

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

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

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

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

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

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

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

    
159
    objects = InvoicingManager()
160

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

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

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

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

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

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

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

    
265

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

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

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

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

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

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

    
464

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

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

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

    
493

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

    
514

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

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

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