Generar PDF

This commit is contained in:
Mauricio Baeza 2017-10-15 17:20:20 -05:00
parent 981fdba5f4
commit 09e3b46eca
3 changed files with 281 additions and 63 deletions

View File

@ -9,3 +9,4 @@ bcrypt
python-dateutil
zeep
chardet
pyqrcode

View File

@ -18,7 +18,9 @@ from xml.etree import ElementTree as ET
import uno
from com.sun.star.beans import PropertyValue
from com.sun.star.awt import Size
import pyqrcode
from dateutil import parser
from .helper import CaseInsensitiveDict, NumLet
@ -538,6 +540,16 @@ class LIBO(object):
self._sd.SearchCaseSensitive = False
return
def _next_cell(self, cell):
col = cell.getCellAddress().Column
row = cell.getCellAddress().Row + 1
return self._sheet.getCellByPosition(col, row)
def _copy_cell(self, cell):
destino = self._next_cell(cell)
self._sheet.copyRange(destino.getCellAddress(), cell.getRangeAddress())
return destino
def _set_cell(self, k='', v=None, cell=None, value=False):
if k:
self._sd.setSearchString(k)
@ -575,30 +587,111 @@ class LIBO(object):
self._set_cell('{cfdi.%s}' % k, v)
return
def _emisor(self, data):
for k, v in data.items():
self._set_cell('{emisor.%s}' % k, v)
return
def _receptor(self, data):
for k, v in data.items():
self._set_cell('{receptor.%s}' % k, v)
return
def _conceptos(self, data):
first = True
for concepto in data:
key = concepto.get('noidentificacion', '')
description = concepto['descripcion']
unidad = concepto['unidad']
cantidad = concepto['cantidad']
valor_unitario = concepto['valorunitario']
importe = concepto['importe']
if first:
first = False
cell_1 = self._set_cell('{noidentificacion}', key)
cell_2 = self._set_cell('{descripcion}', description)
cell_3 = self._set_cell('{unidad}', unidad)
cell_4 = self._set_cell('{cantidad}', cantidad, value=True)
cell_5 = self._set_cell('{valorunitario}', valor_unitario, value=True)
cell_6 = self._set_cell('{importe}', importe, value=True)
return
def _totales(self, data):
currency = data['moneda']
cell_title = self._set_cell('{subtotal.titulo}', 'SubTotal')
value = data['subtotal']
cell_value = self._set_cell('{subtotal}', value, value=True)
cell_value.CellStyle = currency
#~ Si encuentra el campo {total}, se asume que los totales e impuestos
#~ están declarados de forma independiente cada uno
#~ if self._add_totales(xml):
#~ return
#~ Si no se encuentra, copia las celdas hacia abajo de
#~ {subtotal.titulo} y {subtotal}
if 'descuento' in data:
self._copy_cell(cell_title)
self._copy_cell(cell_value)
cell_title = self._set_cell(v='Descuento', cell=cell_title)
value = data['descuento']
cell_value = self._set_cell(v=value, cell=cell_value, value=True)
cell_value.CellStyle = currency
for tax in data['traslados']:
self._copy_cell(cell_title)
self._copy_cell(cell_value)
cell_title = self._set_cell(v=tax[0], cell=cell_title)
cell_value = self._set_cell(v=tax[1], cell=cell_value, value=True)
cell_value.CellStyle = currency
for tax in data['retenciones']:
self._copy_cell(cell_title)
self._copy_cell(cell_value)
cell_title = self._set_cell(v=tax[0], cell=cell_title)
cell_value = self._set_cell(v=tax[1], cell=cell_value, value=True)
cell_value.CellStyle = currency
for tax in data['taxlocales']:
self._copy_cell(cell_title)
self._copy_cell(cell_value)
cell_title = self._set_cell(v=tax[0], cell=cell_title)
cell_value = self._set_cell(v=tax[1], cell=cell_value, value=True)
cell_value.CellStyle = currency
self._copy_cell(cell_title)
self._copy_cell(cell_value)
cell_title = self._set_cell(v='Total', cell=cell_title)
value = data['total']
cell_value = self._set_cell(v=value, cell=cell_value, value=True)
cell_value.CellStyle = currency
return
def _timbre(self, data):
for k, v in data.items():
self._set_cell('{timbre.%s}' % k, v)
#~ pd = self._sheet.getDrawPage()
#~ image = self._template.createInstance('com.sun.star.drawing.GraphicObjectShape')
#~ image.GraphicURL = data['path_cbb']
#~ pd.add(image)
#~ s = Size()
#~ s.Width = 4500
#~ s.Height = 4500
#~ image.setSize(s)
#~ image.Anchor = self._set_cell('{timbre.cbb}')
pd = self._sheet.getDrawPage()
image = self._template.createInstance('com.sun.star.drawing.GraphicObjectShape')
image.GraphicURL = data['path_cbb']
pd.add(image)
s = Size()
s.Width = 4250
s.Height = 4500
image.setSize(s)
image.Anchor = self._set_cell('{timbre.cbb}')
return
def _render(self, data):
self._set_search()
self._comprobante(data['comprobante'])
#~ self._emisor(data['emisor'])
#~ self._receptor(data['receptor'])
#~ self._conceptos(data['conceptos'])
self._emisor(data['emisor'])
self._receptor(data['receptor'])
self._conceptos(data['conceptos'])
self._totales(data['totales'])
self._timbre(data['timbre'])
self._cancelado(False)
#~ self._clean()
self._cancelado(data['cancelada'])
self._clean()
return
def pdf(self, path, data):
@ -609,9 +702,14 @@ class LIBO(object):
self._render(data)
path = '{}.ods'.format(tempfile.mkstemp()[1])
self._template.storeToURL(self._path_url(path), ())
doc = self._doc_open(path, {'Hidden': True})
options = {'FilterName': 'calc_pdf_Export'}
path = tempfile.mkstemp()[1]
self._template.storeToURL(self._path_url(path), self._set_properties(options))
doc.storeToURL(self._path_url(path), self._set_properties(options))
doc.close(True)
self._template.close(True)
return self._read(path)
@ -637,22 +735,23 @@ def get_dict(data):
def to_letters(value, moneda):
monedas = {
'MXN': 'peso',
'USD': 'dólar',
'EUR': 'euro',
}
return NumLet(value, monedas[moneda]).letras
def get_qr(data):
scale = 10
path = tempfile.mkstemp()[1]
code = QRCode(data, mode='binary')
code.png(path, scale)
qr = pyqrcode.create(data, mode='binary')
qr.png(path, scale=7)
return path
def _comprobante(values):
def _comprobante(values, options):
data = CaseInsensitiveDict(values)
del data['certificado']
#~ print (data)
data['totalenletras'] = to_letters(float(data['total']), data['moneda'])
if data['version'] == '3.3':
tipos = {
@ -662,7 +761,116 @@ def _comprobante(values):
}
data['tipodecomprobante'] = tipos.get(data['tipodecomprobante'])
data['lugarexpedicion'] = 'C.P. Expedición: {}'.format(data['lugarexpedicion'])
data['metododepago'] = options['metododepago']
data['formadepago'] = options['formadepago']
data['moneda'] = options['moneda']
data['tipocambio'] = 'Tipo de Cambio: $ {:0.2f}'.format(
float(data['tipocambio']))
return data
def _emisor(doc, version, values):
node = doc.find('{}Emisor'.format(PRE[version]))
data = CaseInsensitiveDict(node.attrib.copy())
node = node.find('{}DomicilioFiscal'.format(PRE[version]))
if not node is None:
data.update(node.attrib.copy())
data['regimenfiscal'] = values['regimenfiscal']
return data
def _receptor(doc, version, values):
node = doc.find('{}Receptor'.format(PRE[version]))
data = CaseInsensitiveDict(node.attrib.copy())
node = node.find('{}Domicilio'.format(PRE[version]))
if not node is None:
data.update(node.attrib.copy())
data['usocfdi'] = values['usocfdi']
return data
def _conceptos(doc, version):
data = []
conceptos = doc.find('{}Conceptos'.format(PRE[version]))
for c in conceptos.getchildren():
values = CaseInsensitiveDict(c.attrib.copy())
if version == '3.3':
values['noidentificacion'] = '{}\n(SAT {})'.format(
values['noidentificacion'], values['ClaveProdServ'])
values['unidad'] = '({}) {}'.format(
values['ClaveUnidad'], values['unidad'])
data.append(values)
return data
def _totales(doc, cfdi, version):
data = {}
data['moneda'] = doc.attrib['Moneda']
data['subtotal'] = cfdi['subtotal']
if 'descuento' in cfdi:
data['descuento'] = cfdi['descuento']
data['total'] = cfdi['total']
tn = {
'001': 'ISR',
'002': 'IVA',
}
traslados = []
retenciones = []
taxlocales = []
imp = doc.find('{}Impuestos'.format(PRE[version]))
if imp is not None:
tmp = CaseInsensitiveDict(imp.attrib.copy())
for k, v in tmp.items():
data[k] = v
node = imp.find('{}Traslados'.format(PRE[version]))
if node is not None:
for n in node.getchildren():
tmp = CaseInsensitiveDict(n.attrib.copy())
if version == '3.3':
title = 'Traslado {} {}'.format(
tn.get(tmp['impuesto']), tmp['tasaocuota'])
else:
title = 'Traslado {} {}'.format(tmp['impuesto'], tmp['tasa'])
traslados.append((title, float(tmp['importe'])))
node = imp.find('{}Retenciones'.format(PRE[version]))
if node is not None:
for n in node.getchildren():
tmp = CaseInsensitiveDict(n.attrib.copy())
if version == '3.3':
title = 'Retención {} {}'.format(
tn.get(tmp['impuesto']), '')
else:
title = 'Retención {} {}'.format(tmp['impuesto'], '')
retenciones.append((title, float(tmp['importe'])))
#~ com = xml.find('%sComplemento' % PRE)
#~ if com is not None:
#~ otros = com.find('%sImpuestosLocales' % IMP_LOCAL)
#~ if otros is not None:
#~ for otro in list(otros):
#~ if otro.tag == '%sRetencionesLocales' % IMP_LOCAL:
#~ name = 'ImpLocRetenido'
#~ tasa = 'TasadeRetencion'
#~ else:
#~ name = 'ImpLocTrasladado'
#~ tasa = 'TasadeTraslado'
#~ title = '%s %s %%' % (otro.attrib[name], otro.attrib[tasa])
#~ value = otro.attrib['Importe']
#~ self._copy_cell(cell_title)
#~ self._copy_cell(cell_value)
#~ cell_title = self._set_cell(v=title, cell=cell_title)
#~ cell_value = self._set_cell(v=value, cell=cell_value, value=True)
#~ cell_value.CellStyle = currency
data['traslados'] = traslados
data['retenciones'] = retenciones
data['taxlocales'] = taxlocales
return data
@ -679,23 +887,29 @@ def _timbre(doc, version, values):
values['rfc_receptor'],
total_s,
node.attrib['UUID'])
#~ data['path_cbb'] = get_qr(qr_data)
data['path_cbb'] = get_qr(qr_data)
data['cadenaoriginal'] = CADENA.format(**node.attrib)
return data
def get_data(invoice, rfc):
def get_data(invoice, rfc, values):
name = '{}_factura.ods'.format(rfc.lower())
path = get_path_template(name)
values = {}
data = {'cancelada': invoice.cancelada}
doc = parse_xml(invoice.xml)
data['comprobante'] = _comprobante(doc.attrib.copy())
data['comprobante'] = _comprobante(doc.attrib.copy(), values)
version = data['comprobante']['version']
values['rfc_emisor'] = '123'
values['rfc_receptor'] = '456'
values['total'] = data['comprobante']['total']
data['timbre'] = _timbre(doc, version, values)
data['emisor'] = _emisor(doc, version, values)
data['receptor'] = _receptor(doc, version, values)
data['conceptos'] = _conceptos(doc, version)
data['totales'] = _totales(doc, data['comprobante'], version)
options = {
'rfc_emisor': data['emisor']['rfc'],
'rfc_receptor': data['receptor']['rfc'],
'total': data['comprobante']['total'],
}
data['timbre'] = _timbre(doc, version, options)
return path, data

