commit
795aa2e50c
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## Mini ERP para la legislación mexicana
|
## Mini ERP para la legislación mexicana
|
||||||
|
|
||||||
Este proyecto esta en continuo desarrollo, contratar un esquema de soporte,
|
|
||||||
|
**En cada relación comercial, hay una relación humana**
|
||||||
|
|
||||||
|
|
||||||
|
Este proyecto está en continuo desarrollo, contratar un esquema de soporte,
|
||||||
nos ayuda a continuar su desarrollo. Ponte en contacto con nosotros para
|
nos ayuda a continuar su desarrollo. Ponte en contacto con nosotros para
|
||||||
contratar.
|
contratar.
|
||||||
|
|
|
@ -9,3 +9,6 @@ bcrypt
|
||||||
python-dateutil
|
python-dateutil
|
||||||
zeep
|
zeep
|
||||||
chardet
|
chardet
|
||||||
|
pyqrcode
|
||||||
|
pypng
|
||||||
|
reportlab
|
||||||
|
|
|
@ -1,12 +1,726 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import falcon
|
#~ import falcon
|
||||||
from models.main import get_cp
|
import re
|
||||||
|
import smtplib
|
||||||
|
import collections
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.mime.base import MIMEBase
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email import encoders
|
||||||
|
from email.utils import formatdate
|
||||||
|
|
||||||
|
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Image
|
||||||
|
from reportlab.lib import colors
|
||||||
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||||
|
from reportlab.lib.units import cm
|
||||||
|
from reportlab.lib.pagesizes import letter
|
||||||
|
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
|
||||||
|
from reportlab.platypus import Paragraph, Table, TableStyle, Spacer
|
||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
|
||||||
|
|
||||||
class AppPostalCode(object):
|
#~ https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37
|
||||||
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
class SendMail(object):
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
self._config = config
|
||||||
|
self._server = None
|
||||||
|
self._error = ''
|
||||||
|
self._is_connect = self._login()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_connect(self):
|
||||||
|
return self._is_connect
|
||||||
|
|
||||||
|
@property
|
||||||
|
def error(self):
|
||||||
|
return self._error
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
try:
|
||||||
|
if self._config['ssl']:
|
||||||
|
self._server = smtplib.SMTP_SSL(
|
||||||
|
self._config['servidor'],
|
||||||
|
self._config['puerto'], timeout=10)
|
||||||
|
else:
|
||||||
|
self._server = smtplib.SMTP(
|
||||||
|
self._config['servidor'],
|
||||||
|
self._config['puerto'], timeout=10)
|
||||||
|
self._server.login(self._config['usuario'], self._config['contra'])
|
||||||
|
return True
|
||||||
|
except smtplib.SMTPAuthenticationError as e:
|
||||||
|
if '535' in str(e):
|
||||||
|
self._error = 'Nombre de usuario o contraseña inválidos'
|
||||||
|
return False
|
||||||
|
if '534' in str(e) and 'gmail' in self._config['servidor']:
|
||||||
|
self._error = 'Necesitas activar el acceso a otras ' \
|
||||||
|
'aplicaciones en tu cuenta de GMail'
|
||||||
|
return False
|
||||||
|
except smtplib.SMTPException as e:
|
||||||
|
self._error = str(e)
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
self._error = str(e)
|
||||||
|
return False
|
||||||
|
return
|
||||||
|
|
||||||
|
def send(self, options):
|
||||||
|
try:
|
||||||
|
message = MIMEMultipart()
|
||||||
|
message['From'] = self._config['usuario']
|
||||||
|
message['To'] = options['para']
|
||||||
|
message['CC'] = options['copia']
|
||||||
|
message['Subject'] = options['asunto']
|
||||||
|
message['Date'] = formatdate(localtime=True)
|
||||||
|
message.attach(MIMEText(options['mensaje'], 'html'))
|
||||||
|
for f in options['files']:
|
||||||
|
part = MIMEBase('application', 'octet-stream')
|
||||||
|
part.set_payload(f[0])
|
||||||
|
encoders.encode_base64(part)
|
||||||
|
part.add_header(
|
||||||
|
'Content-Disposition',
|
||||||
|
"attachment; filename={}".format(f[1]))
|
||||||
|
message.attach(part)
|
||||||
|
|
||||||
|
receivers = options['para'].split(',') + options['copia'].split(',')
|
||||||
|
self._server.sendmail(
|
||||||
|
self._config['usuario'], receivers, message.as_string())
|
||||||
|
return ''
|
||||||
|
except Exception as e:
|
||||||
|
return str(e)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
try:
|
||||||
|
self._server.quit()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class NumberedCanvas(canvas.Canvas):
|
||||||
|
X = 20.59 * cm
|
||||||
|
Y = 1.5 * cm
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
canvas.Canvas.__init__(self, *args, **kwargs)
|
||||||
|
self._saved_page_states = []
|
||||||
|
|
||||||
|
def showPage(self):
|
||||||
|
self._saved_page_states.append(dict(self.__dict__))
|
||||||
|
self._startPage()
|
||||||
|
return
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""add page info to each page (page x of y)"""
|
||||||
|
page_count = len(self._saved_page_states)
|
||||||
|
for state in self._saved_page_states:
|
||||||
|
self.__dict__.update(state)
|
||||||
|
self.draw_page_number(page_count)
|
||||||
|
canvas.Canvas.showPage(self)
|
||||||
|
canvas.Canvas.save(self)
|
||||||
|
return
|
||||||
|
|
||||||
|
def draw_page_number(self, page_count):
|
||||||
|
self.setFont('Helvetica', 8)
|
||||||
|
self.setFillColor(colors.darkred)
|
||||||
|
text = 'Página {} de {}'.format(self._pageNumber, page_count)
|
||||||
|
self.drawRightString(self.X, self.Y, text)
|
||||||
|
text = 'Factura elaborada con software libre: www.empresalibre.net'
|
||||||
|
self.drawString(1.5 * cm, 1.5 * cm, text)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateInvoice(BaseDocTemplate):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# letter 21.6 x 27.9
|
||||||
|
kwargs['pagesize'] = letter
|
||||||
|
kwargs['rightMargin'] = 1 * cm
|
||||||
|
kwargs['leftMargin'] = 1 * cm
|
||||||
|
kwargs['topMargin'] = 1.5 * cm
|
||||||
|
kwargs['bottomMargin'] = 1.5 * cm
|
||||||
|
BaseDocTemplate.__init__(self, *args, **kwargs)
|
||||||
|
self._data = {}
|
||||||
|
self._rows = []
|
||||||
|
|
||||||
|
def _set_rect(self, style):
|
||||||
|
color = style.pop('color', 'black')
|
||||||
|
if isinstance(color, str):
|
||||||
|
self.canv.setFillColor(getattr(colors, color))
|
||||||
|
else:
|
||||||
|
self.canv.setFillColorRGB(*color)
|
||||||
|
|
||||||
|
keys = ('x', 'y', 'width', 'height', 'radius')
|
||||||
|
for k in keys:
|
||||||
|
style[k] = style[k] * cm
|
||||||
|
|
||||||
|
self.canv.roundRect(**style)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _set_text(self, styles, value):
|
||||||
|
text = styles.pop('valor', '')
|
||||||
|
if not value:
|
||||||
|
value = text
|
||||||
|
|
||||||
|
rect = styles['rectangulo']
|
||||||
|
if value:
|
||||||
|
self.canv.setFillColor(colors.white)
|
||||||
|
self.canv.setStrokeColor(colors.white)
|
||||||
|
ps = ParagraphStyle(**styles['estilo'])
|
||||||
|
p = Paragraph(value, ps)
|
||||||
|
p.wrap(rect['width'] * cm, rect['height'] * cm)
|
||||||
|
p.drawOn(self.canv, rect['x'] * cm, rect['y'] * cm)
|
||||||
|
|
||||||
|
self._set_rect(rect)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _emisor(self, styles, data):
|
||||||
|
logo_path = data.pop('logo', '')
|
||||||
|
logo_style = styles.pop('logo', {})
|
||||||
|
|
||||||
|
for k, v in styles.items():
|
||||||
|
self._set_text(styles[k], data.get(k, ''))
|
||||||
|
|
||||||
|
if logo_path and logo_style:
|
||||||
|
rect = logo_style['rectangulo']
|
||||||
|
keys = ('x', 'y', 'width', 'height')
|
||||||
|
for k in keys:
|
||||||
|
rect[k] = rect[k] * cm
|
||||||
|
self.canv.drawImage(logo_path, **rect)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _receptor(self, styles, data):
|
||||||
|
title = styles.pop('titulo', {})
|
||||||
|
|
||||||
|
for k, v in styles.items():
|
||||||
|
self._set_text(styles[k], data.get(k, ''))
|
||||||
|
|
||||||
|
if title:
|
||||||
|
rect = title['rectangulo']
|
||||||
|
self.canv.saveState()
|
||||||
|
self.canv.rotate(90)
|
||||||
|
value = title.pop('valor', '')
|
||||||
|
title['rectangulo']['x'], title['rectangulo']['y'] = \
|
||||||
|
title['rectangulo']['y'], title['rectangulo']['x'] * -1
|
||||||
|
self._set_text(title, value)
|
||||||
|
self.canv.restoreState()
|
||||||
|
return
|
||||||
|
|
||||||
|
def _comprobante1(self, styles, data):
|
||||||
|
title = styles.pop('titulo', {})
|
||||||
|
|
||||||
|
for k, v in styles.items():
|
||||||
|
self._set_text(styles[k], data.get(k, ''))
|
||||||
|
|
||||||
|
if title:
|
||||||
|
rect = title['rectangulo']
|
||||||
|
self.canv.saveState()
|
||||||
|
self.canv.rotate(90)
|
||||||
|
value = title.pop('valor', '')
|
||||||
|
title['rectangulo']['x'], title['rectangulo']['y'] = \
|
||||||
|
title['rectangulo']['y'], title['rectangulo']['x'] * -1
|
||||||
|
self._set_text(title, value)
|
||||||
|
self.canv.restoreState()
|
||||||
|
return
|
||||||
|
|
||||||
|
def afterPage(self):
|
||||||
|
encabezado = self._custom_styles['encabezado']
|
||||||
|
self.canv.saveState()
|
||||||
|
self._emisor(encabezado['emisor'], self._data['emisor'])
|
||||||
|
self._receptor(encabezado['receptor'], self._data['receptor'])
|
||||||
|
self._comprobante1(encabezado['comprobante'], self._data['comprobante'])
|
||||||
|
self.canv.restoreState()
|
||||||
|
return
|
||||||
|
|
||||||
|
def _currency(self, value, simbol='$'):
|
||||||
|
return '{} {:,.2f}'.format(simbol, float(value))
|
||||||
|
|
||||||
|
def _format(self, value, field):
|
||||||
|
fields = ('valorunitario', 'importe')
|
||||||
|
if field in fields:
|
||||||
|
return self._currency(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _conceptos(self, conceptos):
|
||||||
|
headers = (('Clave', 'Descripción', 'Unidad', 'Cantidad',
|
||||||
|
'Valor Unitario', 'Importe'),)
|
||||||
|
fields = ('noidentificacion', 'descripcion', 'unidad', 'cantidad',
|
||||||
|
'valorunitario', 'importe')
|
||||||
|
rows = []
|
||||||
|
for concepto in conceptos:
|
||||||
|
row = tuple([self._format(concepto[f], f) for f in fields])
|
||||||
|
rows.append(row)
|
||||||
|
return headers + tuple(rows)
|
||||||
|
|
||||||
|
def _totales(self, values):
|
||||||
|
#~ print (values)
|
||||||
|
rows = [('', 'Subtotal', self._currency(values['subtotal']))]
|
||||||
|
|
||||||
|
for tax in values['traslados']:
|
||||||
|
row = ('', tax[0], self._currency(tax[1]))
|
||||||
|
rows.append(row)
|
||||||
|
for tax in values['retenciones']:
|
||||||
|
row = ('', tax[0], self._currency(tax[1]))
|
||||||
|
rows.append(row)
|
||||||
|
for tax in values['taxlocales']:
|
||||||
|
row = ('', tax[0], self._currency(tax[1]))
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
|
row = ('', 'Total', self._currency(values['total']))
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
|
widths = [12.5 * cm, 4 * cm, 3 * cm]
|
||||||
|
table_styles = [
|
||||||
|
('GRID', (0, 0), (-1, -1), 0.05 * cm, colors.white),
|
||||||
|
('ALIGN', (1, 0), (-1, -1), 'RIGHT'),
|
||||||
|
('FONTSIZE', (0, 0), (-1, -1), 8),
|
||||||
|
('BACKGROUND', (1, 0), (-1, -1), colors.linen),
|
||||||
|
('TEXTCOLOR', (1, 0), (-1, -1), colors.darkred),
|
||||||
|
('FACE', (1, 0), (-1, -1), 'Helvetica-Bold'),
|
||||||
|
]
|
||||||
|
table = Table(rows, colWidths=widths, spaceBefore=0.25*cm)
|
||||||
|
table.setStyle(TableStyle(table_styles))
|
||||||
|
return table
|
||||||
|
|
||||||
|
def _comprobante2(self, styles, data):
|
||||||
|
leyenda = styles.pop('leyenda', {})
|
||||||
|
|
||||||
|
ls = []
|
||||||
|
for k, v in styles.items():
|
||||||
|
if k in data:
|
||||||
|
if 'spaceBefore' in v['estilo']:
|
||||||
|
v['estilo']['spaceBefore'] = v['estilo']['spaceBefore'] * cm
|
||||||
|
ps = ParagraphStyle(**v['estilo'])
|
||||||
|
p = Paragraph(data[k], ps)
|
||||||
|
ls.append(p)
|
||||||
|
|
||||||
|
cbb = Image(data['path_cbb'])
|
||||||
|
cbb.drawHeight = 4 * cm
|
||||||
|
cbb.drawWidth = 4 * cm
|
||||||
|
|
||||||
|
style_bt = getSampleStyleSheet()['BodyText']
|
||||||
|
style_bt.leading = 8
|
||||||
|
html_t = '<b><font size=6>{}</font></b>'
|
||||||
|
html = '<font color="darkred" size=5>{}</font>'
|
||||||
|
msg = 'Cadena original del complemento de certificación digital del SAT'
|
||||||
|
rows = [
|
||||||
|
(cbb, Paragraph(html_t.format('Sello Digital del CFDI'), style_bt)),
|
||||||
|
('', Paragraph(html.format(data['sellocfd']), style_bt)),
|
||||||
|
('', Paragraph(html_t.format('Sello Digital del SAT'), style_bt)),
|
||||||
|
('', Paragraph(html.format(data['sellosat']), style_bt)),
|
||||||
|
('', Paragraph(html_t.format(msg), style_bt)),
|
||||||
|
('', Paragraph(html.format(data['cadenaoriginal']), style_bt)),
|
||||||
|
]
|
||||||
|
|
||||||
|
widths = [4 * cm, 15.5 * cm]
|
||||||
|
table_styles = [
|
||||||
|
('FONTSIZE', (0, 0), (-1, -1), 6),
|
||||||
|
('SPAN', (0, 0), (0, -1)),
|
||||||
|
('FACE', (1, 0), (1, 0), 'Helvetica-Bold'),
|
||||||
|
('BACKGROUND', (1, 1), (1, 1), colors.linen),
|
||||||
|
('TEXTCOLOR', (1, 1), (1, 1), colors.darkred),
|
||||||
|
('FACE', (1, 2), (1, 2), 'Helvetica-Bold'),
|
||||||
|
('BACKGROUND', (1, 3), (1, 3), colors.linen),
|
||||||
|
('TEXTCOLOR', (1, 3), (1, 3), colors.darkred),
|
||||||
|
('FACE', (1, 4), (1, 4), 'Helvetica-Bold'),
|
||||||
|
('BACKGROUND', (1, 5), (1, 5), colors.linen),
|
||||||
|
('TEXTCOLOR', (1, 5), (1, 5), colors.darkred),
|
||||||
|
('ALIGN', (0, 0), (0, 0), 'CENTER'),
|
||||||
|
('VALIGN', (0, 0), (0, 0), 'MIDDLE'),
|
||||||
|
]
|
||||||
|
table = Table(rows, colWidths=widths)
|
||||||
|
table.setStyle(TableStyle(table_styles))
|
||||||
|
ls.append(table)
|
||||||
|
|
||||||
|
if leyenda:
|
||||||
|
if 'spaceBefore' in leyenda['estilo']:
|
||||||
|
leyenda['estilo']['spaceBefore'] = \
|
||||||
|
leyenda['estilo']['spaceBefore'] * cm
|
||||||
|
msg = 'Este documento es una representación impresa de un CFDI'
|
||||||
|
ps = ParagraphStyle(**leyenda['estilo'])
|
||||||
|
p = Paragraph(msg, ps)
|
||||||
|
ls.append(p)
|
||||||
|
|
||||||
|
return ls
|
||||||
|
|
||||||
|
@property
|
||||||
|
def custom_styles(self):
|
||||||
|
return self._custom_styles
|
||||||
|
@custom_styles.setter
|
||||||
|
def custom_styles(self, values):
|
||||||
|
self._custom_styles = values
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self):
|
||||||
|
return self._data
|
||||||
|
@data.setter
|
||||||
|
def data(self, values):
|
||||||
|
#~ print (values)
|
||||||
|
self._data = values
|
||||||
|
|
||||||
|
rows = self._conceptos(self._data['conceptos'])
|
||||||
|
widths = [2 * cm, 9 * cm, 1.5 * cm, 2 * cm, 2 * cm, 3 * cm]
|
||||||
|
table_styles = [
|
||||||
|
('GRID', (0, 0), (-1, -1), 0.05 * cm, colors.white),
|
||||||
|
('FONTSIZE', (0, 0), (-1, 0), 7),
|
||||||
|
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
||||||
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
||||||
|
('FACE', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||||
|
('BACKGROUND', (0, 0), (-1, 0), colors.darkred),
|
||||||
|
('FONTSIZE', (0, 1), (-1, -1), 7),
|
||||||
|
('VALIGN', (0, 1), (-1, -1), 'TOP'),
|
||||||
|
('ALIGN', (0, 1), (0, -1), 'CENTER'),
|
||||||
|
('ALIGN', (2, 1), (2, -1), 'CENTER'),
|
||||||
|
('ALIGN', (3, 1), (5, -1), 'RIGHT'),
|
||||||
|
('LINEBELOW', (0, 1), (-1, -1), 0.05 * cm, colors.darkred),
|
||||||
|
('LINEBEFORE', (0, 1), (-1, -1), 0.05 * cm, colors.white),
|
||||||
|
]
|
||||||
|
table_conceptos = Table(rows, colWidths=widths, repeatRows=1)
|
||||||
|
table_conceptos.setStyle(TableStyle(table_styles))
|
||||||
|
|
||||||
|
totales = self._totales(self.data['totales'])
|
||||||
|
comprobante = self._comprobante2(
|
||||||
|
self._custom_styles['comprobante'], self.data['comprobante'])
|
||||||
|
|
||||||
|
self._rows = [Spacer(0, 6*cm), table_conceptos, totales] + comprobante
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
frame = Frame(self.leftMargin, self.bottomMargin,
|
||||||
|
self.width, self.height, id='normal')
|
||||||
|
template = PageTemplate(id='invoice', frames=frame)
|
||||||
|
self.addPageTemplates([template])
|
||||||
|
self.build(self._rows, canvasmaker=NumberedCanvas)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class ReportTemplate(BaseDocTemplate):
|
||||||
|
"""Override the BaseDocTemplate class to do custom handle_XXX actions"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# letter 21.6 x 27.9
|
||||||
|
kwargs['pagesize'] = letter
|
||||||
|
kwargs['rightMargin'] = 1 * cm
|
||||||
|
kwargs['leftMargin'] = 1 * cm
|
||||||
|
kwargs['topMargin'] = 1.5 * cm
|
||||||
|
kwargs['bottomMargin'] = 1.5 * cm
|
||||||
|
BaseDocTemplate.__init__(self, *args, **kwargs)
|
||||||
|
self.styles = getSampleStyleSheet()
|
||||||
|
self.header = {}
|
||||||
|
self.data = []
|
||||||
|
|
||||||
|
def afterPage(self):
|
||||||
|
"""Called after each page has been processed"""
|
||||||
|
self.canv.saveState()
|
||||||
|
date = datetime.datetime.today().strftime('%A, %d de %B del %Y')
|
||||||
|
self.canv.setStrokeColorRGB(0.5, 0, 0)
|
||||||
|
self.canv.setFont("Helvetica", 8)
|
||||||
|
self.canv.drawRightString(20.59 * cm, 26.9 * cm, date)
|
||||||
|
self.canv.line(1 * cm, 26.4 * cm, 20.6 * cm, 26.4 * cm)
|
||||||
|
|
||||||
|
path_cur = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
path_img = os.path.join(path_cur, 'logo.png')
|
||||||
|
try:
|
||||||
|
self.canv.drawImage(path_img, 1 * cm, 24.2 * cm, 4 * cm, 2 * cm)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.canv.roundRect(
|
||||||
|
5 * cm, 25.4 * cm, 15.5 * cm, 0.6 * cm, 0.15 * cm,
|
||||||
|
stroke=True, fill=False)
|
||||||
|
self.canv.setFont('Helvetica-BoldOblique', 10)
|
||||||
|
self.canv.drawCentredString(12.75 * cm, 25.6 * cm, self.header['emisor'])
|
||||||
|
|
||||||
|
self.canv.roundRect(
|
||||||
|
5 * cm, 24.4 * cm, 15.5 * cm, 0.6 * cm, 0.15 * cm,
|
||||||
|
stroke=True, fill=False)
|
||||||
|
self.canv.setFont('Helvetica-BoldOblique', 9)
|
||||||
|
self.canv.drawCentredString(12.75 * cm, 24.6 * cm, self.header['title'])
|
||||||
|
|
||||||
|
self.canv.line(1 * cm, 1.5 * cm, 20.6 * cm, 1.5 * cm)
|
||||||
|
self.canv.restoreState()
|
||||||
|
return
|
||||||
|
|
||||||
|
def set_data(self, data):
|
||||||
|
self.header['emisor'] = data['emisor']
|
||||||
|
self.header['title'] = data['title']
|
||||||
|
cols = len(data['rows'][0])
|
||||||
|
widths = []
|
||||||
|
for w in data['widths']:
|
||||||
|
widths.append(float(w) * cm)
|
||||||
|
t_styles = [
|
||||||
|
('GRID', (0, 0), (-1, -1), 0.25, colors.darkred),
|
||||||
|
('FONTSIZE', (0, 0), (-1, 0), 9),
|
||||||
|
('BOX', (0, 0), (-1, 0), 1, colors.darkred),
|
||||||
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.black),
|
||||||
|
('FONTSIZE', (0, 1), (-1, -1), 8),
|
||||||
|
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
||||||
|
('ALIGN', (0, 0), (0, -1), 'RIGHT'),
|
||||||
|
]
|
||||||
|
if cols == 6:
|
||||||
|
t_styles += [
|
||||||
|
('ALIGN', (1, 1), (1, -1), 'CENTER'),
|
||||||
|
('ALIGN', (3, 1), (3, -1), 'CENTER'),
|
||||||
|
('ALIGN', (4, 1), (4, -1), 'RIGHT'),
|
||||||
|
]
|
||||||
|
elif cols == 3:
|
||||||
|
t_styles += [
|
||||||
|
('ALIGN', (-1, 0), (-1, -1), 'RIGHT'),
|
||||||
|
('ALIGN', (-2, 0), (-2, -1), 'RIGHT'),
|
||||||
|
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
||||||
|
]
|
||||||
|
elif cols == 2:
|
||||||
|
t_styles += [
|
||||||
|
('ALIGN', (-1, 0), (-1, -1), 'RIGHT'),
|
||||||
|
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
||||||
|
]
|
||||||
|
rows = []
|
||||||
|
for i, r in enumerate(data['rows']):
|
||||||
|
n = i + 1
|
||||||
|
rows.append(('{}.-'.format(n),) + r)
|
||||||
|
if cols == 6:
|
||||||
|
if r[4] == 'Cancelado':
|
||||||
|
t_styles += [
|
||||||
|
('GRID', (0, n), (-1, n), 0.25, colors.red),
|
||||||
|
('TEXTCOLOR', (0, n), (-1, n), colors.red),
|
||||||
|
]
|
||||||
|
rows.insert(0, data['titles'])
|
||||||
|
t = Table(rows, colWidths=widths, repeatRows=1)
|
||||||
|
t.setStyle(TableStyle(t_styles))
|
||||||
|
|
||||||
|
text = 'Total este reporte = $ {}'.format(data['total'])
|
||||||
|
ps = ParagraphStyle(
|
||||||
|
name='Total',
|
||||||
|
fontSize=12,
|
||||||
|
fontName='Helvetica-BoldOblique',
|
||||||
|
textColor=colors.darkred,
|
||||||
|
spaceBefore=0.5 * cm,
|
||||||
|
spaceAfter=0.5 * cm)
|
||||||
|
p1 = Paragraph(text, ps)
|
||||||
|
text = 'Nota: esta suma no incluye documentos cancelados'
|
||||||
|
ps = ParagraphStyle(
|
||||||
|
name='Note',
|
||||||
|
fontSize=7,
|
||||||
|
fontName='Helvetica-BoldOblique')
|
||||||
|
p2 = Paragraph(text, ps)
|
||||||
|
self.data = [t, p1, p2]
|
||||||
|
return
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
frame = Frame(self.leftMargin, self.bottomMargin,
|
||||||
|
self.width, self.height, id='normal')
|
||||||
|
template = PageTemplate(id='report', frames=frame)
|
||||||
|
self.addPageTemplates([template])
|
||||||
|
self.build(self.data, canvasmaker=NumberedCanvas)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def on_get(self, req, resp):
|
|
||||||
values = req.params
|
|
||||||
req.context['result'] = get_cp(values['cp'])
|
|
||||||
resp.status = falcon.HTTP_200
|
|
||||||
|
|
|
@ -76,12 +76,33 @@ class AppValues(object):
|
||||||
if file_object is None:
|
if file_object is None:
|
||||||
session = req.env['beaker.session']
|
session = req.env['beaker.session']
|
||||||
values = req.params
|
values = req.params
|
||||||
|
if table == 'correo':
|
||||||
|
req.context['result'] = self._db.validate_email(values)
|
||||||
|
elif table == 'sendmail':
|
||||||
|
req.context['result'] = self._db.send_email(values, session)
|
||||||
|
else:
|
||||||
req.context['result'] = self._db.validate_cert(values, session)
|
req.context['result'] = self._db.validate_cert(values, session)
|
||||||
else:
|
else:
|
||||||
req.context['result'] = self._db.add_cert(file_object)
|
req.context['result'] = self._db.add_cert(file_object)
|
||||||
resp.status = falcon.HTTP_200
|
resp.status = falcon.HTTP_200
|
||||||
|
|
||||||
|
|
||||||
|
class AppConfig(object):
|
||||||
|
|
||||||
|
def __init__(self, db):
|
||||||
|
self._db = db
|
||||||
|
|
||||||
|
def on_get(self, req, resp):
|
||||||
|
values = req.params
|
||||||
|
req.context['result'] = self._db.get_config(values)
|
||||||
|
resp.status = falcon.HTTP_200
|
||||||
|
|
||||||
|
def on_post(self, req, resp):
|
||||||
|
values = req.params
|
||||||
|
req.context['result'] = self._db.add_config(values)
|
||||||
|
resp.status = falcon.HTTP_200
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AppPartners(object):
|
class AppPartners(object):
|
||||||
|
|
||||||
|
@ -206,8 +227,9 @@ class AppDocumentos(object):
|
||||||
#~ self._not_json = True
|
#~ self._not_json = True
|
||||||
|
|
||||||
def on_get(self, req, resp, type_doc, id_doc):
|
def on_get(self, req, resp, type_doc, id_doc):
|
||||||
|
session = req.env['beaker.session']
|
||||||
req.context['result'], file_name, content_type = \
|
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',
|
resp.append_header('Content-Disposition',
|
||||||
'attachment; filename={}'.format(file_name))
|
'attachment; filename={}'.format(file_name))
|
||||||
resp.content_type = content_type
|
resp.content_type = content_type
|
||||||
|
|
|
@ -20,7 +20,10 @@ from zeep.cache import SqliteCache
|
||||||
from zeep.transports import Transport
|
from zeep.transports import Transport
|
||||||
from zeep.exceptions import Fault, TransportError
|
from zeep.exceptions import Fault, TransportError
|
||||||
|
|
||||||
from .configpac import DEBUG, TIMEOUT, AUTH, URL
|
if __name__ == '__main__':
|
||||||
|
from configpac import DEBUG, TIMEOUT, AUTH, URL
|
||||||
|
else:
|
||||||
|
from .configpac import DEBUG, TIMEOUT, AUTH, URL
|
||||||
|
|
||||||
|
|
||||||
log = Logger('PAC')
|
log = Logger('PAC')
|
||||||
|
@ -147,7 +150,7 @@ class Ecodex(object):
|
||||||
|
|
||||||
class Finkok(object):
|
class Finkok(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, auth={}):
|
||||||
self.codes = URL['codes']
|
self.codes = URL['codes']
|
||||||
self.error = ''
|
self.error = ''
|
||||||
self.message = ''
|
self.message = ''
|
||||||
|
@ -159,6 +162,9 @@ class Finkok(object):
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self._history = HistoryPlugin()
|
self._history = HistoryPlugin()
|
||||||
self._plugins = [self._history]
|
self._plugins = [self._history]
|
||||||
|
self._auth = AUTH
|
||||||
|
else:
|
||||||
|
self._auth = auth
|
||||||
|
|
||||||
def _debug(self):
|
def _debug(self):
|
||||||
if not DEBUG:
|
if not DEBUG:
|
||||||
|
@ -225,6 +231,11 @@ class Finkok(object):
|
||||||
|
|
||||||
def timbra_xml(self, file_xml):
|
def timbra_xml(self, file_xml):
|
||||||
self.error = ''
|
self.error = ''
|
||||||
|
|
||||||
|
if not DEBUG and not self._auth:
|
||||||
|
self.error = 'Sin datos para timbrar'
|
||||||
|
return
|
||||||
|
|
||||||
method = 'timbra'
|
method = 'timbra'
|
||||||
ok, xml = self._validate_xml(file_xml)
|
ok, xml = self._validate_xml(file_xml)
|
||||||
if not ok:
|
if not ok:
|
||||||
|
@ -233,8 +244,8 @@ class Finkok(object):
|
||||||
URL[method], transport=self._transport, plugins=self._plugins)
|
URL[method], transport=self._transport, plugins=self._plugins)
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
'username': AUTH['USER'],
|
'username': self._auth['USER'],
|
||||||
'password': AUTH['PASS'],
|
'password': self._auth['PASS'],
|
||||||
'xml': xml,
|
'xml': xml,
|
||||||
}
|
}
|
||||||
if URL['quick_stamp']:
|
if URL['quick_stamp']:
|
||||||
|
@ -261,8 +272,8 @@ class Finkok(object):
|
||||||
URL[method], transport=self._transport, plugins=self._plugins)
|
URL[method], transport=self._transport, plugins=self._plugins)
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
'username': AUTH['USER'],
|
'username': self._auth['USER'],
|
||||||
'password': AUTH['PASS'],
|
'password': self._auth['PASS'],
|
||||||
'uuid': uuid,
|
'uuid': uuid,
|
||||||
'taxpayer_id': self.rfc,
|
'taxpayer_id': self.rfc,
|
||||||
'invoice_type': 'I',
|
'invoice_type': 'I',
|
||||||
|
@ -296,7 +307,8 @@ class Finkok(object):
|
||||||
client = Client(
|
client = Client(
|
||||||
URL[method], transport=self._transport, plugins=self._plugins)
|
URL[method], transport=self._transport, plugins=self._plugins)
|
||||||
try:
|
try:
|
||||||
result = client.service.stamped(xml, AUTH['USER'], AUTH['PASS'])
|
result = client.service.stamped(
|
||||||
|
xml, self._auth['user'], self._auth['pass'])
|
||||||
except Fault as e:
|
except Fault as e:
|
||||||
self.error = str(e)
|
self.error = str(e)
|
||||||
return ''
|
return ''
|
||||||
|
@ -310,7 +322,8 @@ class Finkok(object):
|
||||||
client = Client(
|
client = Client(
|
||||||
URL[method], transport=self._transport, plugins=self._plugins)
|
URL[method], transport=self._transport, plugins=self._plugins)
|
||||||
try:
|
try:
|
||||||
result = client.service.query_pending(AUTH['USER'], AUTH['PASS'], uuid)
|
result = client.service.query_pending(
|
||||||
|
self._auth['USER'], self._auth['PASS'], uuid)
|
||||||
#~ print (result.date)
|
#~ print (result.date)
|
||||||
#~ tree = parseString(unescape(result.xml))
|
#~ tree = parseString(unescape(result.xml))
|
||||||
#~ response = tree.toprettyxml(encoding='utf-8').decode('utf-8')
|
#~ response = tree.toprettyxml(encoding='utf-8').decode('utf-8')
|
||||||
|
@ -334,8 +347,8 @@ class Finkok(object):
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
'UUIDS': uuid_type(uuids=sa(string=uuids)),
|
'UUIDS': uuid_type(uuids=sa(string=uuids)),
|
||||||
'username': AUTH['USER'],
|
'username': self._auth['USER'],
|
||||||
'password': AUTH['PASS'],
|
'password': self._auth['PASS'],
|
||||||
'taxpayer_id': rfc,
|
'taxpayer_id': rfc,
|
||||||
'cer': cer,
|
'cer': cer,
|
||||||
'key': key,
|
'key': key,
|
||||||
|
@ -366,8 +379,8 @@ class Finkok(object):
|
||||||
URL[method], transport=self._transport, plugins=self._plugins)
|
URL[method], transport=self._transport, plugins=self._plugins)
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
'username': AUTH['USER'],
|
'username': self._auth['USER'],
|
||||||
'password': AUTH['PASS'],
|
'password': self._auth['PASS'],
|
||||||
'xml': xml,
|
'xml': xml,
|
||||||
'store_pending': True,
|
'store_pending': True,
|
||||||
}
|
}
|
||||||
|
@ -385,8 +398,8 @@ class Finkok(object):
|
||||||
URL[method], transport=self._transport, plugins=self._plugins)
|
URL[method], transport=self._transport, plugins=self._plugins)
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
'username': AUTH['USER'],
|
'username': self._auth['USER'],
|
||||||
'password': AUTH['PASS'],
|
'password': self._auth['PASS'],
|
||||||
'taxpayer_id': rfc,
|
'taxpayer_id': rfc,
|
||||||
'uuid': '',
|
'uuid': '',
|
||||||
'type': type_acuse,
|
'type': type_acuse,
|
||||||
|
@ -413,8 +426,8 @@ class Finkok(object):
|
||||||
URL[method], transport=self._transport, plugins=self._plugins)
|
URL[method], transport=self._transport, plugins=self._plugins)
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
'username': AUTH['USER'],
|
'username': self._auth['USER'],
|
||||||
'password': AUTH['PASS'],
|
'password': self._auth['PASS'],
|
||||||
'uuid': '',
|
'uuid': '',
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -8,15 +8,32 @@ import mimetypes
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import time
|
||||||
import unicodedata
|
import unicodedata
|
||||||
import uuid
|
import uuid
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
from smtplib import SMTPException, SMTPAuthenticationError
|
||||||
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
|
try:
|
||||||
|
import uno
|
||||||
|
from com.sun.star.beans import PropertyValue
|
||||||
|
from com.sun.star.awt import Size
|
||||||
|
APP_LIBO = True
|
||||||
|
except:
|
||||||
|
APP_LIBO = False
|
||||||
|
|
||||||
|
import pyqrcode
|
||||||
from dateutil import parser
|
from dateutil import parser
|
||||||
|
|
||||||
|
from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice
|
||||||
from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \
|
from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \
|
||||||
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL
|
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE
|
||||||
|
|
||||||
|
|
||||||
#~ def _get_hash(password):
|
#~ def _get_hash(password):
|
||||||
|
@ -78,7 +95,23 @@ def get_value(arg):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _valid_db_companies():
|
||||||
|
con = sqlite3.connect(COMPANIES)
|
||||||
|
sql = """
|
||||||
|
CREATE TABLE IF NOT EXISTS names(
|
||||||
|
rfc TEXT NOT NULL COLLATE NOCASE UNIQUE,
|
||||||
|
con TEXT NOT NULL
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
cursor = con.cursor()
|
||||||
|
cursor.executescript(sql)
|
||||||
|
cursor.close()
|
||||||
|
con.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def _get_args(rfc):
|
def _get_args(rfc):
|
||||||
|
_valid_db_companies()
|
||||||
con = sqlite3.connect(COMPANIES)
|
con = sqlite3.connect(COMPANIES)
|
||||||
cursor = con.cursor()
|
cursor = con.cursor()
|
||||||
sql = "SELECT con FROM names WHERE rfc=?"
|
sql = "SELECT con FROM names WHERE rfc=?"
|
||||||
|
@ -94,6 +127,18 @@ def _get_args(rfc):
|
||||||
return values[0]
|
return values[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_rfcs():
|
||||||
|
_valid_db_companies()
|
||||||
|
con = sqlite3.connect(COMPANIES)
|
||||||
|
cursor = con.cursor()
|
||||||
|
sql = "SELECT * FROM names"
|
||||||
|
cursor.execute(sql)
|
||||||
|
values = cursor.fetchall()
|
||||||
|
cursor.close()
|
||||||
|
con.close()
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
def get_con(rfc=''):
|
def get_con(rfc=''):
|
||||||
if not rfc:
|
if not rfc:
|
||||||
rfc = get_value('RFC').upper()
|
rfc = get_value('RFC').upper()
|
||||||
|
@ -106,17 +151,6 @@ def get_con(rfc=''):
|
||||||
return loads(args)
|
return loads(args)
|
||||||
|
|
||||||
|
|
||||||
def get_rfcs():
|
|
||||||
con = sqlite3.connect(COMPANIES)
|
|
||||||
cursor = con.cursor()
|
|
||||||
sql = "SELECT * FROM names"
|
|
||||||
cursor.execute(sql)
|
|
||||||
values = cursor.fetchall()
|
|
||||||
cursor.close()
|
|
||||||
con.close()
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
def get_sat_key(table, key):
|
def get_sat_key(table, key):
|
||||||
con = sqlite3.connect(DB_SAT)
|
con = sqlite3.connect(DB_SAT)
|
||||||
cursor = con.cursor()
|
cursor = con.cursor()
|
||||||
|
@ -155,6 +189,10 @@ def get_file(path):
|
||||||
return open(path, 'rb')
|
return open(path, 'rb')
|
||||||
|
|
||||||
|
|
||||||
|
def read_file(path, mode='rb'):
|
||||||
|
return open(path, mode).read()
|
||||||
|
|
||||||
|
|
||||||
def get_size(path):
|
def get_size(path):
|
||||||
return os.path.getsize(path)
|
return os.path.getsize(path)
|
||||||
|
|
||||||
|
@ -165,6 +203,32 @@ def get_template(name, data={}):
|
||||||
return template.render(**data)
|
return template.render(**data)
|
||||||
|
|
||||||
|
|
||||||
|
def get_custom_styles(name, default='plantilla_factura.json'):
|
||||||
|
path = _join(PATH_MEDIA, 'templates', name.lower())
|
||||||
|
if is_file(path):
|
||||||
|
with open(path) as fh:
|
||||||
|
return loads(fh.read())
|
||||||
|
|
||||||
|
path = _join(PATH_TEMPLATES, default)
|
||||||
|
if is_file(path):
|
||||||
|
with open(path) as fh:
|
||||||
|
return loads(fh.read())
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def get_template_ods(name, default='plantilla_factura.ods'):
|
||||||
|
path = _join(PATH_MEDIA, 'templates', name.lower())
|
||||||
|
if is_file(path):
|
||||||
|
return path
|
||||||
|
|
||||||
|
path = _join(PATH_TEMPLATES, default)
|
||||||
|
if is_file(path):
|
||||||
|
return path
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def dumps(data):
|
def dumps(data):
|
||||||
return json.dumps(data, default=str)
|
return json.dumps(data, default=str)
|
||||||
|
|
||||||
|
@ -207,7 +271,7 @@ def to_slug(string):
|
||||||
value = (unicodedata.normalize('NFKD', string)
|
value = (unicodedata.normalize('NFKD', string)
|
||||||
.encode('ascii', 'ignore')
|
.encode('ascii', 'ignore')
|
||||||
.decode('ascii').lower())
|
.decode('ascii').lower())
|
||||||
return value
|
return value.replace(' ', '_')
|
||||||
|
|
||||||
|
|
||||||
class Certificado(object):
|
class Certificado(object):
|
||||||
|
@ -384,11 +448,19 @@ def make_xml(data, certificado):
|
||||||
return cfdi.add_sello(sello)
|
return cfdi.add_sello(sello)
|
||||||
|
|
||||||
|
|
||||||
def timbra_xml(xml):
|
def timbra_xml(xml, auth):
|
||||||
from .pac import Finkok as PAC
|
from .pac import Finkok as PAC
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
auth = {}
|
||||||
|
else:
|
||||||
|
if not auth:
|
||||||
|
msg = 'Sin datos para timbrar'
|
||||||
|
result = {'ok': True, 'error': msg}
|
||||||
|
return result
|
||||||
|
|
||||||
result = {'ok': True, 'error': ''}
|
result = {'ok': True, 'error': ''}
|
||||||
pac = PAC()
|
pac = PAC(auth)
|
||||||
xml = pac.timbra_xml(xml)
|
xml = pac.timbra_xml(xml)
|
||||||
if not xml:
|
if not xml:
|
||||||
result['ok'] = False
|
result['ok'] = False
|
||||||
|
@ -399,3 +471,663 @@ def timbra_xml(xml):
|
||||||
result['uuid'] = pac.uuid
|
result['uuid'] = pac.uuid
|
||||||
result['fecha'] = pac.fecha
|
result['fecha'] = pac.fecha
|
||||||
return result
|
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 _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)
|
||||||
|
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 _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 = 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._totales(data['totales'])
|
||||||
|
self._timbre(data['timbre'])
|
||||||
|
self._cancelado(data['cancelada'])
|
||||||
|
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)
|
||||||
|
|
||||||
|
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]
|
||||||
|
doc.storeToURL(self._path_url(path), self._set_properties(options))
|
||||||
|
doc.close(True)
|
||||||
|
self._template.close(True)
|
||||||
|
|
||||||
|
return self._read(path)
|
||||||
|
|
||||||
|
|
||||||
|
def to_pdf(data):
|
||||||
|
rfc = data['emisor']['rfc']
|
||||||
|
version = data['comprobante']['version']
|
||||||
|
|
||||||
|
if APP_LIBO:
|
||||||
|
app = LIBO()
|
||||||
|
if app.is_running:
|
||||||
|
name = '{}_{}.ods'.format(rfc, version)
|
||||||
|
path = get_template_ods(name)
|
||||||
|
if path:
|
||||||
|
return app.pdf(path, data)
|
||||||
|
|
||||||
|
name = '{}_{}.json'.format(rfc, version)
|
||||||
|
custom_styles = get_custom_styles(name)
|
||||||
|
|
||||||
|
path = get_path_temp()
|
||||||
|
pdf = TemplateInvoice(path)
|
||||||
|
pdf.custom_styles = custom_styles
|
||||||
|
pdf.data = data
|
||||||
|
pdf.render()
|
||||||
|
return read_file(path)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_xml(xml):
|
||||||
|
return ET.fromstring(xml)
|
||||||
|
|
||||||
|
|
||||||
|
def get_dict(data):
|
||||||
|
return CaseInsensitiveDict(data)
|
||||||
|
|
||||||
|
|
||||||
|
def to_letters(value, moneda):
|
||||||
|
monedas = {
|
||||||
|
'MXN': 'peso',
|
||||||
|
'USD': 'dólar',
|
||||||
|
'EUR': 'euro',
|
||||||
|
}
|
||||||
|
return NumLet(value, monedas[moneda]).letras
|
||||||
|
|
||||||
|
|
||||||
|
def get_qr(data):
|
||||||
|
path = tempfile.mkstemp()[1]
|
||||||
|
qr = pyqrcode.create(data, mode='binary')
|
||||||
|
qr.png(path, scale=7)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def _comprobante(values, options):
|
||||||
|
data = CaseInsensitiveDict(values)
|
||||||
|
del data['certificado']
|
||||||
|
|
||||||
|
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. de 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']))
|
||||||
|
if 'serie' in data:
|
||||||
|
data['folio'] = '{}-{}'.format(data['serie'], data['folio'])
|
||||||
|
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(CaseInsensitiveDict(node.attrib.copy()))
|
||||||
|
data['regimenfiscal'] = values['regimenfiscal']
|
||||||
|
|
||||||
|
path = _join(PATH_MEDIA, 'logos', '{}.png'.format(data['rfc'].lower()))
|
||||||
|
if is_file(path):
|
||||||
|
data['logo'] = path
|
||||||
|
|
||||||
|
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'] = '({})\n{}'.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',
|
||||||
|
'003': 'IEPS',
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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_from_xml(invoice, values):
|
||||||
|
data = {'cancelada': invoice.cancelada}
|
||||||
|
doc = parse_xml(invoice.xml)
|
||||||
|
data['comprobante'] = _comprobante(doc.attrib.copy(), values)
|
||||||
|
version = data['comprobante']['version']
|
||||||
|
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)
|
||||||
|
del data['timbre']['version']
|
||||||
|
data['comprobante'].update(data['timbre'])
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def to_zip(*files):
|
||||||
|
zip_buffer = BytesIO()
|
||||||
|
|
||||||
|
with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED, False) as zip_file:
|
||||||
|
for data, file_name in files:
|
||||||
|
zip_file.writestr(file_name, data)
|
||||||
|
|
||||||
|
return zip_buffer.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def make_fields(xml):
|
||||||
|
doc = ET.fromstring(xml)
|
||||||
|
data = CaseInsensitiveDict(doc.attrib.copy())
|
||||||
|
data.pop('certificado')
|
||||||
|
data.pop('sello')
|
||||||
|
version = data['version']
|
||||||
|
receptor = doc.find('{}Receptor'.format(PRE[version]))
|
||||||
|
receptor = CaseInsensitiveDict(receptor.attrib.copy())
|
||||||
|
data['receptor_nombre'] = receptor['nombre']
|
||||||
|
data['receptor_rfc'] = receptor['rfc']
|
||||||
|
data = {k.lower(): v for k, v in data.items()}
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def make_info_mail(data, fields):
|
||||||
|
return data.format(**fields).replace('\n', '<br/>')
|
||||||
|
|
||||||
|
|
||||||
|
def send_mail(data):
|
||||||
|
msg = ''
|
||||||
|
server = SendMail(data['server'])
|
||||||
|
is_connect = server.is_connect
|
||||||
|
if is_connect:
|
||||||
|
msg = server.send(data['options'])
|
||||||
|
else:
|
||||||
|
msg = server.error
|
||||||
|
server.close()
|
||||||
|
return {'ok': is_connect, 'msg': msg}
|
||||||
|
|
||||||
|
|
||||||
|
def get_path_info(path):
|
||||||
|
path, filename = os.path.split(path)
|
||||||
|
name, extension = os.path.splitext(filename)
|
||||||
|
return (path, filename, name, extension)
|
||||||
|
|
||||||
|
|
||||||
|
def get_path_temp():
|
||||||
|
return tempfile.mkstemp()[1]
|
||||||
|
|
||||||
|
|
||||||
|
class ImportFacturaLibre(object):
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
self._con = None
|
||||||
|
self._cursor = None
|
||||||
|
self._is_connect = self._connect(path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_connect(self):
|
||||||
|
return self._is_connect
|
||||||
|
|
||||||
|
def _connect(self, path):
|
||||||
|
try:
|
||||||
|
self._con = sqlite3.connect(path)
|
||||||
|
self._con.row_factory = sqlite3.Row
|
||||||
|
self._cursor = self._con.cursor()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
log.error(e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
try:
|
||||||
|
self._cursor.close()
|
||||||
|
self._con.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
def import_data(self):
|
||||||
|
data = {}
|
||||||
|
tables = (
|
||||||
|
('receptores', 'Socios'),
|
||||||
|
)
|
||||||
|
for source, target in tables:
|
||||||
|
data[target] = self._get_table(source)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _get_table(self, table):
|
||||||
|
return getattr(self, '_{}'.format(table))()
|
||||||
|
|
||||||
|
def _receptores(self):
|
||||||
|
sql = "SELECT * FROM receptores"
|
||||||
|
self._cursor.execute(sql)
|
||||||
|
rows = self._cursor.fetchall()
|
||||||
|
#~ names = [d[0] for d in self._cursor.description]
|
||||||
|
fields = (
|
||||||
|
('id', 'id'),
|
||||||
|
('rfc', 'rfc'),
|
||||||
|
('nombre', 'nombre'),
|
||||||
|
('calle', 'calle'),
|
||||||
|
('noExterior', 'no_exterior'),
|
||||||
|
('noInterior', 'no_interior'),
|
||||||
|
('colonia', 'colonia'),
|
||||||
|
('municipio', 'municipio'),
|
||||||
|
('estado', 'estado'),
|
||||||
|
('pais', 'pais'),
|
||||||
|
('codigoPostal', 'codigo_postal'),
|
||||||
|
('extranjero', 'es_extranjero'),
|
||||||
|
('activo', 'es_activo'),
|
||||||
|
('fechaalta', 'fecha_alta'),
|
||||||
|
('notas', 'notas'),
|
||||||
|
('cuentaCliente', 'cuenta_cliente'),
|
||||||
|
('cuentaProveedor', 'cuenta_proveedor'),
|
||||||
|
('saldoCliente', 'saldo_cliente'),
|
||||||
|
('saldoProveedor', 'saldo_proveedor'),
|
||||||
|
('esCliente', 'es_cliente'),
|
||||||
|
('esProveedor', 'es_proveedor'),
|
||||||
|
)
|
||||||
|
data = []
|
||||||
|
|
||||||
|
sql1 = "SELECT correo FROM correos WHERE id_cliente=?"
|
||||||
|
sql2 = "SELECT telefono FROM telefonos WHERE id_cliente=?"
|
||||||
|
for row in rows:
|
||||||
|
new = {t: row[s] for s, t in fields}
|
||||||
|
new['slug'] = to_slug(new['nombre'])
|
||||||
|
if new['es_extranjero']:
|
||||||
|
new['tipo_persona'] = 4
|
||||||
|
elif new['rfc'] == 'XAXX010101000':
|
||||||
|
new['tipo_persona'] = 3
|
||||||
|
elif len(new['rfc']) == 12:
|
||||||
|
new['tipo_persona'] = 2
|
||||||
|
|
||||||
|
self._cursor.execute(sql1, (new['id'],))
|
||||||
|
tmp = self._cursor.fetchall()
|
||||||
|
if tmp:
|
||||||
|
new['correo_facturas'] = ', '.join([r[0] for r in tmp])
|
||||||
|
|
||||||
|
self._cursor.execute(sql2, (new['id'],))
|
||||||
|
tmp = self._cursor.fetchall()
|
||||||
|
if tmp:
|
||||||
|
new['telefonos'] = ', '.join([r[0] for r in tmp])
|
||||||
|
|
||||||
|
data.append(new)
|
||||||
|
return data
|
||||||
|
|
|
@ -13,7 +13,7 @@ from middleware import (
|
||||||
)
|
)
|
||||||
from models.db import StorageEngine
|
from models.db import StorageEngine
|
||||||
from controllers.main import (
|
from controllers.main import (
|
||||||
AppLogin, AppLogout, AppAdmin, AppEmisor,
|
AppLogin, AppLogout, AppAdmin, AppEmisor, AppConfig,
|
||||||
AppMain, AppValues, AppPartners, AppProducts, AppInvoices, AppFolios,
|
AppMain, AppValues, AppPartners, AppProducts, AppInvoices, AppFolios,
|
||||||
AppDocumentos
|
AppDocumentos
|
||||||
)
|
)
|
||||||
|
@ -38,6 +38,7 @@ api.add_route('/emisor', AppEmisor(db))
|
||||||
api.add_route('/folios', AppFolios(db))
|
api.add_route('/folios', AppFolios(db))
|
||||||
api.add_route('/main', AppMain(db))
|
api.add_route('/main', AppMain(db))
|
||||||
api.add_route('/values/{table}', AppValues(db))
|
api.add_route('/values/{table}', AppValues(db))
|
||||||
|
api.add_route('/config', AppConfig(db))
|
||||||
api.add_route('/doc/{type_doc}/{id_doc}', AppDocumentos(db))
|
api.add_route('/doc/{type_doc}/{id_doc}', AppDocumentos(db))
|
||||||
api.add_route('/partners', AppPartners(db))
|
api.add_route('/partners', AppPartners(db))
|
||||||
api.add_route('/products', AppProducts(db))
|
api.add_route('/products', AppProducts(db))
|
||||||
|
|
|
@ -14,12 +14,24 @@ class StorageEngine(object):
|
||||||
def get_values(self, table, values=None):
|
def get_values(self, table, values=None):
|
||||||
return getattr(self, '_get_{}'.format(table))(values)
|
return getattr(self, '_get_{}'.format(table))(values)
|
||||||
|
|
||||||
|
def get_config(self, values):
|
||||||
|
return main.Configuracion.get_(values)
|
||||||
|
|
||||||
|
def add_config(self, values):
|
||||||
|
return main.Configuracion.add(values)
|
||||||
|
|
||||||
def add_cert(self, file_object):
|
def add_cert(self, file_object):
|
||||||
return main.Certificado.add(file_object)
|
return main.Certificado.add(file_object)
|
||||||
|
|
||||||
def validate_cert(self, values, session):
|
def validate_cert(self, values, session):
|
||||||
return main.Certificado.validate(values, session)
|
return main.Certificado.validate(values, session)
|
||||||
|
|
||||||
|
def validate_email(self, values):
|
||||||
|
return main.test_correo(values)
|
||||||
|
|
||||||
|
def send_email(self, values, session):
|
||||||
|
return main.Facturas.send(values['id'], session['rfc'])
|
||||||
|
|
||||||
def _get_cert(self, values):
|
def _get_cert(self, values):
|
||||||
return main.Certificado.get_data()
|
return main.Certificado.get_data()
|
||||||
|
|
||||||
|
@ -115,9 +127,15 @@ class StorageEngine(object):
|
||||||
def add_folios(self, values):
|
def add_folios(self, values):
|
||||||
return main.Folios.add(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':
|
if type_doc == 'xml':
|
||||||
data, file_name = main.Facturas.get_xml(id)
|
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'
|
||||||
|
if type_doc == 'zip':
|
||||||
|
data, file_name = main.Facturas.get_zip(id, rfc)
|
||||||
|
content_type = 'application/octet-stream'
|
||||||
return data, file_name, content_type
|
return data, file_name, content_type
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,9 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
|
|
||||||
from controllers import util
|
from controllers import util
|
||||||
from settings import log, VERSION, PATH_CP, COMPANIES
|
from settings import log, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, \
|
||||||
|
INIT_VALUES
|
||||||
|
|
||||||
|
|
||||||
FORMAT = '{0:.2f}'
|
FORMAT = '{0:.2f}'
|
||||||
|
|
||||||
|
@ -59,9 +61,34 @@ def desconectar():
|
||||||
|
|
||||||
|
|
||||||
class Configuracion(BaseModel):
|
class Configuracion(BaseModel):
|
||||||
clave = TextField()
|
clave = TextField(unique=True)
|
||||||
valor = TextField(default='')
|
valor = TextField(default='')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_(cls, keys):
|
||||||
|
if keys['fields'] == 'correo':
|
||||||
|
fields = ('correo_servidor', 'correo_puerto', 'correo_ssl',
|
||||||
|
'correo_usuario', 'correo_contra', 'correo_copia',
|
||||||
|
'correo_asunto', 'correo_mensaje', 'correo_directo')
|
||||||
|
data = (Configuracion
|
||||||
|
.select()
|
||||||
|
.where(Configuracion.clave.in_(fields))
|
||||||
|
)
|
||||||
|
values = {r.clave: r.valor for r in data}
|
||||||
|
return values
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add(cls, values):
|
||||||
|
try:
|
||||||
|
for k, v in values.items():
|
||||||
|
obj, created = Configuracion.get_or_create(clave=k)
|
||||||
|
obj.valor = v
|
||||||
|
obj.save()
|
||||||
|
return {'ok': True}
|
||||||
|
except Exception as e:
|
||||||
|
log.error(str(e))
|
||||||
|
return {'ok': False, 'msg': str(e)}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
order_by = ('clave',)
|
order_by = ('clave',)
|
||||||
indexes = (
|
indexes = (
|
||||||
|
@ -110,6 +137,9 @@ class SATRegimenes(BaseModel):
|
||||||
(('key', 'name'), True),
|
(('key', 'name'), True),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{} ({})'.format(self.name, self.key)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_(cls, ids):
|
def get_(cls, ids):
|
||||||
if isinstance(ids, int):
|
if isinstance(ids, int):
|
||||||
|
@ -155,6 +185,10 @@ class Emisor(BaseModel):
|
||||||
correo = TextField(default='')
|
correo = TextField(default='')
|
||||||
web = TextField(default='')
|
web = TextField(default='')
|
||||||
curp = TextField(default='')
|
curp = TextField(default='')
|
||||||
|
correo_timbrado = TextField(default='')
|
||||||
|
token_timbrado = TextField(default='')
|
||||||
|
token_soporte = TextField(default='')
|
||||||
|
logo = TextField(default='')
|
||||||
regimenes = ManyToManyField(SATRegimenes, related_name='emisores')
|
regimenes = ManyToManyField(SATRegimenes, related_name='emisores')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -184,6 +218,7 @@ class Emisor(BaseModel):
|
||||||
'emisor_municipio': obj.municipio,
|
'emisor_municipio': obj.municipio,
|
||||||
'emisor_estado': obj.estado,
|
'emisor_estado': obj.estado,
|
||||||
'emisor_pais': obj.pais,
|
'emisor_pais': obj.pais,
|
||||||
|
'emisor_logo': obj.logo,
|
||||||
'emisor_nombre_comercial': obj.nombre_comercial,
|
'emisor_nombre_comercial': obj.nombre_comercial,
|
||||||
'emisor_telefono': obj.telefono,
|
'emisor_telefono': obj.telefono,
|
||||||
'emisor_correo': obj.correo,
|
'emisor_correo': obj.correo,
|
||||||
|
@ -193,6 +228,9 @@ class Emisor(BaseModel):
|
||||||
'ong_autorizacion': obj.autorizacion,
|
'ong_autorizacion': obj.autorizacion,
|
||||||
'ong_fecha': obj.fecha_autorizacion,
|
'ong_fecha': obj.fecha_autorizacion,
|
||||||
'ong_fecha_dof': obj.fecha_dof,
|
'ong_fecha_dof': obj.fecha_dof,
|
||||||
|
'correo_timbrado': obj.correo_timbrado,
|
||||||
|
'token_timbrado': obj.token_timbrado,
|
||||||
|
'token_soporte': obj.token_soporte,
|
||||||
'regimenes': [row.id for row in obj.regimenes]
|
'regimenes': [row.id for row in obj.regimenes]
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
|
@ -200,6 +238,14 @@ class Emisor(BaseModel):
|
||||||
|
|
||||||
return {'ok': True, 'row': row}
|
return {'ok': True, 'row': row}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_auth(cls):
|
||||||
|
try:
|
||||||
|
obj = Emisor.select()[0]
|
||||||
|
return {'USER': obj.correo_timbrado, 'PASS': obj.token_timbrado}
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_regimenes(cls):
|
def get_regimenes(cls):
|
||||||
obj = Emisor.select()[0]
|
obj = Emisor.select()[0]
|
||||||
|
@ -216,6 +262,7 @@ class Emisor(BaseModel):
|
||||||
fields['municipio'] = fields.pop('emisor_municipio', '')
|
fields['municipio'] = fields.pop('emisor_municipio', '')
|
||||||
fields['estado'] = fields.pop('emisor_estado', '')
|
fields['estado'] = fields.pop('emisor_estado', '')
|
||||||
fields['pais'] = fields.pop('emisor_pais', 'México')
|
fields['pais'] = fields.pop('emisor_pais', 'México')
|
||||||
|
fields['logo'] = fields.pop('emisor_logo', '')
|
||||||
fields['nombre_comercial'] = fields.pop('emisor_nombre_comercial', '')
|
fields['nombre_comercial'] = fields.pop('emisor_nombre_comercial', '')
|
||||||
fields['telefono'] = fields.pop('emisor_telefono', '')
|
fields['telefono'] = fields.pop('emisor_telefono', '')
|
||||||
fields['correo'] = fields.pop('emisor_correo', '')
|
fields['correo'] = fields.pop('emisor_correo', '')
|
||||||
|
@ -458,6 +505,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 +546,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
|
||||||
|
@ -529,6 +582,22 @@ class SATImpuestos(BaseModel):
|
||||||
return tuple(rows)
|
return tuple(rows)
|
||||||
|
|
||||||
|
|
||||||
|
class SATTipoRelacion(BaseModel):
|
||||||
|
key = TextField(index=True, unique=True)
|
||||||
|
name = TextField(default='', index=True)
|
||||||
|
activo = BooleanField(default=False)
|
||||||
|
default = BooleanField(default=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
order_by = ('-default', 'name',)
|
||||||
|
indexes = (
|
||||||
|
(('key', 'name'), True),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'Tipo de relación: ({}) {}'.format(self.key, self.name)
|
||||||
|
|
||||||
|
|
||||||
class SATUsoCfdi(BaseModel):
|
class SATUsoCfdi(BaseModel):
|
||||||
key = TextField(index=True, unique=True)
|
key = TextField(index=True, unique=True)
|
||||||
name = TextField(default='', index=True)
|
name = TextField(default='', index=True)
|
||||||
|
@ -543,6 +612,9 @@ class SATUsoCfdi(BaseModel):
|
||||||
(('key', 'name'), True),
|
(('key', 'name'), True),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'Uso del CFDI: {} ({})'.format(self.name, self.key)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_activos(cls, values):
|
def get_activos(cls, values):
|
||||||
field = SATUsoCfdi.id
|
field = SATUsoCfdi.id
|
||||||
|
@ -594,6 +666,8 @@ class Socios(BaseModel):
|
||||||
es_proveedor = BooleanField(default=False)
|
es_proveedor = BooleanField(default=False)
|
||||||
cuenta_cliente = TextField(default='')
|
cuenta_cliente = TextField(default='')
|
||||||
cuenta_proveedor = TextField(default='')
|
cuenta_proveedor = TextField(default='')
|
||||||
|
saldo_cliente = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||||
|
saldo_proveedor = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||||
web = TextField(default='')
|
web = TextField(default='')
|
||||||
correo_facturas = TextField(default='')
|
correo_facturas = TextField(default='')
|
||||||
forma_pago = ForeignKeyField(SATFormaPago, null=True)
|
forma_pago = ForeignKeyField(SATFormaPago, null=True)
|
||||||
|
@ -644,12 +718,12 @@ class Socios(BaseModel):
|
||||||
Socios.id, Socios.nombre, Socios.rfc,
|
Socios.id, Socios.nombre, Socios.rfc,
|
||||||
SATFormaPago.key.alias('forma_pago'),
|
SATFormaPago.key.alias('forma_pago'),
|
||||||
SATUsoCfdi.key.alias('uso_cfdi'))
|
SATUsoCfdi.key.alias('uso_cfdi'))
|
||||||
.join(SATFormaPago).switch(Socios)
|
.join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios)
|
||||||
.join(SATUsoCfdi).switch(Socios)
|
.join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios)
|
||||||
.where(
|
.where((Socios.id==id) & (Socios.es_cliente==True))
|
||||||
(Socios.id==id) & (Socios.es_cliente==True))
|
|
||||||
.dicts()
|
.dicts()
|
||||||
)
|
)
|
||||||
|
print (id, row)
|
||||||
if len(row):
|
if len(row):
|
||||||
return {'ok': True, 'row': row[0]}
|
return {'ok': True, 'row': row[0]}
|
||||||
return {'ok': False}
|
return {'ok': False}
|
||||||
|
@ -660,8 +734,8 @@ class Socios(BaseModel):
|
||||||
.select(Socios.id, Socios.nombre, Socios.rfc,
|
.select(Socios.id, Socios.nombre, Socios.rfc,
|
||||||
SATFormaPago.key.alias('forma_pago'),
|
SATFormaPago.key.alias('forma_pago'),
|
||||||
SATUsoCfdi.key.alias('uso_cfdi'))
|
SATUsoCfdi.key.alias('uso_cfdi'))
|
||||||
.join(SATFormaPago).switch(Socios)
|
.join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios)
|
||||||
.join(SATUsoCfdi).switch(Socios)
|
.join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios)
|
||||||
.where((Socios.es_cliente==True) &
|
.where((Socios.es_cliente==True) &
|
||||||
(Socios.rfc.contains(name) |
|
(Socios.rfc.contains(name) |
|
||||||
Socios.nombre.contains(name)))
|
Socios.nombre.contains(name)))
|
||||||
|
@ -724,7 +798,7 @@ class Socios(BaseModel):
|
||||||
class Productos(BaseModel):
|
class Productos(BaseModel):
|
||||||
categoria = ForeignKeyField(Categorias, null=True)
|
categoria = ForeignKeyField(Categorias, null=True)
|
||||||
clave = TextField(unique=True, index=True)
|
clave = TextField(unique=True, index=True)
|
||||||
clave_sat = TextField()
|
clave_sat = TextField(default='')
|
||||||
descripcion = TextField(index=True)
|
descripcion = TextField(index=True)
|
||||||
unidad = ForeignKeyField(SATUnidades)
|
unidad = ForeignKeyField(SATUnidades)
|
||||||
valor_unitario = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
valor_unitario = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||||
|
@ -901,6 +975,7 @@ class Productos(BaseModel):
|
||||||
|
|
||||||
class Facturas(BaseModel):
|
class Facturas(BaseModel):
|
||||||
cliente = ForeignKeyField(Socios)
|
cliente = ForeignKeyField(Socios)
|
||||||
|
version = TextField(default=CURRENT_CFDI)
|
||||||
serie = TextField(default='')
|
serie = TextField(default='')
|
||||||
folio = IntegerField(default=0)
|
folio = IntegerField(default=0)
|
||||||
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
|
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
|
||||||
|
@ -928,6 +1003,9 @@ class Facturas(BaseModel):
|
||||||
regimen_fiscal = TextField(default='')
|
regimen_fiscal = TextField(default='')
|
||||||
notas = TextField(default='')
|
notas = TextField(default='')
|
||||||
pagada = BooleanField(default=False)
|
pagada = BooleanField(default=False)
|
||||||
|
cancelada = BooleanField(default=False)
|
||||||
|
donativo = BooleanField(default=False)
|
||||||
|
tipo_relacion = TextField(default='')
|
||||||
error = TextField(default='')
|
error = TextField(default='')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -939,6 +1017,158 @@ class Facturas(BaseModel):
|
||||||
name = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc)
|
name = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc)
|
||||||
return obj.xml, name
|
return obj.xml, name
|
||||||
|
|
||||||
|
#~ Revisar
|
||||||
|
def _get_data_cfdi_to_pdf(self, xml, cancel, version):
|
||||||
|
pre_nomina = PRE['NOMINA'][version]
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc)
|
||||||
|
if obj.uuid is None:
|
||||||
|
return b'', name
|
||||||
|
|
||||||
|
values = cls._get_not_in_xml(cls, obj)
|
||||||
|
data = util.get_data_from_xml(obj, values)
|
||||||
|
doc = util.to_pdf(data)
|
||||||
|
return doc, name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_zip(cls, id, rfc):
|
||||||
|
obj = Facturas.get(Facturas.id==id)
|
||||||
|
name_zip = '{}{}_{}.zip'.format(obj.serie, obj.folio, obj.cliente.rfc)
|
||||||
|
if obj.uuid is None:
|
||||||
|
return b'', name_zip
|
||||||
|
|
||||||
|
file_xml = cls.get_xml(id)
|
||||||
|
if not file_xml[0]:
|
||||||
|
return b'', name_zip
|
||||||
|
|
||||||
|
file_pdf = cls.get_pdf(id, rfc)
|
||||||
|
if not file_pdf[0]:
|
||||||
|
return b'', name_zip
|
||||||
|
|
||||||
|
file_zip = util.to_zip(file_xml, file_pdf)
|
||||||
|
|
||||||
|
return file_zip, name_zip
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def send(cls, id, rfc):
|
||||||
|
values = Configuracion.get_({'fields': 'correo'})
|
||||||
|
if not values:
|
||||||
|
msg = 'No esta configurado el servidor de correo de salida'
|
||||||
|
return {'ok': False, 'msg': msg}
|
||||||
|
|
||||||
|
obj = Facturas.get(Facturas.id==id)
|
||||||
|
if obj.uuid is None:
|
||||||
|
msg = 'La factura no esta timbrada'
|
||||||
|
return {'ok': False, 'msg': msg}
|
||||||
|
|
||||||
|
if not obj.cliente.correo_facturas:
|
||||||
|
msg = 'El cliente no tiene configurado el correo para facturas'
|
||||||
|
return {'ok': False, 'msg': msg}
|
||||||
|
|
||||||
|
files = (cls.get_zip(id, rfc),)
|
||||||
|
|
||||||
|
fields = util.make_fields(obj.xml)
|
||||||
|
server = {
|
||||||
|
'servidor': values['correo_servidor'],
|
||||||
|
'puerto': values['correo_puerto'],
|
||||||
|
'ssl': bool(int(values['correo_ssl'])),
|
||||||
|
'usuario': values['correo_usuario'],
|
||||||
|
'contra': values['correo_contra'],
|
||||||
|
}
|
||||||
|
options = {
|
||||||
|
'para': obj.cliente.correo_facturas,
|
||||||
|
'copia': values['correo_copia'],
|
||||||
|
'asunto': util.make_info_mail(values['correo_asunto'], fields),
|
||||||
|
'mensaje': util.make_info_mail(values['correo_mensaje'], fields),
|
||||||
|
'files': files,
|
||||||
|
}
|
||||||
|
data= {
|
||||||
|
'server': server,
|
||||||
|
'options': options,
|
||||||
|
}
|
||||||
|
result = util.send_mail(data)
|
||||||
|
if not result['ok'] or result['msg']:
|
||||||
|
return {'ok': False, 'msg': result['msg']}
|
||||||
|
|
||||||
|
msg = 'Factura enviada correctamente'
|
||||||
|
return {'ok': True, 'msg': msg}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_(cls, values):
|
def get_(cls, values):
|
||||||
rows = tuple(Facturas
|
rows = tuple(Facturas
|
||||||
|
@ -1214,14 +1444,17 @@ class Facturas(BaseModel):
|
||||||
obj.estatus = 'Generada'
|
obj.estatus = 'Generada'
|
||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
|
auth = Emisor.get_auth()
|
||||||
|
|
||||||
error = False
|
error = False
|
||||||
msg = 'Factura timbrada correctamente'
|
msg = 'Factura timbrada correctamente'
|
||||||
result = util.timbra_xml(obj.xml)
|
result = util.timbra_xml(obj.xml, auth)
|
||||||
if result['ok']:
|
if result['ok']:
|
||||||
obj.xml = result['xml']
|
obj.xml = result['xml']
|
||||||
obj.uuid = result['uuid']
|
obj.uuid = result['uuid']
|
||||||
obj.fecha_timbrado = result['fecha']
|
obj.fecha_timbrado = result['fecha']
|
||||||
obj.estatus = 'Timbrada'
|
obj.estatus = 'Timbrada'
|
||||||
|
obj.error = ''
|
||||||
obj.save()
|
obj.save()
|
||||||
row = {'uuid': obj.uuid, 'estatus': 'Timbrada'}
|
row = {'uuid': obj.uuid, 'estatus': 'Timbrada'}
|
||||||
else:
|
else:
|
||||||
|
@ -1234,6 +1467,17 @@ class Facturas(BaseModel):
|
||||||
return {'ok': result['ok'], 'msg': msg, 'row': row}
|
return {'ok': result['ok'], 'msg': msg, 'row': row}
|
||||||
|
|
||||||
|
|
||||||
|
class FacturasRelacionadas(BaseModel):
|
||||||
|
factura = ForeignKeyField(Facturas, related_name='original')
|
||||||
|
factura_origen = ForeignKeyField(Facturas, related_name='relacion')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
order_by = ('factura',)
|
||||||
|
indexes = (
|
||||||
|
(('factura', 'factura_origen'), True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FacturasDetalle(BaseModel):
|
class FacturasDetalle(BaseModel):
|
||||||
factura = ForeignKeyField(Facturas)
|
factura = ForeignKeyField(Facturas)
|
||||||
producto = ForeignKeyField(Productos, null=True)
|
producto = ForeignKeyField(Productos, null=True)
|
||||||
|
@ -1330,26 +1574,51 @@ def get_sat_key(key):
|
||||||
return util.get_sat_key('products', key)
|
return util.get_sat_key('products', key)
|
||||||
|
|
||||||
|
|
||||||
|
def test_correo(values):
|
||||||
|
server = {
|
||||||
|
'servidor': values['correo_servidor'],
|
||||||
|
'puerto': values['correo_puerto'],
|
||||||
|
'ssl': bool(values['correo_ssl'].replace('0', '')),
|
||||||
|
'usuario': values['correo_usuario'],
|
||||||
|
'contra': values['correo_contra'],
|
||||||
|
}
|
||||||
|
options = {
|
||||||
|
'para': values['correo_usuario'],
|
||||||
|
'copia': values['correo_copia'],
|
||||||
|
'asunto': values['correo_asunto'],
|
||||||
|
'mensaje': values['correo_mensaje'].replace('\n', '<br/>'),
|
||||||
|
'files': [],
|
||||||
|
}
|
||||||
|
data= {
|
||||||
|
'server': server,
|
||||||
|
'options': options,
|
||||||
|
}
|
||||||
|
return util.send_mail(data)
|
||||||
|
|
||||||
|
|
||||||
def _init_values():
|
def _init_values():
|
||||||
data = (
|
data = (
|
||||||
{'key': 'version', 'value': VERSION},
|
{'key': 'version', 'value': VERSION},
|
||||||
{'key': 'rfc_publico', 'value': 'XAXX010101000'},
|
{'key': 'rfc_publico', 'value': 'XAXX010101000'},
|
||||||
{'key': 'rfc_extranjero', 'value': 'XEXX010101000'},
|
{'key': 'rfc_extranjero', 'value': 'XEXX010101000'},
|
||||||
|
{'key': 'decimales', 'value': '2'},
|
||||||
)
|
)
|
||||||
for row in data:
|
for row in data:
|
||||||
try:
|
try:
|
||||||
Configuration.create(**row)
|
Configuracion.create(**row)
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
pass
|
pass
|
||||||
log.info('Valores iniciales insertados...')
|
log.info('Valores iniciales insertados...')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def _crear_tablas():
|
def _crear_tablas(rfc):
|
||||||
tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion,
|
tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion,
|
||||||
Emisor, Facturas, FacturasDetalle, FacturasImpuestos, Folios, Productos,
|
Emisor, Facturas, FacturasDetalle, FacturasImpuestos, Folios,
|
||||||
|
FacturasRelacionadas, Productos,
|
||||||
SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes,
|
SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes,
|
||||||
SATUnidades, SATUsoCfdi, Socios, Tags, Usuarios,
|
SATTipoRelacion, SATUnidades, SATUsoCfdi,
|
||||||
|
Socios, Tags, Usuarios,
|
||||||
Emisor.regimenes.get_through_model(),
|
Emisor.regimenes.get_through_model(),
|
||||||
Socios.tags.get_through_model(),
|
Socios.tags.get_through_model(),
|
||||||
Productos.impuestos.get_through_model(),
|
Productos.impuestos.get_through_model(),
|
||||||
|
@ -1357,6 +1626,21 @@ def _crear_tablas():
|
||||||
]
|
]
|
||||||
database_proxy.create_tables(tablas, True)
|
database_proxy.create_tables(tablas, True)
|
||||||
log.info('Tablas creadas correctamente...')
|
log.info('Tablas creadas correctamente...')
|
||||||
|
|
||||||
|
try:
|
||||||
|
usuario = 'admin'
|
||||||
|
contraseña = 'blades3.3'
|
||||||
|
obj = Usuarios.create(
|
||||||
|
usuario=usuario, contraseña=contraseña, es_superusuario=True)
|
||||||
|
log.info('SuperUsuario creado correctamente...')
|
||||||
|
except IntegrityError:
|
||||||
|
msg = 'El usuario ya existe'
|
||||||
|
log.error(msg)
|
||||||
|
pass
|
||||||
|
|
||||||
|
_init_values()
|
||||||
|
_importar_valores('', rfc)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -1425,6 +1709,7 @@ def _cambiar_contraseña():
|
||||||
|
|
||||||
|
|
||||||
def _add_emisor(rfc, args):
|
def _add_emisor(rfc, args):
|
||||||
|
util._valid_db_companies()
|
||||||
con = sqlite3.connect(COMPANIES)
|
con = sqlite3.connect(COMPANIES)
|
||||||
cursor = con.cursor()
|
cursor = con.cursor()
|
||||||
sql = """
|
sql = """
|
||||||
|
@ -1442,6 +1727,25 @@ def _add_emisor(rfc, args):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_emisor(rfc):
|
||||||
|
util._valid_db_companies()
|
||||||
|
con = sqlite3.connect(COMPANIES)
|
||||||
|
cursor = con.cursor()
|
||||||
|
sql = """
|
||||||
|
DELETE FROM names
|
||||||
|
WHERE rfc = ?"""
|
||||||
|
try:
|
||||||
|
cursor.execute(sql, (rfc,))
|
||||||
|
except Exception as e:
|
||||||
|
log.error(e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
con.commit()
|
||||||
|
cursor.close()
|
||||||
|
con.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _iniciar_bd():
|
def _iniciar_bd():
|
||||||
rfc = input('Introduce el RFC: ').strip().upper()
|
rfc = input('Introduce el RFC: ').strip().upper()
|
||||||
if not rfc:
|
if not rfc:
|
||||||
|
@ -1454,7 +1758,7 @@ def _iniciar_bd():
|
||||||
return
|
return
|
||||||
|
|
||||||
conectar(args)
|
conectar(args)
|
||||||
if _crear_tablas():
|
if _crear_tablas(rfc):
|
||||||
return
|
return
|
||||||
|
|
||||||
log.error('No se pudieron crear las tablas')
|
log.error('No se pudieron crear las tablas')
|
||||||
|
@ -1481,7 +1785,7 @@ def _agregar_rfc():
|
||||||
|
|
||||||
args = opt.copy()
|
args = opt.copy()
|
||||||
if conectar(args):
|
if conectar(args):
|
||||||
if _add_emisor(rfc, util.dumps(opt)) and _crear_tablas():
|
if _add_emisor(rfc, util.dumps(opt)) and _crear_tablas(rfc):
|
||||||
log.info('RFC agregado correctamente...')
|
log.info('RFC agregado correctamente...')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -1489,6 +1793,23 @@ def _agregar_rfc():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def _borrar_rfc():
|
||||||
|
rfc = input('Introduce el RFC a borrar: ').strip().upper()
|
||||||
|
if not rfc:
|
||||||
|
msg = 'El RFC es requerido'
|
||||||
|
log.error(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
confirm = input('¿Estás seguro de borrar el RFC?')
|
||||||
|
|
||||||
|
if _delete_emisor(rfc):
|
||||||
|
log.info('RFC borrado correctamente...')
|
||||||
|
return
|
||||||
|
|
||||||
|
log.error('No se pudo borrar el RFC')
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def _listar_rfc():
|
def _listar_rfc():
|
||||||
data = util.get_rfcs()
|
data = util.get_rfcs()
|
||||||
for row in data:
|
for row in data:
|
||||||
|
@ -1497,7 +1818,40 @@ def _listar_rfc():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def _importar_valores(archivo):
|
def _importar_valores(archivo='', rfc=''):
|
||||||
|
if not rfc:
|
||||||
|
rfc = input('Introduce el RFC: ').strip().upper()
|
||||||
|
if not rfc:
|
||||||
|
msg = 'El RFC es requerido'
|
||||||
|
log.error(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
args = util.get_con(rfc)
|
||||||
|
if not args:
|
||||||
|
return
|
||||||
|
|
||||||
|
conectar(args)
|
||||||
|
|
||||||
|
if not archivo:
|
||||||
|
archivo = INIT_VALUES
|
||||||
|
|
||||||
|
log.info('Importando datos...')
|
||||||
|
regimen = ''
|
||||||
|
rows = util.loads(open(archivo, 'r').read())
|
||||||
|
for row in rows:
|
||||||
|
log.info('\tImportando tabla: {}'.format(row['tabla']))
|
||||||
|
table = globals()[row['tabla']]
|
||||||
|
for r in row['datos']:
|
||||||
|
try:
|
||||||
|
table.create(**r)
|
||||||
|
except IntegrityError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
log.info('Importación terminada...')
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def _importar_factura_libre(archivo):
|
||||||
rfc = input('Introduce el RFC: ').strip().upper()
|
rfc = input('Introduce el RFC: ').strip().upper()
|
||||||
if not rfc:
|
if not rfc:
|
||||||
msg = 'El RFC es requerido'
|
msg = 'El RFC es requerido'
|
||||||
|
@ -1511,24 +1865,21 @@ def _importar_valores(archivo):
|
||||||
conectar(args)
|
conectar(args)
|
||||||
|
|
||||||
log.info('Importando datos...')
|
log.info('Importando datos...')
|
||||||
regimen = ''
|
app = util.ImportFacturaLibre(archivo)
|
||||||
rows = util.loads(open(archivo, 'r').read())
|
if not app.is_connect:
|
||||||
for row in rows:
|
log.error('\tNo se pudo conectar a la base de datos')
|
||||||
log.info('\tImportando tabla: {}'.format(row['tabla']))
|
return
|
||||||
if row['tabla'] == 'Emisor' and 'regimen' in row:
|
|
||||||
regimen = row['regimen']
|
|
||||||
table = globals()[row['tabla']]
|
|
||||||
for r in row['datos']:
|
|
||||||
try:
|
|
||||||
table.create(**r)
|
|
||||||
except IntegrityError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if regimen:
|
data = app.import_data()
|
||||||
emisor = Emisor.select()[0]
|
for table, rows in data.items():
|
||||||
regimen = SATRegimenes.get(SATRegimenes.key == regimen)
|
log.info('\tImportando: {}'.format(table))
|
||||||
emisor.regimenes.clear()
|
model = globals()[table]
|
||||||
emisor.regimenes.add(regimen)
|
for row in rows:
|
||||||
|
try:
|
||||||
|
model.create(**row)
|
||||||
|
except IntegrityError:
|
||||||
|
msg = '\t{}'.format(str(row))
|
||||||
|
log.error(msg)
|
||||||
|
|
||||||
log.info('Importación terminada...')
|
log.info('Importación terminada...')
|
||||||
return
|
return
|
||||||
|
@ -1555,10 +1906,11 @@ help_lr = 'Listar RFCs'
|
||||||
@click.option('-rfc', '--rfc', help=help_rfc, is_flag=True, default=False)
|
@click.option('-rfc', '--rfc', help=help_rfc, is_flag=True, default=False)
|
||||||
@click.option('-br', '--borrar-rfc', help=help_br, is_flag=True, default=False)
|
@click.option('-br', '--borrar-rfc', help=help_br, is_flag=True, default=False)
|
||||||
@click.option('-lr', '--listar-rfc', help=help_lr, is_flag=True, default=False)
|
@click.option('-lr', '--listar-rfc', help=help_lr, is_flag=True, default=False)
|
||||||
@click.option('-i', '--importar_valores', is_flag=True, default=False)
|
@click.option('-i', '--importar-valores', is_flag=True, default=False)
|
||||||
@click.option('-a', '--archivo')
|
@click.option('-a', '--archivo')
|
||||||
|
@click.option('-fl', '--factura-libre', is_flag=True, default=False)
|
||||||
def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc,
|
def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc,
|
||||||
borrar_rfc, listar_rfc, importar_valores, archivo):
|
borrar_rfc, listar_rfc, importar_valores, archivo, factura_libre):
|
||||||
opt = locals()
|
opt = locals()
|
||||||
|
|
||||||
if opt['iniciar_bd']:
|
if opt['iniciar_bd']:
|
||||||
|
@ -1600,6 +1952,21 @@ def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc,
|
||||||
_importar_valores(opt['archivo'])
|
_importar_valores(opt['archivo'])
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
if opt['factura_libre']:
|
||||||
|
if not opt['archivo']:
|
||||||
|
msg = 'Falta la ruta de la base de datos'
|
||||||
|
raise click.ClickException(msg)
|
||||||
|
if not util.is_file(opt['archivo']):
|
||||||
|
msg = 'No es un archivo'
|
||||||
|
raise click.ClickException(msg)
|
||||||
|
_, _, _, ext = util.get_path_info(opt['archivo'])
|
||||||
|
if ext != '.sqlite':
|
||||||
|
msg = 'No es una base de datos'
|
||||||
|
raise click.ClickException(msg)
|
||||||
|
|
||||||
|
_importar_factura_libre(opt['archivo'])
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,19 +7,25 @@ from mako.lookup import TemplateLookup
|
||||||
from logbook import Logger, StreamHandler, RotatingFileHandler
|
from logbook import Logger, StreamHandler, RotatingFileHandler
|
||||||
logbook.set_datetime_format('local')
|
logbook.set_datetime_format('local')
|
||||||
|
|
||||||
|
from conf import DEBUG
|
||||||
|
|
||||||
DEBUG = True
|
|
||||||
|
DEBUG = DEBUG
|
||||||
VERSION = '0.1.0'
|
VERSION = '0.1.0'
|
||||||
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
|
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
|
||||||
|
|
||||||
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
|
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||||
PATH_STATIC = os.path.abspath(os.path.join(BASE_DIR, '..'))
|
PATH_STATIC = os.path.abspath(os.path.join(BASE_DIR, '..'))
|
||||||
PATH_TEMPLATES = os.path.abspath(os.path.join(BASE_DIR, '..', 'templates'))
|
PATH_TEMPLATES = os.path.abspath(os.path.join(BASE_DIR, '..', 'templates'))
|
||||||
|
PATH_MEDIA = os.path.abspath(os.path.join(BASE_DIR, '..', 'docs'))
|
||||||
|
|
||||||
PATH_CP = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'cp.db'))
|
PATH_CP = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'cp.db'))
|
||||||
COMPANIES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'rfc.db'))
|
COMPANIES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'rfc.db'))
|
||||||
DB_SAT = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'sat.db'))
|
DB_SAT = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'sat.db'))
|
||||||
|
|
||||||
|
IV = 'valores_iniciales.json'
|
||||||
|
INIT_VALUES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', IV))
|
||||||
|
|
||||||
PATH_XSLT = os.path.abspath(os.path.join(BASE_DIR, '..', 'xslt'))
|
PATH_XSLT = os.path.abspath(os.path.join(BASE_DIR, '..', 'xslt'))
|
||||||
PATH_BIN = os.path.abspath(os.path.join(BASE_DIR, '..', 'bin'))
|
PATH_BIN = os.path.abspath(os.path.join(BASE_DIR, '..', 'bin'))
|
||||||
|
|
||||||
|
@ -36,6 +42,7 @@ format_string = '[{record.time:%d-%b-%Y %H:%M:%S}] ' \
|
||||||
'{record.channel}: ' \
|
'{record.channel}: ' \
|
||||||
'{record.message}'
|
'{record.message}'
|
||||||
|
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
LOG_LEVEL = 'DEBUG'
|
LOG_LEVEL = 'DEBUG'
|
||||||
StreamHandler(
|
StreamHandler(
|
||||||
|
@ -51,6 +58,12 @@ else:
|
||||||
level=LOG_LEVEL,
|
level=LOG_LEVEL,
|
||||||
format_string=format_string).push_application()
|
format_string=format_string).push_application()
|
||||||
|
|
||||||
|
StreamHandler(
|
||||||
|
sys.stdout,
|
||||||
|
level=LOG_LEVEL,
|
||||||
|
format_string=format_string).push_application()
|
||||||
|
|
||||||
|
|
||||||
log = Logger(LOG_NAME)
|
log = Logger(LOG_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,3 +72,19 @@ PATH_OPENSSL = 'openssl'
|
||||||
if 'win' in sys.platform:
|
if 'win' in sys.platform:
|
||||||
PATH_XSLTPROC = os.path.join(PATH_BIN, 'xsltproc.exe')
|
PATH_XSLTPROC = os.path.join(PATH_BIN, 'xsltproc.exe')
|
||||||
PATH_OPENSSL = os.path.join(PATH_BIN, 'openssl.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}',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CURRENT_CFDI = '3.3'
|
||||||
|
|
Binary file not shown.
|
@ -23,7 +23,20 @@
|
||||||
{"key": "HUR", "name": "Hora", "activo": true},
|
{"key": "HUR", "name": "Hora", "activo": true},
|
||||||
{"key": "H87", "name": "Pieza", "activo": true},
|
{"key": "H87", "name": "Pieza", "activo": true},
|
||||||
{"key": "E48", "name": "Servicio", "activo": true},
|
{"key": "E48", "name": "Servicio", "activo": true},
|
||||||
{"key": "E51", "name": "Trabajo", "activo": false}
|
{"key": "E51", "name": "Trabajo", "activo": false},
|
||||||
|
{"key": "ACT", "name": "Actividad", "activo": false}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tabla": "SATTipoRelacion",
|
||||||
|
"datos": [
|
||||||
|
{"key": "01", "name": "Nota de crédito de los documentos relacionados", "activo": true},
|
||||||
|
{"key": "02", "name": "Nota de débito de los documentos relacionados", "activo": true},
|
||||||
|
{"key": "03", "name": "Devolución de mercancía sobre facturas o traslados previos", "activo": true},
|
||||||
|
{"key": "04", "name": "Sustitución de los CFDI previos", "activo": true, "default": true},
|
||||||
|
{"key": "05", "name": "Traslados de mercancias facturados previamente", "activo": true},
|
||||||
|
{"key": "06", "name": "Factura generada por los traslados previos", "activo": true},
|
||||||
|
{"key": "07", "name": "Actividad", "CFDI por aplicación de anticipo": true}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,6 +16,8 @@ var controllers = {
|
||||||
$$('up_cert').attachEvent('onUploadComplete', up_cert_upload_complete)
|
$$('up_cert').attachEvent('onUploadComplete', up_cert_upload_complete)
|
||||||
$$('cmd_agregar_serie').attachEvent('onItemClick', cmd_agregar_serie_click)
|
$$('cmd_agregar_serie').attachEvent('onItemClick', cmd_agregar_serie_click)
|
||||||
$$('grid_folios').attachEvent('onItemClick', grid_folios_click)
|
$$('grid_folios').attachEvent('onItemClick', grid_folios_click)
|
||||||
|
$$('cmd_probar_correo').attachEvent('onItemClick', cmd_probar_correo_click)
|
||||||
|
$$('cmd_guardar_correo').attachEvent('onItemClick', cmd_guardar_correo_click)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +135,9 @@ function get_emisor(){
|
||||||
var emisor = values.row.emisor
|
var emisor = values.row.emisor
|
||||||
$$('lst_emisor_regimen').parse(values.row.regimenes)
|
$$('lst_emisor_regimen').parse(values.row.regimenes)
|
||||||
form.setValues(emisor, true)
|
form.setValues(emisor, true)
|
||||||
|
if(emisor.regimenes){
|
||||||
$$('lst_emisor_regimen').select(emisor.regimenes)
|
$$('lst_emisor_regimen').select(emisor.regimenes)
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
msg_error(values.msg)
|
msg_error(values.msg)
|
||||||
}
|
}
|
||||||
|
@ -175,6 +179,24 @@ function get_table_folios(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function get_config_correo(){
|
||||||
|
var form = $$('form_correo')
|
||||||
|
var fields = form.getValues()
|
||||||
|
|
||||||
|
webix.ajax().get('/config', {'fields': 'correo'}, {
|
||||||
|
error: function(text, data, xhr) {
|
||||||
|
msg = 'Error al consultar'
|
||||||
|
msg_error(msg)
|
||||||
|
},
|
||||||
|
success: function(text, data, xhr) {
|
||||||
|
var values = data.json()
|
||||||
|
form.setValues(values)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function multi_admin_change(prevID, nextID){
|
function multi_admin_change(prevID, nextID){
|
||||||
//~ webix.message(nextID)
|
//~ webix.message(nextID)
|
||||||
if(nextID == 'app_emisor'){
|
if(nextID == 'app_emisor'){
|
||||||
|
@ -188,6 +210,11 @@ function multi_admin_change(prevID, nextID){
|
||||||
get_table_folios()
|
get_table_folios()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(nextID == 'app_correo'){
|
||||||
|
get_config_correo()
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -442,10 +469,120 @@ function grid_folios_click(id, e, node){
|
||||||
msg_error(msg)
|
msg_error(msg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function validar_correo(values){
|
||||||
|
|
||||||
|
if(!values.correo_servidor.trim()){
|
||||||
|
msg = 'El servidor de salida no puede estar vacío'
|
||||||
|
msg_error(msg)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(!values.correo_puerto){
|
||||||
|
msg = 'El puerto no puede ser cero'
|
||||||
|
msg_error(msg)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(!values.correo_usuario.trim()){
|
||||||
|
msg = 'El nombre de usuario no puede estar vacío'
|
||||||
|
msg_error(msg)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(!values.correo_contra.trim()){
|
||||||
|
msg = 'La contraseña no puede estar vacía'
|
||||||
|
msg_error(msg)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(!values.correo_asunto.trim()){
|
||||||
|
msg = 'El asunto del correo no puede estar vacío'
|
||||||
|
msg_error(msg)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(!values.correo_mensaje.trim()){
|
||||||
|
msg = 'El mensaje del correo no puede estar vacío'
|
||||||
|
msg_error(msg)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function cmd_probar_correo_click(){
|
||||||
|
var form = $$('form_correo')
|
||||||
|
var values = form.getValues()
|
||||||
|
|
||||||
|
if(!validar_correo(values)){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
webix.ajax().sync().post('/values/correo', values, {
|
||||||
|
error: function(text, data, xhr) {
|
||||||
|
msg = 'Error al probar el correo'
|
||||||
|
msg_error(msg)
|
||||||
|
},
|
||||||
|
success: function(text, data, xhr) {
|
||||||
|
var values = data.json();
|
||||||
|
if (values.ok){
|
||||||
|
msg = 'Correo de prueba enviado correctamente. Ya puedes \
|
||||||
|
guardar esta configuración'
|
||||||
|
msg_sucess(msg)
|
||||||
|
}else{
|
||||||
|
msg_error(values.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function save_config_mail(values){
|
||||||
|
|
||||||
|
webix.ajax().sync().post('/config', values, {
|
||||||
|
error: function(text, data, xhr) {
|
||||||
|
msg = 'Error al guardar la configuración'
|
||||||
|
msg_error(msg)
|
||||||
|
},
|
||||||
|
success: function(text, data, xhr) {
|
||||||
|
var values = data.json();
|
||||||
|
if (values.ok){
|
||||||
|
msg = 'Configuración guardada correctamente'
|
||||||
|
msg_sucess(msg)
|
||||||
|
}else{
|
||||||
|
msg_error(values.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function cmd_guardar_correo_click(){
|
||||||
|
var form = $$('form_correo')
|
||||||
|
var values = form.getValues()
|
||||||
|
|
||||||
|
if(!validar_correo(values)){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = 'Asegurate de haber probado la configuración<BR><BR>\
|
||||||
|
¿Estás seguro de guardar estos datos?'
|
||||||
|
webix.confirm({
|
||||||
|
title: 'Configuración de correo',
|
||||||
|
ok: 'Si',
|
||||||
|
cancel: 'No',
|
||||||
|
type: 'confirm-error',
|
||||||
|
text: msg,
|
||||||
|
callback:function(result){
|
||||||
|
if(result){
|
||||||
|
save_config_mail(values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ function get_series(){
|
||||||
pre = values[0]
|
pre = values[0]
|
||||||
$$('lst_serie').getList().parse(values)
|
$$('lst_serie').getList().parse(values)
|
||||||
$$('lst_serie').setValue(pre.id)
|
$$('lst_serie').setValue(pre.id)
|
||||||
if(pre.usar_con){
|
if(pre.usarcon){
|
||||||
$$('lst_tipo_comprobante').setValue(pre.usar_con)
|
$$('lst_tipo_comprobante').setValue(pre.usarcon)
|
||||||
$$('lst_tipo_comprobante').config.readonly = true
|
$$('lst_tipo_comprobante').config.readonly = true
|
||||||
$$('lst_tipo_comprobante').refresh()
|
$$('lst_tipo_comprobante').refresh()
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ function get_monedas(){
|
||||||
$$('lst_moneda').setValue(pre.id)
|
$$('lst_moneda').setValue(pre.id)
|
||||||
if(values.length == 1){
|
if(values.length == 1){
|
||||||
$$('fs_moneda').hide()
|
$$('fs_moneda').hide()
|
||||||
$$('fs_moneda').refresh()
|
//~ $$('fs_moneda').refresh()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ function get_regimen_fiscal(){
|
||||||
$$('lst_regimen_fiscal').setValue(pre.id)
|
$$('lst_regimen_fiscal').setValue(pre.id)
|
||||||
if(values.length == 1){
|
if(values.length == 1){
|
||||||
$$('fs_regimen_fiscal').hide()
|
$$('fs_regimen_fiscal').hide()
|
||||||
$$('fs_regimen_fiscal').refresh()
|
//~ $$('fs_regimen_fiscal').refresh()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -121,6 +121,9 @@ function delete_invoice(id){
|
||||||
|
|
||||||
|
|
||||||
function cmd_delete_invoice_click(id, e, node){
|
function cmd_delete_invoice_click(id, e, node){
|
||||||
|
if(gi.count() == 0){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var row = gi.getSelectedItem()
|
var row = gi.getSelectedItem()
|
||||||
if (row == undefined){
|
if (row == undefined){
|
||||||
|
@ -642,10 +645,90 @@ function cmd_invoice_timbrar_click(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function enviar_correo(row){
|
||||||
|
if(!row.uuid){
|
||||||
|
msg_error('La factura no esta timbrada')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = '¿Estás seguro de enviar por correo esta factura?'
|
||||||
|
webix.confirm({
|
||||||
|
title: 'Enviar Factura',
|
||||||
|
ok: 'Si',
|
||||||
|
cancel: 'No',
|
||||||
|
type: 'confirm-error',
|
||||||
|
text: msg,
|
||||||
|
callback:function(result){
|
||||||
|
if(result){
|
||||||
|
webix.ajax().post('/values/sendmail', {'id': row.id}, {
|
||||||
|
error:function(text, data, XmlHttpRequest){
|
||||||
|
msg = 'Ocurrio un error, consulta a soporte técnico'
|
||||||
|
msg_error(msg)
|
||||||
|
},
|
||||||
|
success:function(text, data, XmlHttpRequest){
|
||||||
|
values = data.json();
|
||||||
|
if(values.ok){
|
||||||
|
msg_sucess(values.msg)
|
||||||
|
}else{
|
||||||
|
msg_error(values.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function grid_invoices_click(id, e, node){
|
function grid_invoices_click(id, e, node){
|
||||||
var row = this.getItem(id)
|
var row = this.getItem(id)
|
||||||
|
|
||||||
if(id.column == 'xml'){
|
if(id.column == 'xml'){
|
||||||
location = '/doc/xml/' + row.id
|
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'){
|
||||||
|
enviar_correo(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function send_cancel(id){
|
||||||
|
show(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd_invoice_cancelar_click(){
|
||||||
|
if(gi.count() == 0){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var row = gi.getSelectedItem()
|
||||||
|
if (row == undefined){
|
||||||
|
msg_error('Selecciona una factura')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!row.uuid){
|
||||||
|
msg_error('La factura no esta timbrada, solo es posible cancelar \
|
||||||
|
facturas timbradas')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = '¿Estás seguro de enviar a cancelar esta factura?<BR><BR> \
|
||||||
|
ESTA ACCIÓN NO SE PUEDE DESHACER'
|
||||||
|
webix.confirm({
|
||||||
|
title: 'Cancelar Factura',
|
||||||
|
ok: 'Si',
|
||||||
|
cancel: 'No',
|
||||||
|
type: 'confirm-error',
|
||||||
|
text: msg,
|
||||||
|
callback:function(result){
|
||||||
|
if(result){
|
||||||
|
send_cancel(row.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ var controllers = {
|
||||||
$$('grid_details').attachEvent('onBeforeEditStart', grid_details_before_edit_start)
|
$$('grid_details').attachEvent('onBeforeEditStart', grid_details_before_edit_start)
|
||||||
$$('grid_details').attachEvent('onBeforeEditStop', grid_details_before_edit_stop)
|
$$('grid_details').attachEvent('onBeforeEditStop', grid_details_before_edit_stop)
|
||||||
$$('cmd_invoice_timbrar').attachEvent('onItemClick', cmd_invoice_timbrar_click)
|
$$('cmd_invoice_timbrar').attachEvent('onItemClick', cmd_invoice_timbrar_click)
|
||||||
|
$$('cmd_invoice_cancelar').attachEvent('onItemClick', cmd_invoice_cancelar_click)
|
||||||
$$('grid_invoices').attachEvent('onItemClick', grid_invoices_click)
|
$$('grid_invoices').attachEvent('onItemClick', grid_invoices_click)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ var menu_data = [
|
||||||
{id: 'app_home', icon: 'dashboard', value: 'Inicio'},
|
{id: 'app_home', icon: 'dashboard', value: 'Inicio'},
|
||||||
{id: 'app_emisor', icon: 'user-circle', value: 'Emisor'},
|
{id: 'app_emisor', icon: 'user-circle', value: 'Emisor'},
|
||||||
{id: 'app_folios', icon: 'sort-numeric-asc', value: 'Folios'},
|
{id: 'app_folios', icon: 'sort-numeric-asc', value: 'Folios'},
|
||||||
|
{id: 'app_correo', icon: 'envelope-o', value: 'Correo'},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,6 +62,8 @@ var emisor_datos_fiscales = [
|
||||||
|
|
||||||
var emisor_otros_datos= [
|
var emisor_otros_datos= [
|
||||||
{template: 'Generales', type: 'section'},
|
{template: 'Generales', type: 'section'},
|
||||||
|
{view: 'search', id: 'emisor_logo', icon: 'file-image-o',
|
||||||
|
name: 'emisor_logo', label: 'Logotipo: '},
|
||||||
{view: 'text', id: 'emisor_nombre_comercial',
|
{view: 'text', id: 'emisor_nombre_comercial',
|
||||||
name: 'emisor_nombre_comercial', label: 'Nombre comercial: '},
|
name: 'emisor_nombre_comercial', label: 'Nombre comercial: '},
|
||||||
{view: 'text', id: 'emisor_telefono', name: 'emisor_telefono',
|
{view: 'text', id: 'emisor_telefono', name: 'emisor_telefono',
|
||||||
|
@ -86,6 +89,13 @@ var emisor_otros_datos= [
|
||||||
{cols: [{view: 'datepicker', id: 'ong_fecha_dof', name: 'ong_fecha_dof',
|
{cols: [{view: 'datepicker', id: 'ong_fecha_dof', name: 'ong_fecha_dof',
|
||||||
label: 'Fecha de DOF: ', disabled: true, format: '%d-%M-%Y',
|
label: 'Fecha de DOF: ', disabled: true, format: '%d-%M-%Y',
|
||||||
placeholder: 'Fecha de publicación en el DOF'}, {}]},
|
placeholder: 'Fecha de publicación en el DOF'}, {}]},
|
||||||
|
{template: 'Timbrado y Soporte', type: 'section'},
|
||||||
|
{view: 'text', id: 'correo_timbrado',
|
||||||
|
name: 'correo_timbrado', label: 'Usuario para Timbrado: '},
|
||||||
|
{view: 'text', id: 'token_timbrado',
|
||||||
|
name: 'token_timbrado', label: 'Token de Timbrado: '},
|
||||||
|
{view: 'text', id: 'token_soporte',
|
||||||
|
name: 'token_soporte', label: 'Token de Soporte: '},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -205,6 +215,55 @@ var emisor_folios = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
var emisor_correo = [
|
||||||
|
{template: 'Servidor de Salida', type: 'section'},
|
||||||
|
{cols: [
|
||||||
|
{view: 'text', id: 'correo_servidor', name: 'correo_servidor',
|
||||||
|
label: 'Servidor SMTP: '},
|
||||||
|
{}]},
|
||||||
|
{cols: [
|
||||||
|
{view: 'counter', id: 'correo_puerto', name: 'correo_puerto',
|
||||||
|
label: 'Puerto: ', value: 26, step: 1},
|
||||||
|
{}]},
|
||||||
|
{cols: [
|
||||||
|
{view: 'checkbox', id: 'correo_ssl', name: 'correo_ssl',
|
||||||
|
label: 'Usar TLS/SSL: '},
|
||||||
|
{}]},
|
||||||
|
{cols: [
|
||||||
|
{view: 'text', id: 'correo_usuario', name: 'correo_usuario',
|
||||||
|
label: 'Usuario: '},
|
||||||
|
{}]},
|
||||||
|
{cols: [
|
||||||
|
{view: 'text', id: 'correo_contra', name: 'correo_contra',
|
||||||
|
label: 'Contraseña: ', type: 'password'},
|
||||||
|
{}]},
|
||||||
|
{cols: [
|
||||||
|
{view: 'text', id: 'correo_copia', name: 'correo_copia',
|
||||||
|
label: 'Con copia a: '}
|
||||||
|
]},
|
||||||
|
{cols: [
|
||||||
|
{view: 'text', id: 'correo_asunto', name: 'correo_asunto',
|
||||||
|
label: 'Asunto del correo: '}
|
||||||
|
]},
|
||||||
|
{cols: [
|
||||||
|
{view: 'textarea', id: 'correo_mensaje', name: 'correo_mensaje',
|
||||||
|
label: 'Mensaje del correo: ', height: 200}
|
||||||
|
]},
|
||||||
|
{cols: [
|
||||||
|
{view: 'checkbox', id: 'correo_directo', name: 'correo_directo',
|
||||||
|
label: 'Enviar directamente: '},
|
||||||
|
{}]},
|
||||||
|
{minHeight: 25},
|
||||||
|
{cols: [{},
|
||||||
|
{view: 'button', id: 'cmd_probar_correo', label: 'Probar Configuración',
|
||||||
|
autowidth: true, type: 'form'},
|
||||||
|
{maxWidth: 100},
|
||||||
|
{view: 'button', id: 'cmd_guardar_correo', label: 'Guardar Configuración',
|
||||||
|
autowidth: true, type: 'form'},
|
||||||
|
{}]}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
var controls_folios = [
|
var controls_folios = [
|
||||||
{
|
{
|
||||||
view: 'tabview',
|
view: 'tabview',
|
||||||
|
@ -219,6 +278,20 @@ var controls_folios = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
var controls_correo = [
|
||||||
|
{
|
||||||
|
view: 'tabview',
|
||||||
|
id: 'tab_correo',
|
||||||
|
tabbar: {options: ['Correo Electrónico']},
|
||||||
|
animate: true,
|
||||||
|
cells: [
|
||||||
|
{id: 'Correo Electrónico', rows: emisor_correo},
|
||||||
|
{},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
var form_folios = {
|
var form_folios = {
|
||||||
type: 'space',
|
type: 'space',
|
||||||
cols: [{
|
cols: [{
|
||||||
|
@ -239,6 +312,22 @@ var form_folios = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var form_correo = {
|
||||||
|
type: 'space',
|
||||||
|
cols: [{
|
||||||
|
view: 'form',
|
||||||
|
id: 'form_correo',
|
||||||
|
complexData: true,
|
||||||
|
elements: controls_correo,
|
||||||
|
elementsConfig: {
|
||||||
|
labelWidth: 150,
|
||||||
|
labelAlign: 'right'
|
||||||
|
},
|
||||||
|
autoheight: true
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var app_emisor = {
|
var app_emisor = {
|
||||||
id: 'app_emisor',
|
id: 'app_emisor',
|
||||||
rows:[
|
rows:[
|
||||||
|
@ -267,6 +356,17 @@ var app_folios = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var app_correo = {
|
||||||
|
id: 'app_correo',
|
||||||
|
rows:[
|
||||||
|
{view: 'template', id: 'th_correo', type: 'header',
|
||||||
|
template: 'Configuración de correo'},
|
||||||
|
form_correo,
|
||||||
|
{},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var multi_admin = {
|
var multi_admin = {
|
||||||
id: 'multi_admin',
|
id: 'multi_admin',
|
||||||
animate: true,
|
animate: true,
|
||||||
|
@ -278,6 +378,7 @@ var multi_admin = {
|
||||||
},
|
},
|
||||||
app_emisor,
|
app_emisor,
|
||||||
app_folios,
|
app_folios,
|
||||||
|
app_correo,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,9 @@ var toolbar_invoices = [
|
||||||
var toolbar_invoices_util = [
|
var toolbar_invoices_util = [
|
||||||
{view: 'button', id: 'cmd_invoice_timbrar', label: 'Timbrar',
|
{view: 'button', id: 'cmd_invoice_timbrar', label: 'Timbrar',
|
||||||
type: 'iconButton', autowidth: true, icon: 'ticket'},
|
type: 'iconButton', autowidth: true, icon: 'ticket'},
|
||||||
|
{},
|
||||||
|
{view: 'button', id: 'cmd_invoice_cancelar', label: 'Cancelar',
|
||||||
|
type: 'iconButton', autowidth: true, icon: 'ban'},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -142,10 +145,10 @@ var suggest_products = {
|
||||||
header: true,
|
header: true,
|
||||||
columns: [
|
columns: [
|
||||||
{id: 'id', hidden: true},
|
{id: 'id', hidden: true},
|
||||||
{id: 'clave', adjust: 'data'},
|
{id: 'clave', header: 'Clave', adjust: 'data'},
|
||||||
{id: 'descripcion', adjust: 'data'},
|
{id: 'descripcion', header: 'Descripción', adjust: 'data'},
|
||||||
{id: 'unidad', adjust: 'data'},
|
{id: 'unidad', header: 'Unidad', adjust: 'data'},
|
||||||
{id: 'valor_unitario', adjust: 'data',
|
{id: 'valor_unitario', header: 'Valor Unitario', adjust: 'data',
|
||||||
format: webix.i18n.priceFormat}
|
format: webix.i18n.priceFormat}
|
||||||
],
|
],
|
||||||
dataFeed:function(text){
|
dataFeed:function(text){
|
||||||
|
|
|
@ -131,7 +131,7 @@ var toolbar_contacts = [
|
||||||
|
|
||||||
|
|
||||||
var grid_contacts_cols = [
|
var grid_contacts_cols = [
|
||||||
{id: 'index', header:'#', adjust:'data', css:'right',
|
{id: 'index', header: '#', adjust:'data', css:'right',
|
||||||
footer: {content: 'rowCount'}},
|
footer: {content: 'rowCount'}},
|
||||||
{id: 'id', header: '', hidden: true},
|
{id: 'id', header: '', hidden: true},
|
||||||
{id: 'title', header: 'Título', adjust:'data', sort: 'string',
|
{id: 'title', header: 'Título', adjust:'data', sort: 'string',
|
||||||
|
@ -160,7 +160,7 @@ var grid_contacts = {
|
||||||
on:{
|
on:{
|
||||||
'data->onStoreUpdated':function(){
|
'data->onStoreUpdated':function(){
|
||||||
this.data.each(function(obj, i){
|
this.data.each(function(obj, i){
|
||||||
obj.index = i+1;
|
obj.index = i + 1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
{
|
||||||
|
"encabezado": {
|
||||||
|
"emisor": {
|
||||||
|
"direccion": {
|
||||||
|
"rectangulo": {"x": 1.0, "y": 26.2, "width": 19.6, "height": 0.4,
|
||||||
|
"radius": 0.1, "stroke": false, "fill": true, "color": "darkred"}
|
||||||
|
},
|
||||||
|
"nombre": {
|
||||||
|
"rectangulo": {"x": 10.6, "y": 25.6, "width": 10.0, "height": 0.4,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 14, "alignment": 2, "textColor": "darkred",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"rfc": {
|
||||||
|
"rectangulo": {"x": 10.6, "y": 25.0, "width": 10.0, "height": 0.4,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "rfc", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 12, "alignment": 2, "textColor": "darkred",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"regimenfiscal": {
|
||||||
|
"rectangulo": {"x": 10.6, "y": 24.4, "width": 10.0, "height": 0.4,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "regimenfiscal", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 7, "alignment": 2, "textColor": "darkred",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"rectangulo": {"x": 1.0, "y": 24.2, "width": 2.5, "height": 2.5}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"receptor": {
|
||||||
|
"titulo": {
|
||||||
|
"valor": "Receptor",
|
||||||
|
"rectangulo": {"x": 1.5, "y": 20.8, "width": 2.8, "height": 0.9,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 9, "alignment": 1, "textColor": "darkred",
|
||||||
|
"backColor": "linen"}
|
||||||
|
},
|
||||||
|
"nombre": {
|
||||||
|
"rectangulo": {"x": 2.0, "y": 23.2, "width": 15.0, "height": 0.4,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 10, "alignment": 0, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"rfc": {
|
||||||
|
"rectangulo": {"x": 2.0, "y": 22.5, "width": 10.0, "height": 0.4,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "rfc", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 9, "alignment": 0, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"usocfdi": {
|
||||||
|
"rectangulo": {"x": 2.0, "y": 20.7, "width": 15.0, "height": 0.4,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "usocfdi", "fontName": "Helvetica",
|
||||||
|
"fontSize": 8, "alignment": 0, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"comprobante": {
|
||||||
|
"titulo": {
|
||||||
|
"valor": "Datos CFDI",
|
||||||
|
"rectangulo": {"x": 14.0, "y": 20.8, "width": 2.8, "height": 0.9,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 9, "alignment": 1, "textColor": "darkred",
|
||||||
|
"backColor": "linen"}
|
||||||
|
},
|
||||||
|
"t_folio": {
|
||||||
|
"valor": "Folio:",
|
||||||
|
"rectangulo": {"x": 14.2, "y": 23.2, "width": 1.0, "height": 0.8,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 8, "alignment": 0, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"folio": {
|
||||||
|
"rectangulo": {"x": 15.1, "y": 23.2, "width": 3.0, "height": 0.4,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 8, "alignment": 0, "textColor": "red",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"t_tipo": {
|
||||||
|
"valor": "Tipo:",
|
||||||
|
"rectangulo": {"x": 18.2, "y": 23.2, "width": 1.0, "height": 0.8,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 8, "alignment": 0, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"tipodecomprobante": {
|
||||||
|
"rectangulo": {"x": 19.0, "y": 23.2, "width": 1.5, "height": 0.8,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 8, "alignment": 0, "textColor": "red",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"t_uuid": {
|
||||||
|
"valor": "Folio Fiscal:",
|
||||||
|
"rectangulo": {"x": 14.2, "y": 22.7, "width": 3.0, "height": 0.4,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 6, "alignment": 0, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"rectangulo": {"x": 15.6, "y": 22.7, "width": 6.5, "height": 0.4,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 6, "alignment": 0, "textColor": "red",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"t_fecha": {
|
||||||
|
"valor": "Fecha Expedición:",
|
||||||
|
"rectangulo": {"x": 14.2, "y": 22.1, "width": 2.2, "height": 0.3,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 6, "alignment": 2, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"fecha": {
|
||||||
|
"rectangulo": {"x": 16.5, "y": 22.1, "width": 3.0, "height": 0.3,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 6, "alignment": 0, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"t_fecha_timbrado": {
|
||||||
|
"valor": "Fecha Timbrado:",
|
||||||
|
"rectangulo": {"x": 14.2, "y": 21.8, "width": 2.2, "height": 0.3,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 6, "alignment": 2, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"fechatimbrado": {
|
||||||
|
"rectangulo": {"x": 16.5, "y": 21.8, "width": 3.0, "height": 0.3,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 6, "alignment": 0, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"t_serie_emisor": {
|
||||||
|
"valor": "Serie CSD Emisor:",
|
||||||
|
"rectangulo": {"x": 14.2, "y": 21.4, "width": 2.2, "height": 0.3,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 6, "alignment": 2, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"nocertificado": {
|
||||||
|
"rectangulo": {"x": 16.5, "y": 21.4, "width": 3.0, "height": 0.3,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 6, "alignment": 0, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"t_serie_sat": {
|
||||||
|
"valor": "Serie CSD SAT:",
|
||||||
|
"rectangulo": {"x": 14.2, "y": 21.1, "width": 2.2, "height": 0.3,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 6, "alignment": 2, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"nocertificadosat": {
|
||||||
|
"rectangulo": {"x": 16.5, "y": 21.1, "width": 3.0, "height": 0.3,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 6, "alignment": 0, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
},
|
||||||
|
"lugarexpedicion": {
|
||||||
|
"rectangulo": {"x": 14.2, "y": 20.7, "width": 5.5, "height": 0.4,
|
||||||
|
"radius": 0.0, "stroke": false, "fill": false},
|
||||||
|
"estilo": {"name": "nombre", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 7, "alignment": 1, "textColor": "black",
|
||||||
|
"backColor": "white"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"conceptos": {
|
||||||
|
},
|
||||||
|
"comprobante": {
|
||||||
|
"totalenletras": {
|
||||||
|
"estilo": {"name": "enletras", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 7, "alignment": 1, "textColor": "black",
|
||||||
|
"spaceBefore": 0.1}
|
||||||
|
},
|
||||||
|
"formadepago": {
|
||||||
|
"estilo": {"name": "formadepago", "fontName": "Helvetica",
|
||||||
|
"fontSize": 7, "alignment": 0, "textColor": "black"}
|
||||||
|
},
|
||||||
|
"metododepago": {
|
||||||
|
"estilo": {"name": "metododepago", "fontName": "Helvetica",
|
||||||
|
"fontSize": 7, "alignment": 0, "textColor": "black"}
|
||||||
|
},
|
||||||
|
"moneda": {
|
||||||
|
"estilo": {"name": "moneda", "fontName": "Helvetica",
|
||||||
|
"fontSize": 7, "alignment": 0, "textColor": "black"}
|
||||||
|
},
|
||||||
|
"tipocambio": {
|
||||||
|
"estilo": {"name": "tipocambio", "fontName": "Helvetica",
|
||||||
|
"fontSize": 7, "alignment": 0, "textColor": "black"}
|
||||||
|
},
|
||||||
|
"leyenda": {
|
||||||
|
"estilo": {"name": "leyenda", "fontName": "Helvetica-Bold",
|
||||||
|
"fontSize": 6, "alignment": 1, "textColor": "black",
|
||||||
|
"spaceBefore": 0.2}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue