Generar PDF
This commit is contained in:
parent
2f79bb290a
commit
981fdba5f4
|
@ -1,12 +1,225 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import falcon
|
||||
from models.main import get_cp
|
||||
#~ import falcon
|
||||
#~ from models.main import get_cp
|
||||
|
||||
|
||||
class AppPostalCode(object):
|
||||
#~ class AppPostalCode(object):
|
||||
|
||||
def on_get(self, req, resp):
|
||||
values = req.params
|
||||
req.context['result'] = get_cp(values['cp'])
|
||||
resp.status = falcon.HTTP_200
|
||||
#~ def on_get(self, req, resp):
|
||||
#~ values = req.params
|
||||
#~ req.context['result'] = get_cp(values['cp'])
|
||||
#~ resp.status = falcon.HTTP_200
|
||||
|
||||
#~ https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37
|
||||
|
||||
import re
|
||||
import collections
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
class CaseInsensitiveDict(collections.MutableMapping):
|
||||
"""A case-insensitive ``dict``-like object.
|
||||
Implements all methods and operations of
|
||||
``collections.MutableMapping`` as well as dict's ``copy``. Also
|
||||
provides ``lower_items``.
|
||||
All keys are expected to be strings. The structure remembers the
|
||||
case of the last key to be set, and ``iter(instance)``,
|
||||
``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()``
|
||||
will contain case-sensitive keys. However, querying and contains
|
||||
testing is case insensitive::
|
||||
cid = CaseInsensitiveDict()
|
||||
cid['Accept'] = 'application/json'
|
||||
cid['aCCEPT'] == 'application/json' # True
|
||||
list(cid) == ['Accept'] # True
|
||||
For example, ``headers['content-encoding']`` will return the
|
||||
value of a ``'Content-Encoding'`` response header, regardless
|
||||
of how the header name was originally stored.
|
||||
If the constructor, ``.update``, or equality comparison
|
||||
operations are given keys that have equal ``.lower()``s, the
|
||||
behavior is undefined.
|
||||
"""
|
||||
|
||||
def __init__(self, data=None, **kwargs):
|
||||
self._store = OrderedDict()
|
||||
if data is None:
|
||||
data = {}
|
||||
self.update(data, **kwargs)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
# Use the lowercased key for lookups, but store the actual
|
||||
# key alongside the value.
|
||||
self._store[key.lower()] = (key, value)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._store[key.lower()][1]
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._store[key.lower()]
|
||||
|
||||
def __iter__(self):
|
||||
return (casedkey for casedkey, mappedvalue in self._store.values())
|
||||
|
||||
def __len__(self):
|
||||
return len(self._store)
|
||||
|
||||
def lower_items(self):
|
||||
"""Like iteritems(), but with all lowercase keys."""
|
||||
return (
|
||||
(lowerkey, keyval[1])
|
||||
for (lowerkey, keyval)
|
||||
in self._store.items()
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, collections.Mapping):
|
||||
other = CaseInsensitiveDict(other)
|
||||
else:
|
||||
return NotImplemented
|
||||
# Compare insensitively
|
||||
return dict(self.lower_items()) == dict(other.lower_items())
|
||||
|
||||
# Copy is required
|
||||
def copy(self):
|
||||
return CaseInsensitiveDict(self._store.values())
|
||||
|
||||
def __repr__(self):
|
||||
return str(dict(self.items()))
|
||||
|
||||
|
||||
class NumLet(object):
|
||||
|
||||
def __init__(self, value, moneda, **args):
|
||||
self._letras = self._letters(value, moneda)
|
||||
|
||||
@property
|
||||
def letras(self):
|
||||
return self._letras.upper()
|
||||
|
||||
#~ def _letters(self, numero, moneda='peso', texto_inicial='-(',
|
||||
#~ texto_final='/100 m.n.)-', fraccion_letras=False, fraccion=''):
|
||||
def _letters(self, numero, moneda='peso'):
|
||||
|
||||
texto_inicial = ''
|
||||
texto_final = '/100 m.n.)-'
|
||||
fraccion_letras = False
|
||||
fraccion = ''
|
||||
|
||||
enletras = texto_inicial
|
||||
numero = abs(numero)
|
||||
numtmp = '%015d' % numero
|
||||
|
||||
if numero < 1:
|
||||
enletras += 'cero ' + self._plural(moneda) + ' '
|
||||
else:
|
||||
enletras += self._numlet(numero)
|
||||
if numero == 1 or numero < 2:
|
||||
enletras += moneda + ' '
|
||||
elif int(''.join(numtmp[3:])) == 0 or int(''.join(numtmp[9:])) == 0:
|
||||
enletras += 'de ' + self._plural(moneda) + ' '
|
||||
else:
|
||||
enletras += self._plural(moneda) + ' '
|
||||
|
||||
decimal = '%0.2f' % numero
|
||||
decimal = decimal.split('.')[1]
|
||||
#~ decimal = int((numero-int(numero))*100)
|
||||
if fraccion_letras:
|
||||
if decimal == 0:
|
||||
enletras += 'con cero ' + self._plural(fraccion)
|
||||
elif decimal == 1:
|
||||
enletras += 'con un ' + fraccion
|
||||
else:
|
||||
enletras += 'con ' + self._numlet(int(decimal)) + self.plural(fraccion)
|
||||
else:
|
||||
enletras += decimal
|
||||
|
||||
enletras += texto_final
|
||||
return enletras
|
||||
|
||||
def _numlet(self, numero):
|
||||
numtmp = '%015d' % numero
|
||||
co1=0
|
||||
letras = ''
|
||||
leyenda = ''
|
||||
for co1 in range(0,5):
|
||||
inicio = co1*3
|
||||
cen = int(numtmp[inicio:inicio+1][0])
|
||||
dec = int(numtmp[inicio+1:inicio+2][0])
|
||||
uni = int(numtmp[inicio+2:inicio+3][0])
|
||||
letra3 = self.centena(uni, dec, cen)
|
||||
letra2 = self.decena(uni, dec)
|
||||
letra1 = self.unidad(uni, dec)
|
||||
|
||||
if co1 == 0:
|
||||
if (cen+dec+uni) == 1:
|
||||
leyenda = 'billon '
|
||||
elif (cen+dec+uni) > 1:
|
||||
leyenda = 'billones '
|
||||
elif co1 == 1:
|
||||
if (cen+dec+uni) >= 1 and int(''.join(numtmp[6:9])) == 0:
|
||||
leyenda = "mil millones "
|
||||
elif (cen+dec+uni) >= 1:
|
||||
leyenda = "mil "
|
||||
elif co1 == 2:
|
||||
if (cen+dec) == 0 and uni == 1:
|
||||
leyenda = 'millon '
|
||||
elif cen > 0 or dec > 0 or uni > 1:
|
||||
leyenda = 'millones '
|
||||
elif co1 == 3:
|
||||
if (cen+dec+uni) >= 1:
|
||||
leyenda = 'mil '
|
||||
elif co1 == 4:
|
||||
if (cen+dec+uni) >= 1:
|
||||
leyenda = ''
|
||||
|
||||
letras += letra3 + letra2 + letra1 + leyenda
|
||||
letra1 = ''
|
||||
letra2 = ''
|
||||
letra3 = ''
|
||||
leyenda = ''
|
||||
return letras
|
||||
|
||||
def centena(self, uni, dec, cen):
|
||||
letras = ''
|
||||
numeros = ["","","doscientos ","trescientos ","cuatrocientos ","quinientos ","seiscientos ","setecientos ","ochocientos ","novecientos "]
|
||||
if cen == 1:
|
||||
if (dec+uni) == 0:
|
||||
letras = 'cien '
|
||||
else:
|
||||
letras = 'ciento '
|
||||
elif cen >= 2 and cen <= 9:
|
||||
letras = numeros[cen]
|
||||
return letras
|
||||
|
||||
def decena(self, uni, dec):
|
||||
letras = ''
|
||||
numeros = ["diez ","once ","doce ","trece ","catorce ","quince ","dieci","dieci","dieci","dieci"]
|
||||
decenas = ["","","","treinta ","cuarenta ","cincuenta ","sesenta ","setenta ","ochenta ","noventa "]
|
||||
if dec == 1:
|
||||
letras = numeros[uni]
|
||||
elif dec == 2:
|
||||
if uni == 0:
|
||||
letras = 'veinte '
|
||||
elif uni > 0:
|
||||
letras = 'veinti'
|
||||
elif dec >= 3 and dec <= 9:
|
||||
letras = decenas[dec]
|
||||
if uni > 0 and dec > 2:
|
||||
letras = letras+'y '
|
||||
return letras
|
||||
|
||||
def unidad(self, uni, dec):
|
||||
letras = ''
|
||||
numeros = ["","un ","dos ","tres ","cuatro ","cinco ","seis ","siete ","ocho ","nueve "]
|
||||
if dec != 1:
|
||||
if uni > 0 and uni <= 5:
|
||||
letras = numeros[uni]
|
||||
if uni >= 6 and uni <= 9:
|
||||
letras = numeros[uni]
|
||||
return letras
|
||||
|
||||
def _plural(self, palabra):
|
||||
if re.search('[aeiou]$', palabra):
|
||||
return re.sub('$', 's', palabra)
|
||||
else:
|
||||
return palabra + 'es'
|
||||
|
|
|
@ -206,8 +206,9 @@ class AppDocumentos(object):
|
|||
#~ self._not_json = True
|
||||
|
||||
def on_get(self, req, resp, type_doc, id_doc):
|
||||
session = req.env['beaker.session']
|
||||
req.context['result'], file_name, content_type = \
|
||||
self._db.get_doc(type_doc, id_doc)
|
||||
self._db.get_doc(type_doc, id_doc, session['rfc'])
|
||||
resp.append_header('Content-Disposition',
|
||||
'attachment; filename={}'.format(file_name))
|
||||
resp.content_type = content_type
|
||||
|
|
|
@ -8,15 +8,22 @@ import mimetypes
|
|||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import unicodedata
|
||||
import uuid
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
import uno
|
||||
from com.sun.star.beans import PropertyValue
|
||||
|
||||
from dateutil import parser
|
||||
|
||||
from .helper import CaseInsensitiveDict, NumLet
|
||||
from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \
|
||||
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL
|
||||
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PRE
|
||||
|
||||
|
||||
#~ def _get_hash(password):
|
||||
|
@ -165,6 +172,18 @@ def get_template(name, data={}):
|
|||
return template.render(**data)
|
||||
|
||||
|
||||
def get_path_template(name, default='plantilla_factura.ods'):
|
||||
path = _join(PATH_TEMPLATES, name)
|
||||
if is_file(path):
|
||||
return path
|
||||
|
||||
path = _join(PATH_TEMPLATES, default)
|
||||
if is_file(path):
|
||||
return path
|
||||
|
||||
return ''
|
||||
|
||||
|
||||
def dumps(data):
|
||||
return json.dumps(data, default=str)
|
||||
|
||||
|
@ -207,7 +226,7 @@ def to_slug(string):
|
|||
value = (unicodedata.normalize('NFKD', string)
|
||||
.encode('ascii', 'ignore')
|
||||
.decode('ascii').lower())
|
||||
return value
|
||||
return value.replace(' ', '_')
|
||||
|
||||
|
||||
class Certificado(object):
|
||||
|
@ -399,3 +418,284 @@ def timbra_xml(xml):
|
|||
result['uuid'] = pac.uuid
|
||||
result['fecha'] = pac.fecha
|
||||
return result
|
||||
|
||||
|
||||
class LIBO(object):
|
||||
HOST = 'localhost'
|
||||
PORT = '8100'
|
||||
ARG = 'socket,host={},port={};urp;StarOffice.ComponentContext'.format(
|
||||
HOST, PORT)
|
||||
|
||||
def __init__(self):
|
||||
self._app = None
|
||||
self._start_office()
|
||||
self._init_values()
|
||||
|
||||
def _init_values(self):
|
||||
self._ctx = None
|
||||
self._sm = None
|
||||
self._desktop = None
|
||||
if self.is_running:
|
||||
ctx = uno.getComponentContext()
|
||||
service = 'com.sun.star.bridge.UnoUrlResolver'
|
||||
resolver = ctx.ServiceManager.createInstanceWithContext(service, ctx)
|
||||
self._ctx = resolver.resolve('uno:{}'.format(self.ARG))
|
||||
self._sm = self._ctx.ServiceManager
|
||||
self._desktop = self._create_instance('com.sun.star.frame.Desktop')
|
||||
return
|
||||
|
||||
def _create_instance(self, name, with_context=True):
|
||||
if with_context:
|
||||
instance = self._sm.createInstanceWithContext(name, self._ctx)
|
||||
else:
|
||||
instance = self._sm.createInstance(name)
|
||||
return instance
|
||||
|
||||
@property
|
||||
def is_running(self):
|
||||
try:
|
||||
s = socket.create_connection((self.HOST, self.PORT), 5.0)
|
||||
s.close()
|
||||
return True
|
||||
except ConnectionRefusedError:
|
||||
return False
|
||||
|
||||
def _start_office(self):
|
||||
if self.is_running:
|
||||
return
|
||||
|
||||
c = 1
|
||||
while c < 4:
|
||||
c += 1
|
||||
self.app = subprocess.Popen([
|
||||
'soffice', '--headless', '--accept={}'.format(self.ARG)],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
time.sleep(5)
|
||||
if self.is_running:
|
||||
return
|
||||
return
|
||||
|
||||
def _set_properties(self, properties):
|
||||
pl = []
|
||||
for k, v in properties.items():
|
||||
pv = PropertyValue()
|
||||
pv.Name = k
|
||||
pv.Value = v
|
||||
pl.append(pv)
|
||||
return tuple(pl)
|
||||
|
||||
def _doc_open(self, path, options):
|
||||
options = self._set_properties(options)
|
||||
path = self._path_url(path)
|
||||
try:
|
||||
doc = self._desktop.loadComponentFromURL(path, '_blank', 0, options)
|
||||
return doc
|
||||
except:
|
||||
return None
|
||||
|
||||
def _path_url(self, path):
|
||||
if path.startswith('file://'):
|
||||
return path
|
||||
return uno.systemPathToFileUrl(path)
|
||||
|
||||
def close(self):
|
||||
if self.is_running:
|
||||
if not self._desktop is None:
|
||||
self._desktop.terminate()
|
||||
if not self._app is None:
|
||||
self._app.terminate()
|
||||
return
|
||||
|
||||
def _read(self, path):
|
||||
try:
|
||||
return open(path, 'rb').read()
|
||||
except:
|
||||
return b''
|
||||
|
||||
def _clean(self):
|
||||
self._sd.SearchRegularExpression = True
|
||||
self._sd.setSearchString("\{(\w.+)\}")
|
||||
self._search.replaceAll(self._sd)
|
||||
return
|
||||
|
||||
def _cancelado(self, cancel):
|
||||
if not cancel:
|
||||
pd = self._sheet.getDrawPage()
|
||||
if pd.getCount():
|
||||
pd.remove(pd.getByIndex(0))
|
||||
return
|
||||
|
||||
def _set_search(self):
|
||||
self._sheet = self._template.getSheets().getByIndex(0)
|
||||
self._search = self._sheet.getPrintAreas()[0]
|
||||
self._search = self._sheet.getCellRangeByPosition(
|
||||
self._search.StartColumn,
|
||||
self._search.StartRow,
|
||||
self._search.EndColumn,
|
||||
self._search.EndRow
|
||||
)
|
||||
self._sd = self._sheet.createSearchDescriptor()
|
||||
self._sd.SearchCaseSensitive = False
|
||||
return
|
||||
|
||||
def _set_cell(self, k='', v=None, cell=None, value=False):
|
||||
if k:
|
||||
self._sd.setSearchString(k)
|
||||
ranges = self._search.findAll(self._sd)
|
||||
if ranges:
|
||||
ranges = ranges.getRangeAddressesAsString().split(';')
|
||||
for r in ranges:
|
||||
for c in r.split(','):
|
||||
cell = self._sheet.getCellRangeByName(c)
|
||||
if v is None:
|
||||
return cell
|
||||
if cell.getImplementationName() == 'ScCellObj':
|
||||
pattern = re.compile(k, re.IGNORECASE)
|
||||
nv = pattern.sub(v, cell.getString())
|
||||
if value:
|
||||
cell.setValue(nv)
|
||||
else:
|
||||
cell.setString(nv)
|
||||
return cell
|
||||
if cell:
|
||||
if cell.getImplementationName() == 'ScCellObj':
|
||||
ca = cell.getCellAddress()
|
||||
new_cell = self._sheet.getCellByPosition(ca.Column, ca.Row + 1)
|
||||
if value:
|
||||
new_cell.setValue(v)
|
||||
else:
|
||||
new_cell.setString(v)
|
||||
return new_cell
|
||||
|
||||
def _comprobante(self, data):
|
||||
for k, v in data.items():
|
||||
if k in ('total', 'descuento', 'subtotal'):
|
||||
self._set_cell('{cfdi.%s}' % k, v, value=True)
|
||||
else:
|
||||
self._set_cell('{cfdi.%s}' % k, v)
|
||||
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}')
|
||||
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._timbre(data['timbre'])
|
||||
self._cancelado(False)
|
||||
#~ self._clean()
|
||||
return
|
||||
|
||||
def pdf(self, path, data):
|
||||
options = {'AsTemplate': True, 'Hidden': True}
|
||||
self._template = self._doc_open(path, options)
|
||||
if self._template is None:
|
||||
return b''
|
||||
|
||||
self._render(data)
|
||||
|
||||
options = {'FilterName': 'calc_pdf_Export'}
|
||||
path = tempfile.mkstemp()[1]
|
||||
self._template.storeToURL(self._path_url(path), self._set_properties(options))
|
||||
self._template.close(True)
|
||||
|
||||
return self._read(path)
|
||||
|
||||
|
||||
def to_pdf(path, data):
|
||||
app = LIBO()
|
||||
|
||||
if not app.is_running:
|
||||
return b''
|
||||
|
||||
return app.pdf(path, data)
|
||||
|
||||
|
||||
def parse_xml(xml):
|
||||
return ET.fromstring(xml)
|
||||
|
||||
|
||||
def get_dict(data):
|
||||
return CaseInsensitiveDict(data)
|
||||
|
||||
|
||||
def to_letters(value, moneda):
|
||||
monedas = {
|
||||
'MXN': 'peso',
|
||||
}
|
||||
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)
|
||||
return path
|
||||
|
||||
|
||||
def _comprobante(values):
|
||||
data = CaseInsensitiveDict(values)
|
||||
del data['certificado']
|
||||
#~ print (data)
|
||||
data['totalenletras'] = to_letters(float(data['total']), data['moneda'])
|
||||
if data['version'] == '3.3':
|
||||
tipos = {
|
||||
'I': 'ingreso',
|
||||
'E': 'egreso',
|
||||
'T': 'traslado',
|
||||
}
|
||||
data['tipodecomprobante'] = tipos.get(data['tipodecomprobante'])
|
||||
data['lugarexpedicion'] = 'C.P. Expedición: {}'.format(data['lugarexpedicion'])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def _timbre(doc, version, values):
|
||||
CADENA = '||{version}|{UUID}|{FechaTimbrado}|{selloCFD}|{noCertificadoSAT}||'
|
||||
if version == '3.3':
|
||||
CADENA = '||{Version}|{UUID}|{FechaTimbrado}|{SelloCFD}|{NoCertificadoSAT}||'
|
||||
node = doc.find('{}Complemento/{}TimbreFiscalDigital'.format(
|
||||
PRE[version], PRE['TIMBRE']))
|
||||
data = CaseInsensitiveDict(node.attrib.copy())
|
||||
total_s = '%017.06f' % float(values['total'])
|
||||
qr_data = '?re=%s&rr=%s&tt=%s&id=%s' % (
|
||||
values['rfc_emisor'],
|
||||
values['rfc_receptor'],
|
||||
total_s,
|
||||
node.attrib['UUID'])
|
||||
#~ data['path_cbb'] = get_qr(qr_data)
|
||||
data['cadenaoriginal'] = CADENA.format(**node.attrib)
|
||||
return data
|
||||
|
||||
|
||||
def get_data(invoice, rfc):
|
||||
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())
|
||||
version = data['comprobante']['version']
|
||||
values['rfc_emisor'] = '123'
|
||||
values['rfc_receptor'] = '456'
|
||||
values['total'] = data['comprobante']['total']
|
||||
data['timbre'] = _timbre(doc, version, values)
|
||||
|
||||
return path, data
|
||||
|
|
|
@ -115,9 +115,12 @@ class StorageEngine(object):
|
|||
def add_folios(self, values):
|
||||
return main.Folios.add(values)
|
||||
|
||||
def get_doc(self, type_doc, id):
|
||||
def get_doc(self, type_doc, id, rfc):
|
||||
if type_doc == 'xml':
|
||||
data, file_name = main.Facturas.get_xml(id)
|
||||
content_type = 'application.xml'
|
||||
content_type = 'application/xml'
|
||||
if type_doc == 'pdf':
|
||||
data, file_name = main.Facturas.get_pdf(id, rfc)
|
||||
content_type = 'application/pdf'
|
||||
return data, file_name, content_type
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ if __name__ == '__main__':
|
|||
|
||||
|
||||
from controllers import util
|
||||
from settings import log, VERSION, PATH_CP, COMPANIES
|
||||
from settings import log, VERSION, PATH_CP, COMPANIES, PRE
|
||||
|
||||
FORMAT = '{0:.2f}'
|
||||
|
||||
|
@ -928,6 +928,7 @@ class Facturas(BaseModel):
|
|||
regimen_fiscal = TextField(default='')
|
||||
notas = TextField(default='')
|
||||
pagada = BooleanField(default=False)
|
||||
cancelada = BooleanField(default=False)
|
||||
error = TextField(default='')
|
||||
|
||||
class Meta:
|
||||
|
@ -939,6 +940,103 @@ class Facturas(BaseModel):
|
|||
name = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc)
|
||||
return obj.xml, name
|
||||
|
||||
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:
|
||||
data['nomina']['nomina'] = node.attrib.copy()
|
||||
subnode = node.find('{}Emisor'.format(pre_nomina))
|
||||
if not subnode is None:
|
||||
data['emisor'].update(subnode.attrib.copy())
|
||||
subnode = node.find('{}Receptor'.format(pre_nomina))
|
||||
data['receptor'].update(subnode.attrib.copy())
|
||||
|
||||
subnode = node.find('{}Percepciones'.format(pre_nomina))
|
||||
data['nomina']['percepciones'] = subnode.attrib.copy()
|
||||
detalle = []
|
||||
for n in subnode.getchildren():
|
||||
if 'SeparacionIndemnizacion' in n.tag:
|
||||
continue
|
||||
detalle.append(n.attrib.copy())
|
||||
data['nomina']['percepciones']['detalle'] = detalle
|
||||
|
||||
data['nomina']['deducciones'] = None
|
||||
subnode = node.find('{}Deducciones'.format(pre_nomina))
|
||||
if not subnode is None:
|
||||
data['nomina']['deducciones'] = subnode.attrib.copy()
|
||||
detalle = []
|
||||
for n in subnode.getchildren():
|
||||
detalle.append(n.attrib.copy())
|
||||
data['nomina']['deducciones']['detalle'] = detalle
|
||||
|
||||
data['nomina']['incapacidades'] = None
|
||||
subnode = node.find('{}Incapacidades'.format(pre_nomina))
|
||||
if not subnode is None:
|
||||
detalle = []
|
||||
for n in subnode.getchildren():
|
||||
detalle.append(n.attrib.copy())
|
||||
data['nomina']['incapacidades'] = detalle
|
||||
|
||||
data['nomina']['otrospagos'] = None
|
||||
subnode = node.find('{}OtrosPagos'.format(pre_nomina))
|
||||
if not subnode is None:
|
||||
data['nomina']['otrospagos'] = subnode.attrib.copy()
|
||||
detalle = []
|
||||
for n in subnode.getchildren():
|
||||
detalle.append(n.attrib.copy())
|
||||
ns = n.find('{}SubsidioAlEmpleo'.format(pre_nomina))
|
||||
if not ns is None:
|
||||
data['nomina']['otrospagos']['SubsidioCausado'] = ns.attrib['SubsidioCausado']
|
||||
data['nomina']['otrospagos']['detalle'] = detalle
|
||||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def get_pdf(cls, id, rfc):
|
||||
obj = Facturas.get(Facturas.id==id)
|
||||
name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc)
|
||||
if obj.uuid is None:
|
||||
return b'', name
|
||||
|
||||
path, data = util.get_data(obj, rfc)
|
||||
doc = util.to_pdf(path, data)
|
||||
return doc, name
|
||||
|
||||
@classmethod
|
||||
def get_(cls, values):
|
||||
rows = tuple(Facturas
|
||||
|
|
|
@ -59,3 +59,17 @@ PATH_OPENSSL = 'openssl'
|
|||
if 'win' in sys.platform:
|
||||
PATH_XSLTPROC = os.path.join(PATH_BIN, 'xsltproc.exe')
|
||||
PATH_OPENSSL = os.path.join(PATH_BIN, 'openssl.exe')
|
||||
|
||||
|
||||
PRE = {
|
||||
'2.0': '{http://www.sat.gob.mx/cfd/2}',
|
||||
'2.2': '{http://www.sat.gob.mx/cfd/2}',
|
||||
'3.0': '{http://www.sat.gob.mx/cfd/3}',
|
||||
'3.2': '{http://www.sat.gob.mx/cfd/3}',
|
||||
'3.3': '{http://www.sat.gob.mx/cfd/3}',
|
||||
'TIMBRE': '{http://www.sat.gob.mx/TimbreFiscalDigital}',
|
||||
'NOMINA': {
|
||||
'1.1': '{http://www.sat.gob.mx/nomina}',
|
||||
'1.2': '{http://www.sat.gob.mx/nomina12}',
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ function get_series(){
|
|||
pre = values[0]
|
||||
$$('lst_serie').getList().parse(values)
|
||||
$$('lst_serie').setValue(pre.id)
|
||||
if(pre.usar_con){
|
||||
$$('lst_tipo_comprobante').setValue(pre.usar_con)
|
||||
if(pre.usarcon){
|
||||
$$('lst_tipo_comprobante').setValue(pre.usarcon)
|
||||
$$('lst_tipo_comprobante').config.readonly = true
|
||||
$$('lst_tipo_comprobante').refresh()
|
||||
}
|
||||
|
@ -644,8 +644,15 @@ function cmd_invoice_timbrar_click(){
|
|||
|
||||
function grid_invoices_click(id, e, node){
|
||||
var row = this.getItem(id)
|
||||
|
||||
if(id.column == 'xml'){
|
||||
location = '/doc/xml/' + row.id
|
||||
}else if(id.column == 'pdf'){
|
||||
location = '/doc/pdf/' + row.id
|
||||
}else if(id.column == 'zip'){
|
||||
location = '/doc/zip/' + row.id
|
||||
}else if(id.column == 'email'){
|
||||
show('Correo')
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -142,10 +142,10 @@ var suggest_products = {
|
|||
header: true,
|
||||
columns: [
|
||||
{id: 'id', hidden: true},
|
||||
{id: 'clave', adjust: 'data'},
|
||||
{id: 'descripcion', adjust: 'data'},
|
||||
{id: 'unidad', adjust: 'data'},
|
||||
{id: 'valor_unitario', adjust: 'data',
|
||||
{id: 'clave', header: 'Clave', adjust: 'data'},
|
||||
{id: 'descripcion', header: 'Descripción', adjust: 'data'},
|
||||
{id: 'unidad', header: 'Unidad', adjust: 'data'},
|
||||
{id: 'valor_unitario', header: 'Valor Unitario', adjust: 'data',
|
||||
format: webix.i18n.priceFormat}
|
||||
],
|
||||
dataFeed:function(text){
|
||||
|
|
Loading…
Reference in New Issue