Project

General

Profile

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

calebasse / calebasse / facturation / models.py @ 017f4158

1
# -*- coding: utf-8 -*-
2
import tempfile
3

    
4
from datetime import date, datetime
5
from dateutil.relativedelta import relativedelta
6
from decimal import Decimal
7

    
8
from django.db import models
9
from django.db.models import Max, Q
10

    
11
from model_utils import Choices
12

    
13
from calebasse.dossiers.models import PatientRecord
14
from calebasse.ressources.models import (ServiceLinkedManager, PricePerAct,
15
    HealthCenter)
16

    
17
import list_acts
18
import progor
19

    
20
from batches import build_batches
21

    
22
def social_security_id_full(nir):
23
    old = nir
24
    try:
25
        # Corse dpt 2A et 2B
26
        minus = 0
27
        if nir[6] in ('A', 'a'):
28
            nir = [c for c in nir]
29
            nir[6] = '0'
30
            nir = ''.join(nir)
31
            minus = 1000000
32
        elif nir[6] in ('B', 'b'):
33
            nir = [c for c in nir]
34
            nir[6] = '0'
35
            nir = ''.join(nir)
36
            minus = 2000000
37
        nir = int(nir) - minus
38
        return old + ' %0.2d' % (97 - (nir % 97))
39
    except Exception, e:
40
        return old + ' 00'
41

    
42
def quarter_start_and_end_dates(today=None):
43
    '''Returns the first and last day of the current quarter'''
44
    if today is None:
45
        today = date.today()
46
    quarter = (today.month - 1) / 3
47
    start_date = date(day=1, month=((quarter*3) + 1), year=today.year)
48
    end_date = start_date + relativedelta(months=3) + relativedelta(days=-1)
49
    return start_date, end_date
50

    
51
class InvoicingManager(ServiceLinkedManager):
52
    def current_for_service(self, service):
53
        '''Return the currently open invoicing'''
54
        if service.name != 'CMPP':
55
            start_date, end_date = quarter_start_and_end_dates()
56
            invoicing, created = \
57
                self.get_or_create(start_date=start_date,
58
                end_date=end_date, service=service)
59
            if created:
60
                invoicing.status = Invoicing.STATUS.closed
61
                invoicing.save()
62
        else:
63
            try:
64
                invoicing = self.get(service=service,
65
                        status=Invoicing.STATUS.open)
66
            except Invoicing.DoesNotExist:
67
                today = date.today()
68
                start_date = date(day=today.day, month=today.month, year=today.year)
69
                invoicing, created = self.get_or_create(service=service,
70
                    start_date=start_date,
71
                    status=Invoicing.STATUS.open)
72
        return invoicing
73

    
74
    def last_for_service(self, service):
75
        current = self.current_for_service(service)
76
        last_seq_id = current.seq_id - 1
77
        try:
78
            return self.get(service=service,
79
                seq_id=last_seq_id)
80
        except:
81
            return None
82

    
83

    
84
def build_invoices_from_acts(acts_diagnostic, acts_treatment):
85
    invoices = {}
86
    len_invoices = 0
87
    len_invoices_hors_pause = 0
88
    len_acts_invoiced = 0
89
    len_acts_invoiced_hors_pause = 0
90
    pricing = PricePerAct.pricing()
91
    for patient, acts in acts_diagnostic.items():
92
        invoices[patient] = []
93
        act, hc = acts[0]
94
        invoice = dict()
95
        invoice['ppa'] = pricing.price_at_date(act.date)
96
        invoice['year'] = act.date.year
97
        invoice['acts'] = [(act, hc)]
98
        if len(acts) > 1:
99
            for act, hc in acts[1:]:
100
                if invoice['ppa'] != pricing.price_at_date(act.date) or \
101
                        invoice['year'] != act.date.year:
102
                    invoices[patient].append(invoice)
103
                    len_invoices += 1
104
                    len_acts_invoiced += len(invoice['acts'])
105
                    if not patient.pause:
106
                        len_invoices_hors_pause += 1
107
                        len_acts_invoiced_hors_pause += len(invoice['acts'])
108
                    invoice = dict()
109
                    invoice['ppa'] = pricing.price_at_date(act.date)
110
                    invoice['year'] = act.date.year
111
                    invoice['acts'] = [(act, hc)]
112
                else:
113
                    invoice['acts'].append((act, hc))
114
        invoices[patient].append(invoice)
115
        len_invoices += 1
116
        len_acts_invoiced += len(invoice['acts'])
117
        if not patient.pause:
118
            len_invoices_hors_pause += 1
119
            len_acts_invoiced_hors_pause += len(invoice['acts'])
120
    pricing = PricePerAct.pricing()
121
    for patient, acts in acts_treatment.items():
122
        if not patient in invoices:
123
            invoices[patient] = []
124
        act, hc = acts[0]
125
        invoice = dict()
126
        invoice['ppa'] = pricing.price_at_date(act.date)
127
        invoice['year'] = act.date.year
128
        invoice['acts'] = [(act, hc)]
129
        if len(acts) > 1:
130
            for act, hc in acts[1:]:
131
                if invoice['ppa'] != pricing.price_at_date(act.date) or \
132
                        invoice['year'] != act.date.year:
133
                    invoices[patient].append(invoice)
134
                    len_invoices += 1
135
                    len_acts_invoiced += len(invoice['acts'])
136
                    if not patient.pause:
137
                        len_invoices_hors_pause += 1
138
                        len_acts_invoiced_hors_pause += len(invoice['acts'])
139
                    invoice = dict()
140
                    invoice['ppa'] = pricing.price_at_date(act.date)
141
                    invoice['year'] = act.date.year
