Generar PDF
This commit is contained in:
parent
981fdba5f4
commit
09e3b46eca
|
@ -9,3 +9,4 @@ bcrypt
|
|||
python-dateutil
|
||||
zeep
|
||||
chardet
|
||||
pyqrcode
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue