Project

General

Profile

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

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

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

    
3
import os
4
import sys
5
import re
6
import glob
7
import tempfile
8
import time
9
import datetime
10
import hashlib
11
import base64
12
import json
13
from smtplib import SMTP, SMTPException
14

    
15
from calebasse.facturation.models import Invoicing
16
from batches import build_batches
17
from transmission_utils import build_mail
18

    
19
DEFAULT_OUTPUT_DIRECTORY = '/var/lib/calebasse/B2'
20
DEFAULT_NORME = 'CP  '
21
DEFAULT_TYPE_EMETTEUR = 'TE'
22
DEFAULT_APPLICATION = 'TR'
23
DEFAULT_CATEGORIE = '189'
24
DEFAULT_STATUT = '60'
25
DEFAULT_MODE_TARIF = '05'
26
DEFAULT_MESSAGE = 'ENTROUVERT 0143350135 CALEBASSE 1307'
27

    
28
# B2 informations / configuration
29
# from settings.py :
30
# B2_TRANSMISSION = {
31
#     'nom': 'CMPP FOOBAR',
32
#     'numero_emetteur': '123456789',
33
#     'smtp_from': 'transmission@domaine.fr',
34
#     ...
35
# }
36

    
37
try:
38
    from django.conf import settings
39
    b2_transmission_settings = settings.B2_TRANSMISSION or {}
40
except (ImportError, AttributeError):
41
    b2_transmission_settings = {}
42

    
43
# B2 informations
44
NORME = b2_transmission_settings.get('norme', DEFAULT_NORME)
45

    
46
TYPE_EMETTEUR = b2_transmission_settings.get('type_emetteur', DEFAULT_TYPE_EMETTEUR)
47
NUMERO_EMETTEUR = b2_transmission_settings.get('numero_emetteur')
48
APPLICATION = b2_transmission_settings.get('application', DEFAULT_APPLICATION)
49

    
50
CATEGORIE = b2_transmission_settings.get('categorie', DEFAULT_CATEGORIE)
51
STATUT = b2_transmission_settings.get('statut', DEFAULT_STATUT)
52
MODE_TARIF = b2_transmission_settings.get('mode_tarif', DEFAULT_MODE_TARIF)
53

    
54
NOM = b2_transmission_settings.get('nom', '')[:40]
55
NOM = NOM + ' '*(40-len(NOM))
56

    
57
MESSAGE = b2_transmission_settings.get('message', DEFAULT_MESSAGE)[:37]
58
MESSAGE = MESSAGE + ' '*(37-len(MESSAGE))
59

    
60
# b2 output
61
OUTPUT_DIRECTORY = b2_transmission_settings.get('output_directory', DEFAULT_OUTPUT_DIRECTORY)
62

    
63
# mailing
64
SMTP_FROM = b2_transmission_settings.get('smtp_from')
65
SMTP_HOST = b2_transmission_settings.get('smtp_host', '127.0.0.1')
66
SMTP_PORT = b2_transmission_settings.get('smtp_port', 25)
67
SMTP_LOGIN = b2_transmission_settings.get('smtp_login')
68
SMTP_PASSWORD = b2_transmission_settings.get('smtp_password')
69
SMTP_DELAY = b2_transmission_settings.get('smtp_delay')
70

    
71
# if "smtp_debug_to" setting is present, send all B2 mails to this address
72
# instead of real ones (yy.xxx@xxx.yy.rss.fr) and output SMTP dialog
73
DEBUG_TO = b2_transmission_settings.get('smtp_debug_to')
74

    
75

    
76
def b2_is_configured():
77
    if 'nom' in b2_transmission_settings and \
78
            'numero_emetteur' in b2_transmission_settings and \
79
            'smtp_from' in b2_transmission_settings:
80
        return True
81
    return False
82

    
83
def b2_output_directory():
84
    if not os.path.isdir(OUTPUT_DIRECTORY):
85
        raise IOError('B2 output directory (%s) is not a directory' % OUTPUT_DIRECTORY)
86
    if not os.access(OUTPUT_DIRECTORY, os.R_OK + os.W_OK + os.X_OK):
87
        raise IOError('B2 output directory (%s) is not accessible (rwx)' % OUTPUT_DIRECTORY)
88
    return OUTPUT_DIRECTORY
89

    
90
def filler(n, car=' '):
91
    return car*n
