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 python-dateutil
zeep zeep
chardet chardet
pyqrcode

View File

@ -18,7 +18,9 @@ from xml.etree import ElementTree as ET
import uno import uno
from com.sun.star.beans import PropertyValue from com.sun.star.beans import PropertyValue
from com.sun.star.awt import Size
import pyqrcode
from dateutil import parser from dateutil import parser
from .helper import CaseInsensitiveDict, NumLet from .helper import CaseInsensitiveDict, NumLet
@ -538,6 +540,16 @@ class LIBO(object):
self._sd.SearchCaseSensitive = False self._sd.SearchCaseSensitive = False
return 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): def _set_cell(self, k='', v=None, cell=None, value=False):
if k: if k:
self._sd.setSearchString(k) self._sd.setSearchString(k)
@ -575,30 +587,111 @@ class LIBO(object):
self._set_cell('{cfdi.%s}' % k, v) self._set_cell('{cfdi.%s}' % k, v)
return 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): def _timbre(self, data):
for k, v in data.items(): for k, v in data.items():
self._set_cell('{timbre.%s}' % k, v) self._set_cell('{timbre.%s}' % k, v)
#~ pd = self._sheet.getDrawPage() pd = self._sheet.getDrawPage()
#~ image = self._template.createInstance('com.sun.star.drawing.GraphicObjectShape') image = self._template.createInstance('com.sun.star.drawing.GraphicObjectShape')
#~ image.GraphicURL = data['path_cbb'] image.GraphicURL = data['path_cbb']
#~ pd.add(image) pd.add(image)
#~ s = Size() s = Size()
#~ s.Width = 4500 s.Width = 4250
#~ s.Height = 4500 s.Height = 4500
#~ image.setSize(s) image.setSize(s)
#~ image.Anchor = self._set_cell('{timbre.cbb}') image.Anchor = self._set_cell('{timbre.cbb}')
return return
def _render(self, data): def _render(self, data):
self._set_search() self._set_search()
self._comprobante(data['comprobante']) self._comprobante(data['comprobante'])
#~ self._emisor(data['emisor']) self._emisor(data['emisor'])
#~ self._receptor(data['receptor']) self._receptor(data['receptor'])
#~ self._conceptos(data['conceptos']) self._conceptos(data['conceptos'])
self._totales(data['totales'])
self._timbre(data['timbre']) self._timbre(data['timbre'])
self._cancelado(False) self._cancelado(data['cancelada'])
#~ self._clean() self._clean()
return return
def pdf(self, path, data): def pdf(self, path, data):
@ -609,9 +702,14 @@ class LIBO(object):
self._render(data) 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'} options = {'FilterName': 'calc_pdf_Export'}
path = tempfile.mkstemp()[1] 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) self._template.close(True)
return self._read(path) return self._read(path)
@ -637,22 +735,23 @@ def get_dict(data):
def to_letters(value, moneda): def to_letters(value, moneda):
monedas = { monedas = {
'MXN': 'peso', 'MXN': 'peso',
'USD': 'dólar',
'EUR': 'euro',
} }
return NumLet(value, monedas[moneda]).letras return NumLet(value, monedas[moneda]).letras
def get_qr(data): def get_qr(data):
scale = 10
path = tempfile.mkstemp()[1] path = tempfile.mkstemp()[1]
code = QRCode(data, mode='binary') qr = pyqrcode.create(data, mode='binary')
code.png(path, scale) qr.png(path, scale=7)
return path return path
def _comprobante(values): def _comprobante(values, options):
data = CaseInsensitiveDict(values) data = CaseInsensitiveDict(values)
del data['certificado'] del data['certificado']
#~ print (data)
data['totalenletras'] = to_letters(float(data['total']), data['moneda']) data['totalenletras'] = to_letters(float(data['total']), data['moneda'])
if data['version'] == '3.3': if data['version'] == '3.3':
tipos = { tipos = {
@ -662,7 +761,116 @@ def _comprobante(values):
} }
data['tipodecomprobante'] = tipos.get(data['tipodecomprobante']) data['tipodecomprobante'] = tipos.get(data['tipodecomprobante'])
data['lugarexpedicion'] = 'C.P. Expedición: {}'.format(data['lugarexpedicion']) 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 return data
@ -679,23 +887,29 @@ def _timbre(doc, version, values):
values['rfc_receptor'], values['rfc_receptor'],
total_s, total_s,
node.attrib['UUID']) node.attrib['UUID'])
#~ data['path_cbb'] = get_qr(qr_data) data['path_cbb'] = get_qr(qr_data)
data['cadenaoriginal'] = CADENA.format(**node.attrib) data['cadenaoriginal'] = CADENA.format(**node.attrib)
return data return data
def get_data(invoice, rfc): def get_data(invoice, rfc, values):
name = '{}_factura.ods'.format(rfc.lower()) name = '{}_factura.ods'.format(rfc.lower())
path = get_path_template(name) path = get_path_template(name)
values = {}
data = {'cancelada': invoice.cancelada} data = {'cancelada': invoice.cancelada}
doc = parse_xml(invoice.xml) doc = parse_xml(invoice.xml)
data['comprobante'] = _comprobante(doc.attrib.copy()) data['comprobante'] = _comprobante(doc.attrib.copy(), values)
version = data['comprobante']['version'] version = data['comprobante']['version']
values['rfc_emisor'] = '123' data['emisor'] = _emisor(doc, version, values)
values['rfc_receptor'] = '456' data['receptor'] = _receptor(doc, version, values)
values['total'] = data['comprobante']['total'] data['conceptos'] = _conceptos(doc, version)
data['timbre'] = _timbre(doc, version, values) 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 return path, data