View File

@ -110,6 +110,9 @@ class SATRegimenes(BaseModel):
(('key', 'name'), True),
)
def __str__(self):
return '({}) {}'.format(self.key, self.name)
@classmethod
def get_(cls, ids):
if isinstance(ids, int):
@ -458,6 +461,9 @@ class SATFormaPago(BaseModel):
(('key', 'name'), True),
)
def __str__(self):
return 'Forma de pago: ({}) {}'.format(self.key, self.name)
@classmethod
def get_activos(cls, values):
field = SATFormaPago.id
@ -496,6 +502,9 @@ class SATMonedas(BaseModel):
(('key', 'name'), True),
)
def __str__(self):
return 'Moneda: ({}) {}'.format(self.key, self.name)
@classmethod
def get_activos(cls):
rows = (SATMonedas
@ -543,6 +552,9 @@ class SATUsoCfdi(BaseModel):
(('key', 'name'), True),
)
def __str__(self):
return 'Uso del CFDI: ({}) {}'.format(self.key, self.name)
@classmethod
def get_activos(cls, values):
field = SATUsoCfdi.id
@ -943,39 +955,6 @@ class Facturas(BaseModel):
def _get_data_cfdi_to_pdf(self, xml, cancel, version):
pre_nomina = PRE['NOMINA'][version]
data['comprobante']['letters'] = NumerosLetras().letters(
float(data['comprobante']['total'])).upper()
data['year'] = data['comprobante']['fecha'][0:4]
data['month'] = data['comprobante']['fecha'][5:7]
node = doc.find('{}Emisor'.format(pre))
data['emisor'] = node.attrib.copy()
rfc_emisor = data['emisor']['rfc']
node = node.find('{}DomicilioFiscal'.format(pre))
if not node is None:
data['emisor'].update(node.attrib.copy())
node = doc.find('{}Receptor'.format(pre))
data['receptor'] = node.attrib.copy()
rfc_receptor = data['receptor']['rfc']
node = node.find('{}Domicilio'.format(pre))
if not node is None:
data['receptor'].update(node.attrib.copy())
data['conceptos'] = []
conceptos = doc.find('{}Conceptos'.format(pre))
for c in conceptos.getchildren():
data['conceptos'].append(c.attrib.copy())
node = doc.find('{}Complemento/{}TimbreFiscalDigital'.format(pre, PRE['TIMBRE']))
data['timbre'] = node.attrib.copy()
total_s = '%017.06f' % float(doc.attrib['total'])
qr_data = '?re=%s&rr=%s&tt=%s&id=%s' % (
rfc_emisor, rfc_receptor, total_s, node.attrib['UUID'])
data['timbre']['path_cbb'] = get_qr(node.attrib['UUID'], qr_data)
data['timbre']['cadenaoriginal'] = CADENA.format(**node.attrib)
data['nomina'] = {}
node = doc.find('{}Complemento/{}Nomina'.format(pre, pre_nomina))
if not node is None:
@ -1026,6 +1005,29 @@ class Facturas(BaseModel):
return data
def _get_not_in_xml(self, invoice):
values = {}
obj = SATRegimenes.get(SATRegimenes.key==invoice.regimen_fiscal)
values['regimenfiscal'] = str(obj)
obj = SATUsoCfdi.get(SATUsoCfdi.key==invoice.uso_cfdi)
values['usocfdi'] = str(obj)
mp = {
'PUE': 'Pago en una sola exhibición',
'PPD': 'Pago en parcialidades o diferido',
}
values['metododepago'] = 'Método de Pago: ({}) {}'.format(
invoice.metodo_pago, mp[invoice.metodo_pago])
obj = SATFormaPago.get(SATFormaPago.key==invoice.forma_pago)
values['formadepago'] = str(obj)
obj = SATMonedas.get(SATMonedas.key==invoice.moneda)
values['moneda'] = str(obj)
return values
@classmethod
def get_pdf(cls, id, rfc):
obj = Facturas.get(Facturas.id==id)
@ -1033,7 +1035,8 @@ class Facturas(BaseModel):
if obj.uuid is None:
return b'', name
path, data = util.get_data(obj, rfc)
values = cls._get_not_in_xml(cls, obj)
path, data = util.get_data(obj, rfc, values)
doc = util.to_pdf(path, data)
return doc, name