92
def filler0(n):
93
    return filler(n, '0')
94
def b2date(d):
95
    return d.strftime('%y%m%d')
96
def get_control_key(nir):
97
    try:
98
        # Corse dpt 2A et 2B
99
        minus = 0
100
        if nir[6] in ('A', 'a'):
101
            nir = [c for c in nir]
102
            nir[6] = '0'
103
            nir = ''.join(nir)
104
            minus = 1000000
105
        elif nir[6] in ('B', 'b'):
106
            nir = [c for c in nir]
107
            nir[6] = '0'
108
            nir = ''.join(nir)
109
            minus = 2000000
110
        nir = int(nir) - minus
111
        return '%0.2d' % (97 - (nir % 97))
112
    except Exception, e:
113
        return '00'
114

    
115

    
116
def write128(output_file, line):
117
    if len(line) != 128:
118
        raise RuntimeError('length of this B2 line is %d != 128 : "%s"' %
119
                (len(line), line))
120
    output_file.write(line)
121

    
122
def write_invoice(output_file, invoice):
123
    invoice_lines = 0
124
    start_date = invoice.start_date
125
    start_2 = '2' + NUMERO_EMETTEUR + ' ' + \
126
            invoice.policy_holder_social_security_id + \
127
            get_control_key(invoice.policy_holder_social_security_id) + \
128
            '000' + ('%0.9d' % invoice.number) + \
129
            '1' + ('%0.9d' % invoice.patient_id) + \
130
            invoice.policy_holder_healthcenter.large_regime.code + \
131
            invoice.policy_holder_healthcenter.dest_organism + \
132
            (invoice.policy_holder_other_health_center or '0000') + \
133
            '3' + b2date(start_date) + '000000' + \
134
            invoice.policy_holder_healthcenter.dest_organism + '000' + \
135
            '10' + '3' +  \
136
            b2date(start_date) + \
137
            '000000000' + ' ' + \
138
            b2date(invoice.patient_birthdate) + \
139
            ('%d' %  invoice.patient_twinning_rank)[-1:] + \
140
            b2date(start_date) + b2date(invoice.end_date) + '01' + \
141
            '00' + filler(10)
142
    write128(output_file, start_2)
143
    invoice_lines += 1
144
    nb_type3 = 0
145
    kind = invoice.first_tag[0]
146
    prestation = u'SNS  ' if kind == 'T' else u'SD   '
147
    for date in invoice.list_dates.split('$'):
148
        line_3 = '3' + NUMERO_EMETTEUR + ' ' + \
149
                invoice.policy_holder_social_security_id + \
150
                get_control_key(invoice.policy_holder_social_security_id) + \
151
                '000' + ('%0.9d' % invoice.number) + \
152
                '19' + '320' + \
153
                b2date(datetime.datetime.strptime(date, "%d/%m/%Y")) + \
154
                b2date(datetime.datetime.strptime(date, "%d/%m/%Y")) + \
155
                prestation + '001' + \
156
                ' ' + '00100' +  ' ' + '00000' + \
157
                ('%0.7d' % invoice.ppa) + \
158
                ('%0.8d' % invoice.ppa) + \
159
                '100' + \
160
                ('%0.8d' % invoice.ppa) + \
161
                ('%0.8d' % invoice.ppa) + \
162
                '0000' + '000' + ' ' + filler(2) + ' ' + \
163
                ' ' + '0000000'
164
        write128(output_file, line_3)
165
        invoice_lines += 1
166
        nb_type3 += 1
167

    
168
    end_5 = '5' + NUMERO_EMETTEUR + ' ' + \
169
            invoice.policy_holder_social_security_id + \
170
            get_control_key(invoice.policy_holder_social_security_id) + \
171
            '000' + ('%0.9d' % invoice.number) + \
172
            ('%0.3d' % nb_type3) + \
173
            ('%0.8d' % invoice.amount) + \
174
            ('%0.8d' % invoice.amount) + \
175
            '00000000' + '00000000' + '00000000' + '00000000' + '00000000' + \
176
            filler(17) + \
177
            ('%0.8d' % invoice.amount) + \
178
            filler(4+2)
179
    write128(output_file, end_5)
180
    invoice_lines += 1
181

    
182
    return invoice_lines
183

    
184
def b2(seq_id, hc, batches, regenerate=False):
185
    to = hc.b2_000()
186
    total = sum(b.total for b in batches)
187
    first_batch = min(b.number for b in batches)
188

    
189
    output_dir = os.path.join(b2_output_directory(), '%s' % seq_id)
190
    if not os.path.isdir(output_dir):
191
        os.mkdir(output_dir)
192

    
193
    infos = {
194
            'seq_id': seq_id,
195
            'hc': u'%s' % hc,
196
            'hc_b2': to,
197
            'batches': [],
198
            'total': float(total)
199
            }
200

    
201
    # B2 veut un identifiant de fichier sur 6 caractères alphanum
202
    hexdigest = hashlib.sha256('%s%s%s%s%s' % (seq_id, first_batch, NUMERO_EMETTEUR, to, total)).hexdigest()
203
    file_id = base64.encodestring(hexdigest).upper()[0:6]
204
    prefix = '%s-%s-%s-%s-%s.' % (seq_id, NUMERO_EMETTEUR, to, first_batch, file_id)
205

    
206
    b2_filename = os.path.join(output_dir, prefix + 'b2')
207
    if os.path.isfile(b2_filename) and not regenerate:
208
        return None
209

    
210
    output_file = tempfile.NamedTemporaryFile(suffix='.b2tmp',
211
            prefix=prefix, dir=output_dir, delete=False)
212

    
213
    nb_lines = 0
214

    
215
    utcnow = datetime.datetime.utcnow()
216
    start_000 = '000' +  TYPE_EMETTEUR + '00000' + NUMERO_EMETTEUR + \
217
            filler(6) + to + filler(6) + APPLICATION + \
218
            file_id + b2date(utcnow) + NORME + 'B2' + filler(15) + \
219
            '128' + filler(6) + MESSAGE
220
    write128(output_file, start_000)
221
    nb_lines += 1
222
    nb_batches = 0
223

    
224
    for batch in batches:
225
        start_1 = '1' + NUMERO_EMETTEUR + filler(6) + \
226
                hc.dest_organism[0:3] + \
227
                ('%0.3d' % batch.number) + CATEGORIE + STATUT + MODE_TARIF + \
228
                NOM + 'B2' + b2date(utcnow) + ' ' + NORME[0:2] + \
229
                ' ' + '062007' + 'U' + filler(2+3+1+34)
230
        write128(output_file, start_1)
231
        nb_lines += 1
232

    
233
        infos['batches'].append({
234
            'batch': batch.number,
235
            'dest': '%s' % hc.dest_organism[0:3],
236
            'hc': u'%s' % batch.health_center,
237
            'total': float(batch.total),
238
            'number_of_invoices': batch.number_of_invoices,
239
            'number_of_acts': batch.number_of_acts
240
            })
241

    
242
        for i in batch.invoices:
243
            nb_lines += write_invoice(output_file, i)
244

    
245
        end_6 = '6' + NUMERO_EMETTEUR + \
246
                ('%0.3d' % batch.number_of_invoices) + \
247
                ('%0.4d' % batch.number_of_acts) + \
248
                ('%0.4d' % batch.number_of_invoices) + \
249
                ('%0.3d' % batch.number_of_invoices) + \
250
                ('%0.9d' % (batch.total * 100)) + \
251
                ('%0.9d' % (batch.total * 100)) + \
252
                '000000000' + ('%0.3d' % batch.number) + \
253
                filler(1+1+4+12+12+3+9+32)
254
        write128(output_file, end_6)
255
        nb_lines += 1
256
        nb_batches += 1
257

    
258
    if nb_lines > 990:
259
        raise
260

    
261
    end_999 = '999' +  TYPE_EMETTEUR + '00000' + NUMERO_EMETTEUR + \
262
            filler(6) + to + filler(6) + APPLICATION + \
263
            file_id + \
264
            ('%0.8d' % (nb_lines+1)) + \
265
            filler(19) + \
266
            ('%0.3d' % nb_batches) + \
267
            filler(43)
268
    write128(output_file, end_999)
269

    
270
    old_filename = output_file.name
271
    output_file.close()
272

    
273
    b2_filename = os.path.join(output_dir, prefix + 'b2')
274
    os.rename(old_filename, b2_filename)
275

    
276
    # create S/MIME mail
277
    fd = open(b2_filename + '-mail', 'w')
278
    fd.write(build_mail(hc.large_regime.code, hc.dest_organism, b2_filename))