142
                    invoice['acts'] = [(act, hc)]
143
                else:
144
                    invoice['acts'].append((act, hc))
145
        invoices[patient].append(invoice)
146
        len_invoices += 1
147
        len_acts_invoiced += len(invoice['acts'])
148
        if not patient.pause:
149
            len_invoices_hors_pause += 1
150
            len_acts_invoiced_hors_pause += len(invoice['acts'])
151
    return (invoices, len_invoices, len_invoices_hors_pause,
152
        len_acts_invoiced, len_acts_invoiced_hors_pause)
153

    
154
# The firts cmpp invoicing with calebasse
155
INVOICING_OFFSET = 134
156

    
157
class Invoicing(models.Model):
158
    '''Represent a batch of invoices:
159

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

    
164
       STATUS - the possible status:
165
        - open, the invoicing is open for new acts
166
        - closed, invoicing has been closed, no new acts will be accepted after
167
          the end_date,
168
        - validated,
169
    '''
170
    STATUS = Choices('open', 'closed', 'validated', 'sent')
171

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

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

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

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

    
184
    status = models.CharField(
185
            verbose_name=u'Statut',
186
            choices=STATUS,
187
            default=STATUS.open,
188
            max_length=20)
189

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

    
192
    objects = InvoicingManager()
193

    
194
    class Meta:
195
        unique_together = (('seq_id', 'service'),)
196

    
197
    def allocate_seq_id(self):
198
        '''Allocate a new sequence id for a new invoicing.'''
199
        seq_id = 1
200
        max_seq_id = Invoicing.objects.for_service(self.service) \
201
                .aggregate(Max('seq_id'))['seq_id__max']
202
        if not max_seq_id:
203
            if self.service.name == 'CMPP':
204
                seq_id = INVOICING_OFFSET
205
        else:
206
            seq_id = max_seq_id + 1
207
        return seq_id
208

    
209
    def list_for_billing(self):
210
        '''Return the acts candidates for billing'''
211
        if self.service.name == 'CMPP':
212
            end_date = self.end_date
213
            if not end_date:
214
                today = date.today()
215
                end_date = date(day=today.day, month=today.month, year=today.year)
216
            return list_acts.list_acts_for_billing_CMPP(end_date,
217
                    service=self.service)
218
        elif self.service.name == 'CAMSP':
219
            return list_acts.list_acts_for_billing_CAMSP(self.start_date,
220
                    self.end_date, service=self.service)
221
        elif 'SESSAD' in self.service.name:
222
            return list_acts.list_acts_for_billing_SESSAD(self.start_date,
223
                    self.end_date, service=self.service)
224
        else:
225
            raise RuntimeError('Unknown service', self.service)
226

    
227
    def get_batches(self):
228
        batches = list()
229
        for hc, bs in build_batches(self).iteritems():
230
            for batch in bs:
231
                amount_rejected = sum([invoice.decimal_amount
232
                    for invoice in batch.invoices
233
                    if invoice.rejected])
234
                versement = batch.total - amount_rejected
235
                batches.append((hc, batch, amount_rejected, versement))
236
        return batches
237

    
238
    def get_stats_per_price_per_year(self):
239
        stats_final = dict()
240
        stats_final['total'] = (0, 0)
241
        stats_final['detail'] = dict()
242
        if self.service.name != 'CMPP' or \
243
                self.status in (Invoicing.STATUS.open,
244
                Invoicing.STATUS.closed):
245
            return stats_final
246
        stats = stats_final['detail']
247
        invoices = self.invoice_set.all()
248
        for invoice in invoices:
249
            if not invoice.list_dates:
250
                continue
251
            # All acts of an invoice are the same year and at the same price
252
            dates = invoice.list_dates.split('$')
253
            year = datetime.strptime(dates[0], "%d/%m/%Y").year
254
            ppa = invoice.decimal_ppa
255
            if year not in stats:
256
                stats[year] = dict()
257
            if ppa not in stats[year]:
258
                stats[year][ppa] = (0, 0)
259
            nb_acts, amount = stats[year][ppa]
260
            nb_acts += len(dates)
261
            amount += invoice.decimal_amount
262
            stats[year][ppa] = (nb_acts, amount)
263
            nb_acts_f, amount_f = stats_final['total']
264
            nb_acts_f += invoice.acts.count()
265
            amount_f += invoice.decimal_amount
266
            stats_final['total'] = (nb_acts_f, amount_f)
267
        return stats_final
268

    
269
    def get_stats_or_validate(self, commit=False):
270
        '''
271
            If the invoicing is in state open or closed and commit is False
272
                Return the stats of the billing
273
            If the invoicing is in state open or closed and commit is True
274
                Proceed to invoices creation, healthcare charging, acts as billed
275
                Return the stats of the billing
276
            If the invoicing is in state validated or sent
277
                Return the stats of the billing
278
        '''
279
        days_not_locked = 0
280
        if self.service.name == 'CMPP':
281
            if self.status in (Invoicing.STATUS.open,
282
                    Invoicing.STATUS.closed):
283
                '''
284
                    The stats are build dynamiccaly according to the
285
                    acts billable and the healthcares
286
                '''
287
                (acts_not_locked, days_not_locked, acts_not_valide,
288
                    acts_not_billable, acts_pause, acts_diagnostic,
289
                    acts_treatment, acts_losts,
290
                    acts_losts_missing_policy,
291
                    acts_losts_missing_birthdate) = \
292
                        self.list_for_billing()
293

    
294
                (invoices, len_invoices, len_invoices_hors_pause,
295
                    len_acts_invoiced, len_acts_invoiced_hors_pause) = \
296
                        build_invoices_from_acts(acts_diagnostic, acts_treatment)