View File

@ -110,6 +110,9 @@ class SATRegimenes(BaseModel):
(('key', 'name'), True), (('key', 'name'), True),
) )
def __str__(self):
return '({}) {}'.format(self.key, self.name)
@classmethod @classmethod
def get_(cls, ids): def get_(cls, ids):
if isinstance(ids, int): if isinstance(ids, int):
@ -458,6 +461,9 @@ class SATFormaPago(BaseModel):
(('key', 'name'), True), (('key', 'name'), True),
) )
def __str__(self):
return 'Forma de pago: ({}) {}'.format(self.key, self.name)
@classmethod @classmethod
def get_activos(cls, values): def get_activos(cls, values):
field = SATFormaPago.id field = SATFormaPago.id
@ -496,6 +502,9 @@ class SATMonedas(BaseModel):
(('key', 'name'), True), (('key', 'name'), True),
) )
def __str__(self):
return 'Moneda: ({}) {}'.format(self.key, self.name)
@classmethod @classmethod
def get_activos(cls): def get_activos(cls):
rows = (SATMonedas rows = (SATMonedas
@ -543,6 +552,9 @@ class SATUsoCfdi(BaseModel):
(('key', 'name'), True), (('key', 'name'), True),
) )
def __str__(self):
return 'Uso del CFDI: ({}) {}'.format(self.key, self.name)
@classmethod @classmethod
def get_activos(cls, values): def get_activos(cls, values):
field = SATUsoCfdi.id field = SATUsoCfdi.id
@ -943,39 +955,6 @@ class Facturas(BaseModel):
def _get_data_cfdi_to_pdf(self, xml, cancel, version): def _get_data_cfdi_to_pdf(self, xml, cancel, version):
pre_nomina = PRE['NOMINA'][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'] = {} data['nomina'] = {}
node = doc.find('{}Complemento/{}Nomina'.format(pre, pre_nomina)) node = doc.find('{}Complemento/{}Nomina'.format(pre, pre_nomina))
if not node is None: if not node is None:
@ -1026,6 +1005,29 @@ class Facturas(BaseModel):
return data 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 @classmethod
def get_pdf(cls, id, rfc): def get_pdf(cls, id, rfc):
obj = Facturas.get(Facturas.id==id) obj = Facturas.get(Facturas.id==id)
@ -1033,7 +1035,8 @@ class Facturas(BaseModel):
if obj.uuid is None: if obj.uuid is None:
return b'', name 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) doc = util.to_pdf(path, data)
return doc, name return doc, name