279
    fd.close()
280

    
281
    # create info file (json)
282
    basename = os.path.basename(b2_filename)
283
    infos['files'] = {
284
            'b2': basename,
285
            'self': basename + '-info',
286
            'mail': basename + '-mail'
287
            }
288
    fd = open(b2_filename + '-info', 'w')
289
    json.dump(infos, fd, sort_keys=True, indent=4, separators=(',', ': '))
290
    fd.close()
291

    
292
    return b2_filename
293

    
294
def buildall(seq_id, regenerate=False):
295
    try:
296
        invoicing = Invoicing.objects.filter(seq_id=seq_id)[0]
297
    except IndexError:
298
        raise RuntimeError('Facturation introuvable')
299
    batches = build_batches(invoicing)
300
    for hc in batches:
301
        for b in batches[hc]:
302
            b2_filename = b2(invoicing.seq_id, hc, [b],
303
                    regenerate=regenerate)
304
            if b2_filename:
305
                print "%s created" % b2_filename
306

    
307

    
308
def sendmail_raw(mail):
309
    if DEBUG_TO:
310
        toaddr = DEBUG_TO
311
        print '(debug mode, sending to', toaddr, ')'
312
    else:
313
        toaddr = re.search('\nTo: +(.*)\n', mail, re.MULTILINE).group(1)
314

    
315
    smtp = SMTP(SMTP_HOST, SMTP_PORT)
316
    if DEBUG_TO:
317
        smtp.set_debuglevel(1)
318
    smtp.ehlo()
319
    if SMTP_LOGIN and SMTP_PASSWORD:
320
        smtp.starttls()
321
        smtp.ehlo()
322
        smtp.login(SMTP_LOGIN, SMTP_PASSWORD)
323
    smtp.sendmail(SMTP_FROM, toaddr, mail)
324
    smtp.close()
325
    return toaddr, "%s:%s" % (SMTP_HOST, SMTP_PORT)
326

    
327
def sendmail(seq_id, oneb2=None):
328
    output_dir = os.path.join(b2_output_directory(), '%s' % seq_id)
329
    if oneb2:
330
        filename = os.path.join(output_dir, oneb2 + '-mail')
331
        if os.path.isfile(filename + '-sent'): # resent
332
            os.rename(filename + '-sent', filename)
333
        filenames = [filename]
334
    else:
335
        filenames = glob.glob(os.path.join(output_dir, '*.b2-mail'))
336
    for mail_filename in filenames:
337
        log = open(mail_filename + '.log', 'a')
338
        log.write('%s mail %s\n' % (datetime.datetime.now(), os.path.basename(mail_filename)))
339
        mail = open(mail_filename).read()
340
        try:
341
            to, via = sendmail_raw(mail)
342
        except SMTPException as e:
343
            log.write('%s SMTP ERROR: %s\n' % (datetime.datetime.now(), e))
344
        else:
345
            log.write('%s OK, MAIL SENT TO %s VIA %s\n' % (datetime.datetime.now(), to, via))
346
            os.rename(mail_filename, mail_filename + '-sent')
347
            os.utime(mail_filename + '-sent', None) # touch
348
        log.close()
349
        if SMTP_DELAY:
350
            time.sleep(SMTP_DELAY) # Exchange, I love you.
351

    
352

    
353
def get_all_infos(seq_id):
354
    output_dir = os.path.join(b2_output_directory(), '%s' % seq_id)
355
    infos = []
356
    for mail_filename in glob.glob(os.path.join(output_dir, '*.b2-info')):
357
        fd = open(mail_filename, 'r')
358
        info = json.load(fd)
359
        stats = os.stat(os.path.join(output_dir, info['files']['b2']))
360
        info['creation_date'] = datetime.datetime.fromtimestamp(stats.st_mtime)
361
        try:
362
            stats = os.stat(os.path.join(output_dir, info['files']['mail'] + '-sent'))
363
            info['mail_date'] = datetime.datetime.fromtimestamp(stats.st_mtime)
364
        except:
365
            pass
366
        try:
367
            fd = open(os.path.join(output_dir, info['files']['mail'] + '.log'), 'r')
368
            info['mail_log'] = fd.read()
369
            fd.close()
370
        except:
371
            pass
372
        infos.append(info)
373
        fd.close()
374
    return infos
(3-3/16)