297
                len_patient_invoiced = len(invoices.keys())
298
                len_patient_invoiced_hors_pause = 0
299
                for patient in invoices.keys():
300
                    if not patient.pause:
301
                        len_patient_invoiced_hors_pause = len_patient_invoiced_hors_pause + 1
302

    
303
                patients = set(acts_diagnostic.keys() + acts_treatment.keys() + \
304
                    acts_losts.keys() + acts_pause.keys() + acts_losts_missing_policy.keys() + \
305
                    acts_losts_missing_birthdate.keys())
306

    
307
                patients_stats = []
308
                len_patient_with_lost_acts = 0
309
                len_acts_lost = 0
310
                len_patient_acts_paused = 0
311
                len_acts_paused = 0
312
                len_patient_with_lost_acts_missing_policy = 0
313
                len_acts_losts_missing_policy = 0
314
                len_patient_with_lost_acts_missing_birthdate = 0
315
                len_acts_losts_missing_birthdate = 0
316
                batches = {}
317
                last_batch = {}
318
                for patient in patients:
319
                    dic = {}
320
                    if patient in invoices.keys():
321
                        dic['invoices'] = invoices[patient]
322
                        if commit and not patient.pause:
323
                            policy_holder = patient.policyholder
324
                            try:
325
                                address = unicode(policy_holder.addresses.filter(place_of_life=True)[0])
326
                            except:
327
                                try:
328
                                    address = unicode(policy_holder.addresses.all()[0])
329
                                except:
330
                                    address = u''
331
                            invoice_kwargs = dict(
332
                                    patient_id=patient.id,
333
                                    patient_last_name=patient.last_name,
334
                                    patient_first_name=patient.first_name,
335
                                    patient_social_security_id=patient.social_security_id or '',
336
                                    patient_birthdate=patient.birthdate,
337
                                    patient_twinning_rank=patient.twinning_rank,
338
                                    patient_healthcenter=patient.health_center,
339
                                    patient_other_health_center=patient.other_health_center or '',
340
                                    patient_entry_date=patient.entry_date,
341
                                    patient_exit_date=patient.exit_date,
342
                                    policy_holder_id=policy_holder.id,
343
                                    policy_holder_last_name=policy_holder.last_name,
344
                                    policy_holder_first_name=policy_holder.first_name,
345
                                    policy_holder_social_security_id=policy_holder.social_security_id,
346
                                    policy_holder_other_health_center=policy_holder.other_health_center or '',
347
                                    policy_holder_healthcenter=policy_holder.health_center,
348
                                    policy_holder_address=address)
349
                            for invoice in invoices[patient]:
350
                                ppa = invoice['ppa'] * 100
351
                                acts = invoice['acts']
352
                                amount = ppa * len(acts)
353
                                list_dates = '$'.join([act.date.strftime('%d/%m/%Y') for act, hc in acts])
354
                                in_o = Invoice(
355
                                        invoicing=self,
356
                                        ppa=ppa,
357
                                        amount=amount,
358
                                        list_dates = list_dates,
359
                                        **invoice_kwargs)
360
                                in_o.save()
361
                                for act, hc in acts:
362
                                    act.is_billed = True
363
                                    act.healthcare = hc
364
                                    act.save()
365
                                    in_o.acts.add(act)
366
                                in_o.first_tag = acts[0][0].get_hc_tag()
367
                                in_o.save()
368

    
369
                                # calcule le numero de lot pour cette facture
370
                                hc = in_o.health_center
371
                                hc_dest = hc.hc_invoice or hc
372
                                dest = hc_dest.large_regime.code + ' ' + hc_dest.dest_organism
373
                                if dest not in batches:
374
                                    batches[dest] = {}
375
                                if hc not in batches[dest]:
376
                                    nb1 = Invoice.objects.new_batch_number(health_center=hc_dest, invoicing=self)
377
                                    nb2 = Invoice.objects.new_batch_number(health_center=hc, invoicing=self)
378
                                    nb = max(nb1, nb2, last_batch.get(dest,0) + 1)
379
                                    last_batch[dest] = nb
380
                                    batches[dest][hc] = [{ 'batch': nb, 'size': 2, 'invoices': [], }]
381
                                # pas plus de 950 lignes par lot (le fichier B2 final ne doit
382
                                # pas depasser 999 lignes au total) :
383
                                b = batches[dest][hc].pop()
384
                                b2_size = in_o.acts.count() + 2
385
                                if (b['size'] + b2_size) > 950:
386
                                    batches[dest][hc].append(b)
387
                                    nb = last_batch.get(dest, 0) + 1
388
                                    last_batch[dest] = nb
389
                                    b = {'batch': nb, 'size': 2, 'invoices': []}
390
                                b['invoices'].append(in_o)
391
                                b['size'] += b2_size
392
                                batches[dest][hc].append(b)
393

    
394
                                in_o.batch = b['batch']
395
                                in_o.save()
396

    
397
                    if patient in acts_losts.keys():
398
                        # TODO: More details about healthcare
399
                        dic['losts'] = acts_losts[patient]
400
                        len_patient_with_lost_acts += 1
401
                        len_acts_lost += len(acts_losts[patient])
402
                    if patient in acts_pause.keys():
403
                        dic['acts_paused'] = acts_pause[patient]
404
                        len_patient_acts_paused += 1
405
                        len_acts_paused += len(acts_pause[patient])
406
                    if patient in acts_losts_missing_policy.keys():
407
                        # TODO: More details about healthcare
408
                        dic['losts_missing_policy'] = acts_losts_missing_policy[patient]
409
                        len_patient_with_lost_acts_missing_policy += 1
410
                        len_acts_losts_missing_policy += len(acts_losts_missing_policy[patient])
411
                    if patient in acts_losts_missing_birthdate.keys():
412
                        dic['losts_missing_birthdate'] = acts_losts_missing_birthdate[patient]
413
                        len_patient_with_lost_acts_missing_birthdate += 1
414
                        len_acts_losts_missing_birthdate += len(acts_losts_missing_birthdate[patient])
415
                    if dic:
416
                        patients_stats.append((patient, dic))
417
                patients_stats = sorted(patients_stats, key=lambda patient: (patient[0].last_name, patient[0].first_name))
418
                len_patients = len(patients_stats)
419

    
420
                if commit:
421
                    self.status = Invoicing.STATUS.validated
422
                    self.save()
423

    
424
            else:
425
                '''
426
                    Grab stats from the invoices
427
                '''
428
                len_patients = 0
429
                len_invoices = 0
430
                len_acts_invoiced = 0
431
                patients_stats = {}
432
                days_not_locked = []
433
                invoices = self.invoice_set.all()
434
                patients = {}
435
                for invoice in invoices:
436
                    len_invoices = len_invoices + 1
437
                    if invoice.list_dates:
438
                        len_acts_invoiced += len(invoice.list_dates.split('$'))
439
                    patient = None
440
                    if not invoice.patient_id in patients.keys():
441
                        try:
442
                            patient = PatientRecord.objects.get(id=invoice.patient_id)
443
                        except:
444
                            patient = PatientRecord(last_name=invoice.patient_last_name, first_name=invoice.patient_first_name)
445
                        patients[invoice.patient_id] = patient
446
                        len_patients = len_patients + 1
447
                        patients_stats[patient] = {}
448
                        patients_stats[patient]['invoices'] = []
449
                    else:
450
                        patient = patients[invoice.patient_id]
451
                    patients_stats[patient]['invoices'].append(invoice)
452
                patients_stats = sorted(patients_stats.items(), key=lambda patient: (patient[0].last_name, patient[0].first_name))
453
                # all patients in the invoicing are invoiced
454
                len_patient_invoiced = 0
455
                # These stats are not necessary because excluded form the validated invoicing
456
                len_invoices_hors_pause = 0
457
                len_acts_invoiced_hors_pause = 0
458
                len_patient_invoiced_hors_pause = 0
459
                len_acts_lost = 0
460
                len_patient_with_lost_acts = 0
461
                len_patient_acts_paused = 0
462
                len_acts_paused = 0
463
                len_patient_with_lost_acts_missing_policy = 0
464
                len_acts_losts_missing_policy = 0
465
                len_patient_with_lost_acts_missing_birthdate = 0
466
                len_acts_losts_missing_birthdate = 0
467
            return (len_patients, len_invoices, len_invoices_hors_pause,
468
                len_acts_invoiced, len_acts_invoiced_hors_pause,
469
                len_patient_invoiced, len_patient_invoiced_hors_pause,
470
                len_acts_lost, len_patient_with_lost_acts, patients_stats,
471
                days_not_locked, len_patient_acts_paused,
472
                len_acts_paused, len_acts_losts_missing_policy,
473
                len_patient_with_lost_acts_missing_policy,
474
                len_acts_losts_missing_birthdate,
475
                len_patient_with_lost_acts_missing_birthdate)
476
        elif self.service.name == 'CAMSP':
477
            if self.status in Invoicing.STATUS.closed:
478
                (acts_not_locked, days_not_locked, acts_not_valide,
479
                acts_not_billable, acts_pause, acts_bad_state,
480
                acts_accepted, patients_missing_policy) = self.list_for_billing()
481
                len_patient_pause = 0
482
                len_patient_hors_pause = 0
483
                len_acts_pause = 0
484
                len_acts_hors_pause = 0
485
                len_patient_acts_paused = 0
486
                len_acts_paused = 0
487
                patients = set(acts_accepted.keys() + acts_pause.keys())
488
                patients_stats = []
489
                for patient in patients:
490
                    dic = {}
491
                    if patient in acts_accepted.keys():
492
                        acts = acts_accepted[patient]
493
                        dic['accepted'] = acts
494
                        if patient.pause:
495
                            len_patient_pause += 1
496
                            len_acts_pause += len(acts)
497
                        else:
498
                            len_patient_hors_pause += 1
499
                            len_acts_hors_pause += len(acts)
500
                            if commit:
501
                                policy_holder = patient.policyholder
502
                                try:
503
                                    address = unicode(policy_holder.addresses.filter(place_of_life=True)[0])
504
                                except:
505
                                    try:
506
                                        address = unicode(policy_holder.addresses.all()[0])
507
                                    except:
508
                                        address = u''
509
                                invoice_kwargs = dict(
510
                                        patient_id=patient.id,
511
                                        patient_last_name=patient.last_name,
512
                                        patient_first_name=patient.first_name,
513
                                        patient_social_security_id=patient.social_security_id or '',
514
                                        patient_birthdate=patient.birthdate,
515
                                        patient_twinning_rank=patient.twinning_rank,
516
                                        patient_healthcenter=patient.health_center,
517
                                        patient_other_health_center=patient.other_health_center or '',
518
                                        patient_entry_date=patient.entry_date,
519
                                        patient_exit_date=patient.exit_date,
520
                                        policy_holder_id=policy_holder.id,
521
                                        policy_holder_last_name=policy_holder.last_name,
522
                                        policy_holder_first_name=policy_holder.first_name,
523
                                        policy_holder_social_security_id=policy_holder.social_security_id or '',
524
                                        policy_holder_other_health_center=policy_holder.other_health_center or '',
525
                                        policy_holder_healthcenter=policy_holder.health_center,
526
                                        policy_holder_address=address)
527
                                if policy_holder.management_code:
528
                                    management_code = policy_holder.management_code
529
                                    invoice_kwargs['policy_holder_management_code'] = management_code.code
530
                                    invoice_kwargs['policy_holder_management_code_name'] = management_code.name
531
                                list_dates = '$'.join([act.date.strftime('%d/%m/%Y') for act in acts])
532
                                invoice = Invoice(
533
                                        invoicing=self,
534
                                        ppa=0, amount=0,
535
                                        list_dates=list_dates,
536
                                        **invoice_kwargs)
537
                                invoice.save()
538
                                for act in acts:
539
                                    act.is_billed = True
540
                                    act.save()
541
                                    invoice.acts.add(act)
542
                    if patient in acts_pause.keys():
543
                        dic['acts_paused'] = acts_pause[patient]
544
                        len_patient_acts_paused += 1
545
                        len_acts_paused += len(acts_pause[patient])
546
                    patients_stats.append((patient, dic))
547
                patients_stats = sorted(patients_stats, key=lambda patient: (patient[0].last_name, patient[0].first_name))
548
                if commit:
549
                    self.status = Invoicing.STATUS.validated
550
                    self.save()
551
            else:
552
                patients_stats = {}
553
                len_patient_pause = 0
554
                len_patient_hors_pause = 0
555
                len_acts_pause = 0
556
                len_acts_hors_pause = 0
557
                len_patient_acts_paused = 0
558
                len_acts_paused = 0
559
                days_not_locked = []
560
                patients_missing_policy = []
561
                invoices = self.invoice_set.all()
562
                for invoice in invoices:
563
                    len_patient_hors_pause += 1
564
                    if invoice.list_dates:
565
                        len_acts_hors_pause += len(invoice.list_dates.split('$'))
566
                    patient = None
567
                    try:
568
                        patient = PatientRecord.objects.get(id=invoice.patient_id)
569
                    except:
570
                        patient = PatientRecord(last_name=invoice.patient_last_name, first_name=invoice.patient_first_name)
571
                    patients_stats[patient] = {}
572
                    patients_stats[patient]['accepted'] = invoice.acts.all()
573
                patients_stats = sorted(patients_stats.items(), key=lambda patient: (patient[0].last_name, patient[0].first_name))
574
            return (len_patient_pause, len_patient_hors_pause,
575
                len_acts_pause, len_acts_hors_pause, patients_stats,
576
                days_not_locked, len_patient_acts_paused,
577
                len_acts_paused, patients_missing_policy)
578
        else:
579
            if self.status in Invoicing.STATUS.closed:
580
                (acts_not_locked, days_not_locked, acts_not_valide,
581
                acts_not_billable, acts_pause, acts_bad_state,
582
                acts_missing_valid_notification, acts_accepted,
583
                patients_missing_policy) = \
584
                    self.list_for_billing()
585

    
586
                len_patient_pause = 0
587
                len_patient_hors_pause = 0
588
                len_acts_pause = 0
589
                len_acts_hors_pause = 0
590
                len_patient_acts_paused = 0
591
                len_acts_paused = 0
592
                len_patient_missing_notif = 0
593
                len_acts_missing_notif = 0
594
                patients = set(acts_accepted.keys() + \
595
                    acts_missing_valid_notification.keys() + \
596
                    acts_pause.keys())
597
                patients_stats = []
598
                patients_missing_notif = []
599
                for patient in patients:
600
                    dic = {}
601
                    acts_to_commit = []
602
                    if patient in acts_accepted.keys():
603
                        acts = acts_accepted[patient]
604
                        acts_to_commit.extend(acts)
605
                        dic['accepted'] = acts
606
                        if patient.pause:
607
                            len_patient_pause += 1
608
                            len_acts_pause += len(acts)
609
                        else:
610
                            len_patient_hors_pause += 1
611
                            len_acts_hors_pause += len(acts)
612
                    if patient in acts_missing_valid_notification.keys():
613
                        patients_missing_notif.append(patient)
614
                        acts = acts_missing_valid_notification[patient]
615
                        acts_to_commit.extend(acts)
616
                        dic['missings'] = acts
617
                        len_patient_missing_notif += 1
618
                        len_acts_missing_notif += len(acts)
619
                        if patient.pause:
620
                            if not 'accepted' in dic:
621
                                len_patient_pause += 1
622
                            len_acts_pause += len(acts)
623
                        else:
624
                            if not 'accepted' in dic:
625
                                len_patient_hors_pause += 1
626
                            len_acts_hors_pause += len(acts)
627
                    if commit and acts_to_commit:
628
                        policy_holder = patient.policyholder
629
                        try:
630
                            address = unicode(policy_holder.addresses.filter(place_of_life=True)[0])
631
                        except:
632
                            try:
633
                                address = unicode(policy_holder.addresses.all()[0])
634
                            except:
635
                                address = u''
636
                        invoice_kwargs = dict(
637
                                patient_id=patient.id,
638
                                patient_last_name=patient.last_name,
639
                                patient_first_name=patient.first_name,
640
                                patient_social_security_id=patient.social_security_id or '',
641
                                patient_birthdate=patient.birthdate,
642
                                patient_twinning_rank=patient.twinning_rank,
643
                                patient_healthcenter=patient.health_center,
644
                                patient_other_health_center=patient.other_health_center or '',
645
                                patient_entry_date=patient.entry_date,
646
                                patient_exit_date=patient.exit_date,
647
                                policy_holder_id=policy_holder.id,
648
                                policy_holder_last_name=policy_holder.last_name,
649
                                policy_holder_first_name=policy_holder.first_name,
650
                                policy_holder_social_security_id=policy_holder.social_security_id or '',
651
                                policy_holder_other_health_center=policy_holder.other_health_center or '',
652
                                policy_holder_healthcenter=policy_holder.health_center,
653
                                policy_holder_address=address)
654
                        if policy_holder.management_code:
655
                            management_code = policy_holder.management_code
656
                            invoice_kwargs['policy_holder_management_code'] = management_code.code
657
                            invoice_kwargs['policy_holder_management_code_name'] = management_code.name
658
                        list_dates = '$'.join([act.date.strftime('%d/%m/%Y') for act in acts_to_commit])
659
                        invoice = Invoice(
660
                                invoicing=self,
661
                                ppa=0, amount=0,
662
                                list_dates=list_dates,
663
                                **invoice_kwargs)
664
                        invoice.save()
665
                        for act in acts_to_commit:
666
                            act.is_billed = True
667
                            act.save()
668
                            invoice.acts.add(act)
669
                    if patient in acts_pause.keys():
670
                        dic['acts_paused'] = acts_pause[patient]
671
                        len_patient_acts_paused += 1
672
                        len_acts_paused += len(acts_pause[patient])
673
                    patients_stats.append((patient, dic))
674
                patients_stats = sorted(patients_stats, key=lambda patient: (patient[0].last_name, patient[0].first_name))
675
                if commit:
676
                    self.status = Invoicing.STATUS.validated
677
                    self.save()
678
            else:
679
                patients_stats = {}
680
                len_patient_pause = 0
681
                len_patient_hors_pause = 0
682
                len_acts_pause = 0
683
                len_acts_hors_pause = 0
684
                len_patient_acts_paused = 0
685
                len_acts_paused = 0
686
                len_patient_missing_notif = 0
687
                len_acts_missing_notif = 0
688
                days_not_locked = []
689
                patients_missing_policy = []
690
                patients_missing_notif = []
691
                invoices = self.invoice_set.all()
692
                for invoice in invoices:
693
                    len_patient_hors_pause += 1
694
                    if invoice.list_dates:
695
                        len_acts_hors_pause += len(invoice.list_dates.split('$'))
696
                    patient = None
697
                    try:
698
                        patient = PatientRecord.objects.get(id=invoice.patient_id)
699
                    except:
700
                        patient = PatientRecord(last_name=invoice.patient_last_name, first_name=invoice.patient_first_name)
701
                    patients_stats[patient] = {}
702
                    patients_stats[patient]['accepted'] = invoice.acts.all()
703
                patients_stats = sorted(patients_stats.items(), key=lambda patient: (patient[0].last_name, patient[0].first_name))
704
            return (len_patient_pause, len_patient_hors_pause,
705
                len_acts_pause, len_acts_hors_pause,
706
                len_patient_missing_notif, len_acts_missing_notif,
707
                patients_stats, days_not_locked,
708
                len_patient_acts_paused, len_acts_paused,
709
                patients_missing_policy, patients_missing_notif)
710

    
711
    def save(self, *args, **kwargs):
712
        if not self.seq_id:
713
            self.seq_id = self.allocate_seq_id()
714
        super(Invoicing, self).save(*args, **kwargs)
715

    
716
    def close(self, end_date=None):
717
        '''Close an open invoicing'''
718
        if self.service.name != 'CMPP':
719
            raise RuntimeError('closing Invoicing is only for the CMPP')
720
        if self.status != Invoicing.STATUS.open:
721
            raise RuntimeError('closing an un-opened Invoicing')
722
        if not end_date:
723
            today = date.today()
724
            end_date = date(day=today.day, month=today.month, year=today.year)
725
            if end_date < self.start_date:
726
                end_date = self.start_date + relativedelta(days=1)
727
        self.status = Invoicing.STATUS.closed
728
        self.end_date = end_date
729
        self.save()
730
        start_date = self.end_date + relativedelta(days=1)
731
        invoicing = Invoicing(service=self.service,
732
            start_date=start_date,
733
            status=Invoicing.STATUS.open)
734
        invoicing.save()
735
        return invoicing
736

    
737
    def export_for_accounting(self):
738
        '''
739
            Il faut separer les ecritures pour l'année en cours et les années
740
            passées.
741
        '''
742
        def add_ecriture(health_center, amount):
743
            accounting_number = '0' + health_center.accounting_number[1:]
744
            '''Le nom du compte ne devrait pas etre important et seul compter
745
            le numéro de compte'''
746
            name = health_center.name
747
            if len(name) > 30:
748
                name = name[0:30]
749
            ecriture = progor.EcritureComptable(date, accounting_number,
750
                name, cycle, debit=amount)
751
            imputation = progor.ImputationAnalytique(debit=amount)
752
            ecriture.add_imputation(imputation)
753
            journal.add_ecriture(ecriture)
754
        journal = progor.IdentificationJournal()
755
        ecritures_current = dict()
756
        ecritures_past = dict()
757
        total_current = 0
758
        total_past = 0
759
        date = self.end_date.strftime("%d/%m/%Y")
760
        cycle = str(self.seq_id)
761
        invoice_year = datetime(self.end_date.year, 1, 1).date()
762
        for invoice in self.invoice_set.all():
763
            hc = invoice.health_center.hc_invoice or invoice.health_center
764
            if invoice.end_date < invoice_year:
765
                ecritures_past.setdefault(hc, 0)
766
                ecritures_past[hc] += invoice.amount
767
                total_past += invoice.amount
768
            else:
769
                ecritures_current.setdefault(hc, 0)
770
                ecritures_current[hc] += invoice.amount
771
                total_current += invoice.amount
772
        '''Ce qui est facturé aux caisses est en débit'''
773
        for health_center, amount in ecritures_past.items():
774
            add_ecriture(health_center, amount)
775
        for health_center, amount in ecritures_current.items():
776
            add_ecriture(health_center, amount)
777
        '''On équilibre avec du produit en crédit'''
778
        #Produit années précédentes
779
        if total_past:
780
            ecriture = progor.EcritureComptable(date, '77200000',
781
                'PRODUITS EXERCICES ANTERIEURS', cycle, credit=total_past, type_compte='0')
782
            imputation = progor.ImputationAnalytique(credit=total_past)
783
            ecriture.add_imputation(imputation)
784
            journal.add_ecriture(ecriture)
785
        #Produit année en cours
786
        ecriture = progor.EcritureComptable(date, '73130000',
787
            'PRODUITS EXERCICE', cycle, credit=total_current, type_compte='0')
788
        imputation = progor.ImputationAnalytique(credit=total_current)
789
        ecriture.add_imputation(imputation)
790
        journal.add_ecriture(ecriture)
791
        content = journal.render()
792
        service = self.service
793
        now = datetime.now()
794
        output_file = tempfile.NamedTemporaryFile(prefix='%s-export-%s-' %
795
                (service.slug, self.id), suffix='-%s.txt' % now, delete=False)
796
        output_file.write(content)
797
        return output_file.name
798

    
799

    
800
#class PricePerAct(models.Model):
801
#    price = models.IntegerField()
802
#    start_date = models.DateField(
803
#            verbose_name=u"Prise d'effet",
804
#            default=date(day=5,month=1,year=1970))
805
#    end_date = models.DateField(
806
#            verbose_name=u"Fin d'effet",
807
#            blank=True,
808
#            null=True)
809

    
810
#    @classmethod
811
#    def get_price(cls, at_date=None):
812
#        if not at_date:
813
#            at_date = date.today()
814
#        if isinstance(at_date, datetime):
815
#            at_date = date(day=at_date.day, month=at_date.month,
816
#                year=at_date.year)
817
#        found = cls.objects.filter(start_date__lte = at_date).latest('start_date')
818
#        if not found:
819
#            raise Exception('No price to apply')
820
#        return found.price
821

    
822
#    def __unicode__(self):
823
#        val = str(self.price) + ' from ' + str(self.start_date)
824
#        if self.end_date:
825
#            val = val + ' to ' + str(self.end_date)
826
#        return val
827

    
828

    
829
#def add_price(price, start_date=None):
830
#    price_o = None
831
#    ppas = PricePerAct.objects.all()
832
#    if ppas:
833
#        if not start_date:
834
#            raise Exception('A start date is mandatory to add a new Price')
835
#        last_ppa = PricePerAct.objects.latest('start_date')
836
#        if last_ppa.start_date >= start_date:
837
#            raise Exception('The new price cannot apply before the price that currently applies.')
838
#        if last_ppa.end_date and last_ppa.end_date != (start_date + relativedelta(days=-1)):
839
#            raise Exception('The new price should apply the day after the last price ends.')
840
#        last_ppa.end_date = start_date + relativedelta(days=-1)
841
#        last_ppa.save()
842
#    if not start_date:
843
#        price_o = PricePerAct(price=price)
844
#    else:
845
#        price_o = PricePerAct(price=price, start_date=start_date)
846
#    price_o.save()
847
#    return price_o
848

    
849

    
850
PREVIOUS_MAX_BATCH_NUMBERS = {
851
    'CF00000004001110': 34,
852
    'CT00000001016421': 1,
853
    'CT00000001016422': 141,
854
    'CT00000001025691': 20,
855
    'CT00000002011011': 8,
856
    'CT00000002381421': 15,
857
    'MA00000002381421': 48,
858

    
859
    'SM00000091007381': 98, # faure 43
860
    'SM00000091007421': 98, # faure 44
861
    'SM00000091007422': 98, # faure 45
862
    'SM00000091007431': 98, # faure 46
863
    'SM00000091007691': 98, # faure 47
864
    'SM00000091007764': 98, # faure 48
865

    
866
    'SM00000092001422': 14,
867
    'SM00000092001691': 13,
868
    'SM00000099038939': 24,
869
}
870

    
871

    
872
class InvoiceManager(models.Manager):
873
    def new_batch_number(self, health_center, invoicing):
874
        '''Compute the next batch number for the given health center'''
875
        global PREVIOUS_MAX_BATCH_NUMBERS
876
        hcs = HealthCenter.objects.filter(hc_invoice=health_center)
877
        agg = self \
878
                .filter(invoicing__service=invoicing.service) \
879
                .filter(invoicing__seq_id__lt=invoicing.seq_id) \
880
                .filter(
881
                    Q(patient_healthcenter=health_center,
882
                        policy_holder_healthcenter__isnull=True)|
883
                    Q(policy_holder_healthcenter=health_center) |
884
                    Q(patient_healthcenter__in=hcs,
885
                        policy_holder_healthcenter__isnull=True)|
886
                    Q(policy_holder_healthcenter__in=hcs)) \
887
                        .aggregate(Max('batch'))
888
        max_bn = agg['batch__max']
889
        if max_bn is None:
890
            max_bn = PREVIOUS_MAX_BATCH_NUMBERS.get(health_center.b2_000(), 0)
891
        else:
892
            max_bn = max(max_bn, PREVIOUS_MAX_BATCH_NUMBERS.get(health_center.b2_000(), 0))
893
        return max_bn + 1
894

    
895

    
896
class Invoice(models.Model):
897
    objects = InvoiceManager()
898
    number = models.IntegerField(blank=True, null=True)
