Project

General

Profile

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

calebasse / calebasse / facturation / models.py @ 99a62b84

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_not_locked.keys() + acts_not_valide.keys() + \
304
                    acts_not_billable.keys() + acts_diagnostic.keys() + acts_treatment.keys() + \
305
                    acts_losts.keys() + acts_pause.keys() + acts_losts_missing_policy.keys() + \
306
                    acts_losts_missing_birthdate.keys())
307

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

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

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

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

    
421
                len_patients = len(patients_stats)
422

    
423
                if commit:
424
                    self.status = Invoicing.STATUS.validated
425
                    self.save()
426

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

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

    
714
    def save(self, *args, **kwargs):
715
        if not self.seq_id:
716
            self.seq_id = self.allocate_seq_id()
717
        super(Invoicing, self).save(*args, **kwargs)
718

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

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

    
802

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

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

    
825
#    def __unicode__(self):
826
#        val = str(self.price) + ' from ' + str(self.start_date)
827
#        if self.end_date:
828
#            val = val + ' to ' + str(self.end_date)
829
#        return val
830

    
831

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

    
852

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

    
862
    'SM00000091007381': 98, # faure 43
863
    'SM00000091007421': 98, # faure 44
864
    'SM00000091007422': 98, # faure 45
865
    'SM00000091007431': 98, # faure 46
866
    'SM00000091007691': 98, # faure 47
867
    'SM00000091007764': 98, # faure 48
868

    
869
    'SM00000092001422': 14,
870
    'SM00000092001691': 13,
871
    'SM00000099038939': 24,
872
}
873

    
874

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

    
898

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

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

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

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

    
977
    @property
978
    def health_center(self):
979
        return self.policy_holder_healthcenter or self.patient_healthcenter
980

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

    
989
    @property
990
    def decimal_amount(self):
991
        return Decimal(self.amount) / Decimal(100)
992

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

    
1000
    @property
1001
    def decimal_ppa(self):
1002
        return Decimal(self.ppa) / Decimal(100)
1003

    
1004
    @property
1005
    def kind(self):
1006
        if not self.first_tag:
1007
            return None
1008
        return self.first_tag[0]
1009

    
1010
    @property
1011
    def patient_social_security_id_full(self):
1012
        if self.patient_social_security_id:
1013
            return social_security_id_full(self.patient_social_security_id)
1014
        else:
1015
            return ''
1016

    
1017
    @property
1018
    def policy_holder_social_security_id_full(self):
1019
        if self.policy_holder_social_security_id:
1020
            return social_security_id_full(self.policy_holder_social_security_id)
1021
        else:
1022
            return ''
1023

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