Revision fa500e83
Added by Benjamin Dauvergne almost 13 years ago
| calebasse/facturation/invoice_header.py | ||
|---|---|---|
|
# -*- coding: utf-8 -*-
|
||
|
import os
|
||
|
import os.path
|
||
|
import tempfile
|
||
|
import datetime
|
||
| ... | ... | |
|
from collections import defaultdict
|
||
|
|
||
|
from xhtml2pdf.pisa import CreatePDF
|
||
|
from django.template.loader import render_to_string
|
||
|
from django.template import loader, Context
|
||
|
|
||
|
from invoice_template import InvoiceTemplate
|
||
|
from ..pdftk import PdfTk
|
||
|
|
||
|
|
||
|
class Batch(object):
|
||
|
def __init__(self, number, invoices):
|
||
|
self.number = number
|
||
| ... | ... | |
|
self.start_date = min(invoice.start_date for invoice in invoices)
|
||
|
self.end_date = max(invoice.end_date for invoice in invoices)
|
||
|
|
||
|
def render_to_pdf_file(template, ctx):
|
||
|
temp = tempfile.NamedTemporaryFile(delete=False)
|
||
|
html = render_to_string(template, ctx)
|
||
|
CreatePDF(html, temp)
|
||
|
temp.flush()
|
||
|
return temp.name
|
||
|
|
||
|
def header_file(service, batches,
|
||
|
header_template='facturation/bordereau.html'):
|
||
|
def render_to_pdf_file(templates, ctx, prefix='tmp', delete=False):
|
||
|
temp = tempfile.NamedTemporaryFile(prefix=prefix, suffix='.pdf',
|
||
|
delete=False)
|
||
|
try:
|
||
|
t = loader.select_template(templates)
|
||
|
html = t.render(Context(ctx))
|
||
|
CreatePDF(html, temp)
|
||
|
temp.flush()
|
||
|
return temp.name
|
||
|
except:
|
||
|
if delete:
|
||
|
try:
|
||
|
os.unlink(temp.name)
|
||
|
except:
|
||
|
pass
|
||
|
raise
|
||
|
|
||
|
|
||
|
def header_file(service, invoicing, health_center, batches,
|
||
|
header_service_template='facturation/bordereau-%s.html',
|
||
|
header_template='facturation/bordereau.html',
|
||
|
delete=False):
|
||
|
synthesis = {
|
||
|
'total': sum(batch.total for batch in batches),
|
||
|
'number_of_acts': sum(batch.number_of_acts for batch in batches),
|
||
|
'number_of_invoices': sum(batch.number_of_invoices for batch in batches),
|
||
|
}
|
||
|
ctx = {
|
||
|
'now': datetime.datetime.now(),
|
||
|
'health_center': health_center,
|
||
|
'service': service,
|
||
|
'batches': batches,
|
||
|
'synthesis': synthesis,
|
||
|
}
|
||
|
return render_to_pdf_file(header_template, ctx)
|
||
|
prefix = '%s-invoicing-%s-healthcenter-%s-' % (
|
||
|
service.slug, invoicing.id, health_center.id)
|
||
|
return render_to_pdf_file(
|
||
|
(header_service_template % service.slug,
|
||
|
header_template), ctx, prefix=prefix, delete=delete)
|
||
|
|
||
|
def invoice_files(service, batch, invoice):
|
||
|
|
||
|
def invoice_files(service, invoicing, batch, invoice):
|
||
|
template_path = os.path.join(
|
||
|
os.path.dirname(__file__),
|
||
|
'static',
|
||
|
'facturation',
|
||
|
'invoice.pdf')
|
||
|
tpl = InvoiceTemplate(template_path=template_path)
|
||
|
tpl = InvoiceTemplate(
|
||
|
template_path=template_path,
|
||
|
prefix='%s-invoicing-%s-invoice-%s-'
|
||
|
% ( service.slug, invoicing.id, invoice.id),
|
||
|
suffix='-%s.pdf' % datetime.datetime.now())
|
||
|
tpl.feed(InvoiceTemplate.NUM_FINESS, '420788606')
|
||
|
tpl.feed(InvoiceTemplate.IDENTIFICATION_ETABLISSEMENT,
|
||
|
'''%s SAINT ETIENNE
|
||
| ... | ... | |
|
# health_center.dest_organism,
|
||
|
# health_center.name)
|
||
|
# tpl.feed(InvoiceTemplate.CODE_ORGANISME, code_organisme)
|
||
|
tpl.feed(InvoiceTemplate.DATE_ENTREE, invoice.start_date.strftime('%d/%m/%Y'))
|
||
|
tpl.feed(InvoiceTemplate.DATE_SORTIE, invoice.end_date.strftime('%d/%m/%Y'))
|
||
|
if invoice.patient_entry_date is not None:
|
||
|
tpl.feed(InvoiceTemplate.DATE_ENTREE,
|
||
|
invoice.patient_entry_date.strftime('%d/%m/%Y'))
|
||
|
if invoice.patient_exit_date is not None:
|
||
|
tpl.feed(InvoiceTemplate.DATE_SORTIE,
|
||
|
invoice.patient_exit_date.strftime('%d/%m/%Y'))
|
||
|
tpl.feed(InvoiceTemplate.ABSENCE_SIGNATURE, True)
|
||
|
if invoice.policy_holder_id:
|
||
|
tpl.feed(InvoiceTemplate.NOM_ASSURE, u' '.join((
|
||
| ... | ... | |
|
prestation = u'SNS' if hc_tag.startswith('T') else u'SD'
|
||
|
d = act.date.strftime('%d/%m/%Y')
|
||
|
total1 += invoice.decimal_ppa
|
||
|
tpl.feed_line(u'19', u'320', prestation, d, d, invoice.decimal_ppa, 1, invoice.decimal_ppa)
|
||
|
tpl.feed_line(u'19', u'320', prestation, d, d, invoice.decimal_ppa, 1,
|
||
|
invoice.decimal_ppa)
|
||
|
for act in acts[12:24]:
|
||
|
hc_tag = act.get_hc_tag()
|
||
|
prestation = u'SNS' if hc_tag.startswith('T') else u'SD'
|
||
|
d = act.date.strftime('%d/%m/%Y')
|
||
|
total2 += invoice.decimal_ppa
|
||
|
tpl.feed_line(u'19', u'320', prestation, d, d, invoice.decimal_ppa, 1, invoice.decimal_ppa)
|
||
|
tpl.feed_line(u'19', u'320', prestation, d, d, invoice.decimal_ppa, 1,
|
||
|
invoice.decimal_ppa)
|
||
|
tpl.feed(InvoiceTemplate.SOUSTOTAL1, total1)
|
||
|
if total2 != Decimal(0):
|
||
|
tpl.feed(InvoiceTemplate.SOUSTOTAL2, total2)
|
||
|
assert invoice.decimal_amount == (total1+total2), "decimal_amount(%s) != total1+total2(%s), ppa: %s len(acts): %s" % (invoice.decimal_amount, total1+total2, invoice.ppa, len(acts))
|
||
|
assert invoice.decimal_amount == (total1+total2), "decimal_amount(%s) != " \
|
||
|
"total1+total2(%s), ppa: %s len(acts): %s" % (invoice.decimal_amount,
|
||
|
total1+total2, invoice.ppa, len(acts))
|
||
|
tpl.feed(InvoiceTemplate.TOTAL2, total1+total2)
|
||
|
return [tpl.generate()]
|
||
|
return [tpl.generate(flatten=True, wait=False)]
|
||
|
|
||
|
|
||
|
def build_batches(invoicing):
|
||
|
invoices = invoicing.invoice_set.order_by('number')
|
||
| ... | ... | |
|
batches_by_health_center[batch.health_center].append(batch)
|
||
|
return batches_by_health_center
|
||
|
|
||
|
def render_invoicing(service, invoicing):
|
||
|
|
||
|
def render_invoicing(service, invoicing, delete=False):
|
||
|
now = datetime.datetime.now()
|
||
|
batches_by_health_center = build_batches(invoicing)
|
||
|
centers = sorted(batches_by_health_center.keys())
|
||
|
files = []
|
||
|
for center in centers:
|
||
|
files.extend(batches_files(service, batches_by_health_center[center]))
|
||
|
output_file = tempfile.NamedTemporaryFile(delete=False)
|
||
|
pdftk = PdfTk()
|
||
|
pdftk.concat(files, output_file.name)
|
||
|
return output_file.name
|
||
|
all_files = []
|
||
|
all_others = []
|
||
|
output_file = None
|
||
|
try:
|
||
|
for center in centers:
|
||
|
files, others = batches_files(service, invoicing, center,
|
||
|
batches_by_health_center[center], delete=delete)
|
||
|
all_files.extend(files)
|
||
|
all_others.extend(others)
|
||
|
print 'all_files', all_files
|
||
|
output_file = tempfile.NamedTemporaryFile(prefix='%s-invoicing-%s-' %
|
||
|
(service.slug, invoicing.id), suffix='-%s.pdf' % now, delete=False)
|
||
|
pdftk = PdfTk()
|
||
|
pdftk.concat(all_files, output_file.name)
|
||
|
return output_file.name
|
||
|
except:
|
||
|
if delete and output_file:
|
||
|
try:
|
||
|
os.unlink(output_file.name)
|
||
|
except:
|
||
|
pass
|
||
|
raise
|
||
|
finally:
|
||
|
# eventual cleanup
|
||
|
if delete:
|
||
|
for path in all_files+all_others:
|
||
|
try:
|
||
|
os.unlink(path)
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
|
||
|
def batches_files(service, batches):
|
||
|
|
||
|
def batches_files(service, invoicing, health_center, batches, delete=False):
|
||
|
files = []
|
||
|
files.append(header_file(service, batches))
|
||
|
for batch in batches:
|
||
|
for invoice in batch.invoices:
|
||
|
files.extend(invoice_files(service, batch, invoice))
|
||
|
return files
|
||
|
procs = []
|
||
|
others = []
|
||
|
try:
|
||
|
files.append(header_file(service, invoicing, health_center, batches, delete=delete))
|
||
|
|
||
|
for batch in batches:
|
||
|
for invoice in batch.invoices:
|
||
|
for name, proc, temp_fdf in invoice_files(service, invoicing, batch,
|
||
|
invoice):
|
||
|
files.append(name)
|
||
|
procs.append(proc)
|
||
|
others.append(temp_fdf)
|
||
|
for proc in procs:
|
||
|
proc.wait()
|
||
|
return files, others
|
||
|
except:
|
||
|
# cleanup
|
||
|
if delete:
|
||
|
for path in files+others:
|
||
|
try:
|
||
|
os.unlink(path)
|
||
|
except:
|
||
|
pass
|
||
|
raise
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
import os
|
||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "calebasse.settings")
|
||
| calebasse/facturation/invoice_template.py | ||
|---|---|---|
|
# -*- coding: utf-8 -*-
|
||
|
|
||
|
import os
|
||
|
import tempfile
|
||
|
|
||
|
from calebasse.pdftk import PdfTk
|
||
| ... | ... | |
|
buttons = [ ABSENCE_SIGNATURE, PRISE_EN_CHARGE, ACCIDENT_CAUSE_TIERS,
|
||
|
PART_OBLIG, PART_COMPL ]
|
||
|
|
||
|
def __init__(self, template_path=None, flatten=False):
|
||
|
def __init__(self, template_path=None, prefix='tmp', suffix='', flatten=False):
|
||
|
self.prefix = prefix
|
||
|
self.suffix = suffix
|
||
|
self.template_path = template_path
|
||
|
self.fields = {}
|
||
|
self.flatten = False
|
||
| ... | ... | |
|
def get_template_path(self):
|
||
|
return self.template_path or 'template.pdf'
|
||
|
|
||
|
def generate(self, flatten=False):
|
||
|
def generate(self, flatten=False, wait=True, delete=False):
|
||
|
flatten = self.flatten or flatten
|
||
|
with tempfile.NamedTemporaryFile(delete=False) as temp_out_pdf:
|
||
|
pdftk = PdfTk()
|
||
|
pdftk.form_fill(self.get_template_path(), self.fields, temp_out_pdf.name, flatten=flatten)
|
||
|
return temp_out_pdf.name
|
||
|
with tempfile.NamedTemporaryFile(prefix=self.prefix,
|
||
|
suffix=self.suffix, delete=False) as temp_out_pdf:
|
||
|
try:
|
||
|
pdftk = PdfTk(prefix=self.prefix)
|
||
|
result = pdftk.form_fill(self.get_template_path(), self.fields, temp_out_pdf.name, flatten=flatten, wait=wait, delete=delete)
|
||
|
if wait:
|
||
|
return temp_out_pdf.name
|
||
|
else:
|
||
|
result, temp_out_fdf = result
|
||
|
return temp_out_pdf.name, result, temp_out_fdf
|
||
|
except:
|
||
|
if delete:
|
||
|
try:
|
||
|
os.unlink(temp_out_pdf.name)
|
||
|
except:
|
||
|
pass
|
||
|
raise
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
import sys
|
||
| calebasse/facturation/templates/facturation/bordereau.html | ||
|---|---|---|
|
<div id="title">Bordereau de facturation</div>
|
||
|
<div id="date">
|
||
|
<div>
|
||
|
Édité le 19/02/2013 à 15:56:56
|
||
|
Édité le {{ now|date:"d/m/Y" }} à {{ now|time:"H:i:s" }}
|
||
|
</div>
|
||
|
<div>
|
||
|
N° : FIXME-NUMBER
|
||
|
N°: 410{{ health_center.large_regime.code|stringformat:"02s" }}{{ health_center.code|stringformat:"03s" }}
|
||
|
</div>
|
||
|
</div>
|
||
|
<div id="header-box">
|
||
| ... | ... | |
|
<table style="padding: 3px; border: 0.5px solid black; height: 5cm;">
|
||
|
<tr style="padding-top: 3px; padding-bottom: 2px; line-height: 50%; background-color: #EEEEEE; margin-bottom: 3cm"><td> Établissement</td></tr>
|
||
|
<tr><td style="height: 5cm; display: block;">
|
||
|
{% block payee %}
|
||
|
{{ service.name }} SAINT ETIENNE</br>
|
||
|
Finess: 420788606 MT: 19 DMT: 320</br>
|
||
|
66/68, RUE MARENGO</br>
|
||
| ... | ... | |
|
</br>
|
||
|
</br>
|
||
|
</br>
|
||
|
{% endblock %}
|
||
|
</td></tr>
|
||
|
</table>
|
||
|
</div>
|
||
| ... | ... | |
|
<tr style="padding-top: 3px; padding-bottom: 2px; line-height: 50%; background-color: #EEEEEE;"><td>Caisse destinataire</td></tr>
|
||
|
<tr>
|
||
|
<td>
|
||
|
SNCF</br>
|
||
|
66/68, RUE MARENGO</br>
|
||
|
42000 SAINT-ETIENNE</br>
|
||
|
Tél.: xx.xx.xx.xx.xx</br>
|
||
|
<p>N°: FIXME-NUMERO</p>
|
||
|
<p>N° de compte: FIXME-NUMERO DE COMPTE</p>
|
||
|
{{ health_center.name }}<br/>
|
||
|
{{ health_center.address }}<br/>
|
||
|
{% if health_center.address_complement %}
|
||
|
{{ health_center.address_complement }}<br/>
|
||
|
{% endif %}
|
||
|
{{ health_center.zip_code }} {{ health_center.city }}<br/>
|
||
|
{% if health_center.phone %}
|
||
|
Tél.: {{ health_center.phone }}</br>
|
||
|
{% endif %}
|
||
|
<p>N° de compte: 410{{ health_center.large_regime.code|stringformat:"02s" }}{{ health_center.code|stringformat:"03s" }}</p>
|
||
|
</td></tr>
|
||
|
</table>
|
||
|
</div>
|
||
| ... | ... | |
|
<tr style="padding-top: 3px; padding-bottom: 2px; line-height: 50%; background-color: #EEEEEE;"><td>Banque de règlement</td></tr>
|
||
|
<tr>
|
||
|
<td>
|
||
|
{% block bank %}
|
||
|
Banque : FIXME-BANK</br>
|
||
|
RIB FIXME-RIB
|
||
|
{% endblock %}
|
||
|
</td>
|
||
|
</tr>
|
||
|
</table>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div id="content">
|
||
|
{% for batch in batches }}
|
||
|
{% for batch in batches %}
|
||
|
<div style="text-align: left;">
|
||
|
<h1>Lot n° : {{ batch.number }}</h1>
|
||
|
</div>
|
||
| ... | ... | |
|
{% for invoice in batch.invoices %}
|
||
|
<tr>
|
||
|
<td>{{ invoice.number }}</td>
|
||
|
<td>{{ invoice.patient.display_name }}</td>
|
||
|
<td>{{ invoice.patient.policy_holder.social_security_id }}</td>
|
||
|
<td></td>
|
||
|
<td>{{ invoice.start_date }}</td>
|
||
|
<td>{{ invoice.end_date }}</td>
|
||
|
<td>{{ invoice.patient_first_name}} {{ invoice.patient_last_name }}</td>
|
||
|
<td>{% firstof invoice.policy_holder_social_security_id invoice.patient_social_security_id %}</td>
|
||
|
<td>{{ invoice.kind }}</td>
|
||
|
<td>{{ invoice.start_date|date:"d/m/Y" }}</td>
|
||
|
<td>{{ invoice.end_date|date:"d/m/Y" }}</td>
|
||
|
<td>{{ invoice.decimal_amount|floatformat:2 }}</td>
|
||
|
</tr>
|
||
|
{% endfor %}
|
||
| ... | ... | |
|
</table>
|
||
|
<div style="text-align: right; border: none;">
|
||
|
<p><b>Lot n° : {{ batch.number }} [ {{ batch.number_of_invoices }} factures ] [ {{ batch.number_of_acts}} actes ] Total = {{ batch.total|floatformat:2 }} €</b></p>
|
||
|
|
||
|
</div>
|
||
|
{% endfor %}
|
||
|
<div style="text-align: right; border: none;">
|
||
|
<p><b>Synthèse caisse destinataire : [ 4 factures ] [ 7 actes ] Total = 1 169,03 euros</b></p>
|
||
|
<p><b>Synthèse caisse destinataire : [ {{ synthesis.number_of_invoices }} factures ] [ {{ synthesis.number_of_acts }} actes ] Total = {{ synthesis.total|floatformat:2 }} euros</b></p>
|
||
|
</div>
|
||
|
<table style="border: none;">
|
||
|
<tr><td style="width: 20cm"> </td>
|
||
|
<td style="padding: 3px; text-align:left; border: 1px solid black">Signature du directeur: Mme Serre</br>
|
||
|
<td style="padding: 3px; text-align:left; border: 1px solid black">
|
||
|
{% block signature %}
|
||
|
Signature du directeur: Mme Serre</br>
|
||
|
</br>
|
||
|
</br>
|
||
|
</br>
|
||
|
{% endblock %}
|
||
|
</td>
|
||
|
</tr>
|
||
|
</table>
|
||
| calebasse/pdftk.py | ||
|---|---|---|
|
import os
|
||
|
import subprocess
|
||
|
import tempfile
|
||
|
|
||
|
from fdfgen import forge_fdf
|
||
|
|
||
|
class PdfTk(object):
|
||
|
def __init__(self, pdftk_path=None):
|
||
|
def __init__(self, pdftk_path=None, prefix='tmp'):
|
||
|
self._pdftk_path = pdftk_path
|
||
|
self.prefix = prefix
|
||
|
|
||
|
@property
|
||
|
def pdftk_path(self):
|
||
|
return self._pdftk_path or '/usr/bin/pdftk'
|
||
|
|
||
|
def do(self, args):
|
||
|
def do(self, args, wait=True):
|
||
|
print 'do', args
|
||
|
args = [self.pdftk_path] + args
|
||
|
proc = subprocess.Popen(args)
|
||
|
return proc.wait()
|
||
|
if wait:
|
||
|
return proc.wait()
|
||
|
else:
|
||
|
return proc
|
||
|
|
||
|
def concat(self, input_files, output_file):
|
||
|
args = input_files + ['cat', 'output', output_file]
|
||
|
return self.do(args)
|
||
|
def concat(self, input_files, output_file, wait=True):
|
||
|
args = input_files + ['cat', 'output', output_file, 'compress']
|
||
|
return self.do(args, wait=wait)
|
||
|
|
||
|
def form_fill(self, pdf_file, fields, output_file, flatten=False):
|
||
|
def form_fill(self, pdf_file, fields, output_file, flatten=False, wait=True, delete=False):
|
||
|
string_fields = []
|
||
|
other_fields = []
|
||
|
# separate string from booleans
|
||
| ... | ... | |
|
string_fields.append((k, v))
|
||
|
else:
|
||
|
other_fields.append((k[0], k[1] if isinstance(v, bool) else v))
|
||
|
with tempfile.NamedTemporaryFile() as temp_fdf:
|
||
|
fdf = forge_fdf("", string_fields, other_fields, [], [])
|
||
|
temp_fdf.write(fdf)
|
||
|
temp_fdf.flush()
|
||
|
args = [pdf_file, 'fill_form', temp_fdf.name, 'output', output_file]
|
||
|
if flatten:
|
||
|
args.insert(3, 'flatten')
|
||
|
result = self.do(args)
|
||
|
temp_fdf.close()
|
||
|
return result
|
||
|
with tempfile.NamedTemporaryFile(delete=wait, prefix=self.prefix,
|
||
|
suffix='.fdf') as temp_fdf:
|
||
|
try:
|
||
|
fdf = forge_fdf("", string_fields, other_fields, [], [])
|
||
|
temp_fdf.write(fdf)
|
||
|
temp_fdf.flush()
|
||
|
args = [pdf_file, 'fill_form', temp_fdf.name, 'output', output_file]
|
||
|
if flatten:
|
||
|
args.append('flatten')
|
||
|
result = self.do(args, wait=wait)
|
||
|
temp_fdf.close()
|
||
|
if wait:
|
||
|
return result
|
||
|
else:
|
||
|
return result, temp_fdf.name
|
||
|
except:
|
||
|
if delete:
|
||
|
try:
|
||
|
os.unlink(temp_fdf.name)
|
||
|
except:
|
||
|
pass
|
||
|
raise
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
import sys
|
||
Also available in: Unified diff
facturation: finish pdf invoice generation, improve template