899
    batch = models.IntegerField(blank=True, null=True)
900
    # the patient can be modified (or even deleted) after invoice creation, so
901
    # we copy his informations here
902
    patient_id = models.IntegerField(blank=True, null=True)
903
    patient_last_name = models.CharField(max_length=128,
904
            verbose_name=u'Nom du patient', default='', blank=True)
905
    patient_first_name = models.CharField(max_length=128,
906
            verbose_name=u'Prénom(s) du patient', default='', blank=True)
907
    patient_social_security_id = models.CharField(max_length=13,
908
            verbose_name=u"NIR", default='', blank=True)
909
    patient_birthdate = models.DateField(verbose_name=u"Date de naissance",
910
            null=True, blank=True)
911
    patient_twinning_rank = models.IntegerField(
912
        verbose_name=u"Rang (gémellité)",
913
        null=True, blank=True)
914
    patient_healthcenter = models.ForeignKey('ressources.HealthCenter',
915
            verbose_name=u"Centre d'assurance maladie",
916
            related_name='related_by_patient_invoices',
917
            null=True, blank=True)
918
    patient_entry_date = models.DateField(verbose_name=u'Date d\'entrée du patient',
919
            blank=True, null=True)
920
    patient_exit_date = models.DateField(verbose_name=u'Date de sortie du patient',
921
            blank=True, null=True)
922
    patient_other_health_center = models.CharField(
923
        verbose_name=u"Centre spécifique", max_length=4, default='',
924
        blank=True)
925
    # policy holder informations
926
    policy_holder_id = models.IntegerField(blank=True, null=True)
927
    policy_holder_last_name = models.CharField(max_length=128,
928
            verbose_name=u'Nom de l\'assuré', default='', blank=True)
929
    policy_holder_first_name = models.CharField(max_length=128,
930
            verbose_name=u'Prénom(s) de l\' assuré', default='', blank=True)
931
    policy_holder_social_security_id = models.CharField(max_length=13,
932
            verbose_name=u"NIR de l\'assuré", default='', blank=True)
933
    policy_holder_healthcenter = models.ForeignKey('ressources.HealthCenter',
934
            verbose_name=u"Centre d'assurance maladie de l\'assuré",
935
            related_name='related_by_policy_holder_invoices',
936
            null=True, blank=True)
937
    policy_holder_other_health_center = models.CharField(
938
            verbose_name=u"Centre spécifique de l\'assuré", max_length=4,
939
            default='', blank=True)
940
    policy_holder_address = models.CharField(max_length=128,
941
            verbose_name=u'Adresse de l\'assuré', default='', blank=True)
942
    policy_holder_management_code = models.CharField(max_length=10,
943
            verbose_name=u'Code de gestion', default='', blank=True)
944
    policy_holder_management_code_name = models.CharField(max_length=256,
945
            verbose_name=u'Libellé du code de gestion', default='', blank=True)
946

    
947
    created = models.DateTimeField(u'Création', auto_now_add=True)
948
    invoicing = models.ForeignKey('facturation.Invoicing',
949
        on_delete=models.PROTECT)
950
    acts = models.ManyToManyField('actes.Act')
951
    list_dates = models.CharField(max_length=2048, blank=True, null=True)
952
    first_tag = models.CharField(max_length=128, blank=True, null=True)
953
    amount = models.IntegerField()
954
    ppa = models.IntegerField()
955
    rejected = models.BooleanField(verbose_name=u"Rejeté", default=False)
956

    
957
    def save(self, *args, **kwargs):
958
        if not self.number:
959
            invoicing = self.invoicing
960
            self.number = invoicing.seq_id * 1000000 + 1
961
            max_number = invoicing.invoice_set.aggregate(Max('number'))['number__max']
962
            if max_number:
963
                self.number = max_number + 1
964
        super(Invoice, self).save(*args, **kwargs)
965

    
966
    @property
967
    def start_date(self):
968
        res = date.max
969
        for d in self.list_dates.split('$'):
970
            date_o = datetime.strptime(d, '%d/%m/%Y').date()
971
            res = min(res, date_o)
972
        return res
973

    
974
    @property
975
    def health_center(self):
976
        return self.policy_holder_healthcenter or self.patient_healthcenter
977

    
978
    @property
979
    def end_date(self):
980
        res = date.min
981
        for d in self.list_dates.split('$'):
982
            date_o = datetime.strptime(d, '%d/%m/%Y').date()
983
            res = max(res, date_o)
984
        return res
985

    
986
    @property
987
    def decimal_amount(self):
988
        return Decimal(self.amount) / Decimal(100)
989

    
990
    @property
991
    def decimal_amount_corrected(self):
992
        amount = 0
993
        for d, ppa in self.list_dates:
994
            amount += ppa
995
        return Decimal(amount) / Decimal(100)
996

    
997
    @property
998
    def decimal_ppa(self):
999
        return Decimal(self.ppa) / Decimal(100)
1000

    
1001
    @property
1002
    def kind(self):
1003
        if not self.first_tag:
1004
            return None
1005
        return self.first_tag[0]
1006

    
1007
    @property
1008
    def patient_social_security_id_full(self):
1009
        if self.patient_social_security_id:
1010
            return social_security_id_full(self.patient_social_security_id)
1011
        else:
1012
            return ''
1013

    
1014
    @property
1015
    def policy_holder_social_security_id_full(self):
1016
        if self.policy_holder_social_security_id:
1017
            return social_security_id_full(self.policy_holder_social_security_id)
1018
        else:
1019
            return ''
1020

    
1021
    def __unicode__(self):
1022
        return "Facture %d de %d euros (%d actes)" % (self.number, self.amount, len(self.acts.all()))
(9-9/16)