forked from elmau/empresa-libre
Factura HTML v1
This commit is contained in:
parent
7dc65a2a2b
commit
0d52e9b570
|
@ -16,9 +16,11 @@
|
||||||
# ~ You should have received a copy of the GNU General Public License
|
# ~ You should have received a copy of the GNU General Public License
|
||||||
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
import getpass
|
import getpass
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
import locale
|
import locale
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
@ -68,6 +70,13 @@ from settings import SEAFILE_SERVER, USAR_TOKEN, API, DECIMALES_TAX
|
||||||
from .configpac import AUTH
|
from .configpac import AUTH
|
||||||
|
|
||||||
|
|
||||||
|
# ~ v2
|
||||||
|
from settings import (
|
||||||
|
MXN,
|
||||||
|
PATHS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _call(args):
|
def _call(args):
|
||||||
return subprocess.check_output(args, shell=True).decode()
|
return subprocess.check_output(args, shell=True).decode()
|
||||||
|
|
||||||
|
@ -650,7 +659,7 @@ class LIBO(object):
|
||||||
self._ctx = None
|
self._ctx = None
|
||||||
self._sm = None
|
self._sm = None
|
||||||
self._desktop = None
|
self._desktop = None
|
||||||
self._currency = 'MXN'
|
self._currency = MXN
|
||||||
self._total_cantidades = 0
|
self._total_cantidades = 0
|
||||||
if self.is_running:
|
if self.is_running:
|
||||||
ctx = uno.getComponentContext()
|
ctx = uno.getComponentContext()
|
||||||
|
@ -1583,11 +1592,11 @@ def to_pdf(data, emisor_rfc, ods=False):
|
||||||
|
|
||||||
def format_currency(value, currency, digits=2):
|
def format_currency(value, currency, digits=2):
|
||||||
c = {
|
c = {
|
||||||
'MXN': '$',
|
MXN: '$',
|
||||||
'USD': '$',
|
'USD': '$',
|
||||||
'EUR': '€',
|
'EUR': '€',
|
||||||
}
|
}
|
||||||
s = c.get(currency, 'MXN')
|
s = c.get(currency, MXN)
|
||||||
return f'{s} {float(value):,.{digits}f}'
|
return f'{s} {float(value):,.{digits}f}'
|
||||||
|
|
||||||
|
|
||||||
|
@ -1597,7 +1606,7 @@ def to_html(data):
|
||||||
template = template_lookup.get_template(name)
|
template = template_lookup.get_template(name)
|
||||||
except TopLevelLookupException:
|
except TopLevelLookupException:
|
||||||
template = template_lookup.get_template('plantilla_factura.html')
|
template = template_lookup.get_template('plantilla_factura.html')
|
||||||
data['rfc'] = 'estilos'
|
data['rfc'] = 'invoice'
|
||||||
|
|
||||||
return template.render(**data)
|
return template.render(**data)
|
||||||
|
|
||||||
|
@ -1651,11 +1660,16 @@ def to_letters(value, currency):
|
||||||
return NumLet(value, currency).letras
|
return NumLet(value, currency).letras
|
||||||
|
|
||||||
|
|
||||||
def get_qr(data):
|
def get_qr(data, p=True):
|
||||||
path = get_path_temp('.qr')
|
|
||||||
qr = pyqrcode.create(data, mode='binary')
|
qr = pyqrcode.create(data, mode='binary')
|
||||||
qr.png(path, scale=7)
|
if p:
|
||||||
return path
|
path = get_path_temp('.qr')
|
||||||
|
qr.png(path, scale=7)
|
||||||
|
return path
|
||||||
|
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
qr.png(buffer, scale=8)
|
||||||
|
return base64.b64encode(buffer.getvalue()).decode()
|
||||||
|
|
||||||
|
|
||||||
def _get_relacionados(doc, version):
|
def _get_relacionados(doc, version):
|
||||||
|
@ -1907,6 +1921,7 @@ def _timbre(doc, version, values):
|
||||||
'sello': '&fe={}'.format(data['sellocfd'][-8:]),
|
'sello': '&fe={}'.format(data['sellocfd'][-8:]),
|
||||||
}
|
}
|
||||||
qr_data = '{url}{uuid}{emisor}{receptor}{total}{sello}'.format(**qr_data)
|
qr_data = '{url}{uuid}{emisor}{receptor}{total}{sello}'.format(**qr_data)
|
||||||
|
|
||||||
data['path_cbb'] = get_qr(qr_data)
|
data['path_cbb'] = get_qr(qr_data)
|
||||||
data['cadenaoriginal'] = CADENA.format(**data)
|
data['cadenaoriginal'] = CADENA.format(**data)
|
||||||
return data
|
return data
|
||||||
|
@ -2110,11 +2125,7 @@ def get_date(value, next_day=False):
|
||||||
|
|
||||||
|
|
||||||
def upload_file(rfc, opt, file_obj):
|
def upload_file(rfc, opt, file_obj):
|
||||||
if opt == 'emisorlogo':
|
if opt == 'txt_plantilla_factura_32':
|
||||||
tmp = file_obj.filename.split('.')
|
|
||||||
name = '{}.{}'.format(rfc.lower(), tmp[-1].lower())
|
|
||||||
path = _join(PATH_MEDIA, 'logos', name)
|
|
||||||
elif opt == 'txt_plantilla_factura_32':
|
|
||||||
tmp = file_obj.filename.split('.')
|
tmp = file_obj.filename.split('.')
|
||||||
ext = tmp[-1].lower()
|
ext = tmp[-1].lower()
|
||||||
if ext != 'ods':
|
if ext != 'ods':
|
||||||
|
|
|
@ -8,7 +8,6 @@ from middleware import (
|
||||||
AuthMiddleware,
|
AuthMiddleware,
|
||||||
JSONTranslator,
|
JSONTranslator,
|
||||||
ConnectionMiddleware,
|
ConnectionMiddleware,
|
||||||
static,
|
|
||||||
handle_404
|
handle_404
|
||||||
)
|
)
|
||||||
from models.db import StorageEngine
|
from models.db import StorageEngine
|
||||||
|
@ -64,9 +63,6 @@ api.add_route('/satformapago', AppSATFormaPago(db))
|
||||||
api.add_route('/socioscb', AppSociosCuentasBanco(db))
|
api.add_route('/socioscb', AppSociosCuentasBanco(db))
|
||||||
|
|
||||||
|
|
||||||
# ~ Activa si usas waitress y NO estas usando servidor web
|
|
||||||
# ~ api.add_sink(static, '/static')
|
|
||||||
|
|
||||||
session_options = {
|
session_options = {
|
||||||
'session.type': 'file',
|
'session.type': 'file',
|
||||||
'session.cookie_expires': True,
|
'session.cookie_expires': True,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import falcon
|
import falcon
|
||||||
from controllers import util
|
from controllers import util
|
||||||
from models import main
|
from models import main
|
||||||
from settings import MV, PATH_STATIC
|
from settings import MV
|
||||||
|
|
||||||
|
|
||||||
def handle_404(req, resp):
|
def handle_404(req, resp):
|
||||||
|
@ -20,14 +20,14 @@ def get_template(req, resp, resource):
|
||||||
resp.body = util.get_template(resource.template, data)
|
resp.body = util.get_template(resource.template, data)
|
||||||
|
|
||||||
|
|
||||||
def static(req, res):
|
# ~ def static(req, res):
|
||||||
path = PATH_STATIC + req.path
|
# ~ path = PATH_STATIC + req.path
|
||||||
if util.is_file(path):
|
# ~ if util.is_file(path):
|
||||||
res.content_type = util.get_mimetype(path)
|
# ~ res.content_type = util.get_mimetype(path)
|
||||||
res.stream, res.stream_len = util.get_stream(path)
|
# ~ res.stream, res.stream_len = util.get_stream(path)
|
||||||
res.status = falcon.HTTP_200
|
# ~ res.status = falcon.HTTP_200
|
||||||
else:
|
# ~ else:
|
||||||
res.status = falcon.HTTP_404
|
# ~ res.status = falcon.HTTP_404
|
||||||
|
|
||||||
|
|
||||||
class AuthMiddleware(object):
|
class AuthMiddleware(object):
|
||||||
|
|
|
@ -37,6 +37,12 @@ from settings import log, DEBUG, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI,
|
||||||
DEFAULT_SAT_NOMINA, DECIMALES_TAX, TITLE_APP, MV, DECIMALES_PRECIOS, \
|
DEFAULT_SAT_NOMINA, DECIMALES_TAX, TITLE_APP, MV, DECIMALES_PRECIOS, \
|
||||||
DEFAULT_CFDIPAY, CURRENCY_MN
|
DEFAULT_CFDIPAY, CURRENCY_MN
|
||||||
|
|
||||||
|
from settings import (
|
||||||
|
EXT,
|
||||||
|
MXN,
|
||||||
|
PATHS,
|
||||||
|
VALUES_PDF,
|
||||||
|
)
|
||||||
|
|
||||||
FORMAT = '{0:.2f}'
|
FORMAT = '{0:.2f}'
|
||||||
FORMAT3 = '{0:.3f}'
|
FORMAT3 = '{0:.3f}'
|
||||||
|
@ -97,9 +103,11 @@ def upload_file(rfc, opt, file_obj):
|
||||||
else:
|
else:
|
||||||
return Facturas.import_cfdi(xml, sxml)
|
return Facturas.import_cfdi(xml, sxml)
|
||||||
|
|
||||||
|
if opt == 'emisorlogo':
|
||||||
|
return Emisor.save_logo(file_obj)
|
||||||
|
|
||||||
result = util.upload_file(rfc, opt, file_obj)
|
result = util.upload_file(rfc, opt, file_obj)
|
||||||
if result['ok']:
|
if result['ok']:
|
||||||
|
|
||||||
names = ('bdfl', 'employees', 'nomina', 'products', 'invoiceods')
|
names = ('bdfl', 'employees', 'nomina', 'products', 'invoiceods')
|
||||||
if not opt in names:
|
if not opt in names:
|
||||||
Configuracion.add({opt: file_obj.filename})
|
Configuracion.add({opt: file_obj.filename})
|
||||||
|
@ -765,6 +773,10 @@ class SATRegimenes(BaseModel):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{} ({})'.format(self.name, self.key)
|
return '{} ({})'.format(self.name, self.key)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_key(cls, key):
|
||||||
|
return SATRegimenes.get(SATRegimenes.key==key)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_(cls, ids):
|
def get_(cls, ids):
|
||||||
if isinstance(ids, int):
|
if isinstance(ids, int):
|
||||||
|
@ -817,12 +829,30 @@ class Emisor(BaseModel):
|
||||||
registro_patronal = TextField(default='')
|
registro_patronal = TextField(default='')
|
||||||
regimenes = ManyToManyField(SATRegimenes, related_name='emisores')
|
regimenes = ManyToManyField(SATRegimenes, related_name='emisores')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
order_by = ('nombre',)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
t = '{} ({})'
|
t = '{} ({})'
|
||||||
return t.format(self.nombre, self.rfc)
|
return t.format(self.nombre, self.rfc)
|
||||||
|
|
||||||
class Meta:
|
@classmethod
|
||||||
order_by = ('nombre',)
|
def save_logo(cls, file_obj):
|
||||||
|
result = {'status': 'error', 'ok': False}
|
||||||
|
name = file_obj.filename.split('.')
|
||||||
|
if name[-1].lower() != EXT['PNG']:
|
||||||
|
return result
|
||||||
|
|
||||||
|
emisor = Emisor.select()[0]
|
||||||
|
rfc = emisor.rfc.lower()
|
||||||
|
name = f'{rfc}.png'
|
||||||
|
path = util._join(PATHS['STATIC'], 'img', name)
|
||||||
|
if util.save_file(path, file_obj.file.read()):
|
||||||
|
emisor.logo = file_obj.filename
|
||||||
|
emisor.save()
|
||||||
|
result = {'status': 'server', 'name': file_obj.filename, 'ok': True}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_(cls, rfc):
|
def get_(cls, rfc):
|
||||||
|
@ -850,6 +880,7 @@ class Emisor(BaseModel):
|
||||||
'emisor_telefono': obj.telefono,
|
'emisor_telefono': obj.telefono,
|
||||||
'emisor_correo': obj.correo,
|
'emisor_correo': obj.correo,
|
||||||
'emisor_web': obj.web,
|
'emisor_web': obj.web,
|
||||||
|
'emisor_logo': obj.logo,
|
||||||
'es_escuela': obj.es_escuela,
|
'es_escuela': obj.es_escuela,
|
||||||
'es_ong': obj.es_ong,
|
'es_ong': obj.es_ong,
|
||||||
'ong_autorizacion': obj.autorizacion,
|
'ong_autorizacion': obj.autorizacion,
|
||||||
|
@ -1369,6 +1400,10 @@ class SATMonedas(BaseModel):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'Moneda: ({}) {}'.format(self.key, self.name)
|
return 'Moneda: ({}) {}'.format(self.key, self.name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_key(cls, key):
|
||||||
|
return SATMonedas.get(SATMonedas.key==key)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_multi_currency(cls):
|
def get_multi_currency(cls):
|
||||||
count_currencies = len(cls.get_activos())
|
count_currencies = len(cls.get_activos())
|
||||||
|
@ -1949,6 +1984,10 @@ class SATUsoCfdi(BaseModel):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'Uso del CFDI: {} ({})'.format(self.name, self.key)
|
return 'Uso del CFDI: {} ({})'.format(self.name, self.key)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_key(cls, key):
|
||||||
|
return SATUsoCfdi.get(SATUsoCfdi.key==key)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def actualizar(self, values):
|
def actualizar(self, values):
|
||||||
id = int(values['id'])
|
id = int(values['id'])
|
||||||
|
@ -3674,26 +3713,40 @@ class Facturas(BaseModel):
|
||||||
|
|
||||||
return doc, name
|
return doc, name
|
||||||
|
|
||||||
|
def _get_concepto(self, node, currency):
|
||||||
|
d = util.get_dict(node.attrib)
|
||||||
|
concepto = {
|
||||||
|
'clave': f"{d['noidentificacion']}<BR>({d['claveprodserv']})",
|
||||||
|
'descripcion': self._get_description(self, node, d),
|
||||||
|
'unidad': f"{d['unidad']}<BR>({d['claveunidad']})",
|
||||||
|
'cantidad': d['cantidad'],
|
||||||
|
'valorunitario': util.format_currency(d['valorunitario'], currency),
|
||||||
|
'importe': util.format_currency(d['importe'], currency),
|
||||||
|
}
|
||||||
|
return concepto
|
||||||
|
|
||||||
def _get_description(self, node, data):
|
def _get_description(self, node, data):
|
||||||
return data['descripcion']
|
return data['descripcion']
|
||||||
|
|
||||||
def _get_others_values(self, invoice, emisor):
|
def _get_tax(self, node, data, currency):
|
||||||
v_cancel = {True: 'inline', False: 'none'}
|
type_tax = 'Traslado'
|
||||||
v_type = {'I': 'Ingreso', 'E': 'Egreso', 'T': 'Traslado'}
|
if 'Retenciones' in node.tag:
|
||||||
v_method = {
|
type_tax = 'Retención'
|
||||||
'PUE': 'Pago en una sola exhibición',
|
|
||||||
'PPD': 'Pago en parcialidades o diferido',
|
|
||||||
}
|
|
||||||
v_tax = {
|
|
||||||
'001': 'ISR',
|
|
||||||
'002': 'IVA',
|
|
||||||
'003': 'IEPS',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for n in node:
|
||||||
|
d = util.get_dict(n.attrib)
|
||||||
|
name = VALUES_PDF['TAX'].get(d['impuesto'])
|
||||||
|
tasa = FORMAT.format(float(d['tasaocuota']))
|
||||||
|
title = f"{type_tax} {name} {tasa}"
|
||||||
|
importe = util.format_currency(d['importe'], currency)
|
||||||
|
data['totales'].append((title, importe))
|
||||||
|
return
|
||||||
|
|
||||||
|
def _get_others_values(self, invoice, emisor):
|
||||||
data = {
|
data = {
|
||||||
'rfc': emisor.rfc.lower(),
|
'rfc': emisor.rfc.lower(),
|
||||||
'version': invoice.version,
|
'version': invoice.version,
|
||||||
'cancelada': v_cancel.get(invoice.cancelada),
|
'cancelada': VALUES_PDF['CANCEL'].get(invoice.cancelada),
|
||||||
'cfdi_notas': invoice.notas,
|
'cfdi_notas': invoice.notas,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3704,17 +3757,18 @@ class Facturas(BaseModel):
|
||||||
|
|
||||||
data.update({f'cfdi_{k.lower()}': v for k, v in d.items()})
|
data.update({f'cfdi_{k.lower()}': v for k, v in d.items()})
|
||||||
|
|
||||||
data['cfdi_tipodecomprobante'] = v_type[data['cfdi_tipodecomprobante']]
|
data['cfdi_tipodecomprobante'] = \
|
||||||
|
VALUES_PDF['TYPE'][data['cfdi_tipodecomprobante']]
|
||||||
|
|
||||||
if data.get('cfdi_formapago', ''):
|
if data.get('cfdi_formapago', ''):
|
||||||
obj = SATFormaPago.get(SATFormaPago.key==invoice.forma_pago)
|
data['cfdi_formapago'] = str(
|
||||||
data['cfdi_formapago'] = str(obj)
|
SATFormaPago.get_by_key(data['cfdi_formapago']))
|
||||||
if data.get('cfdi_metodopago', ''):
|
if data.get('cfdi_metodopago', ''):
|
||||||
data['cfdi_metodopago'] = 'Método de Pago: ({}) {}'.format(
|
data['cfdi_metodopago'] = 'Método de Pago: ({}) {}'.format(
|
||||||
data['cfdi_metodopago'], v_method[data['cfdi_metodopago']])
|
data['cfdi_metodopago'],
|
||||||
|
VALUES_PDF['METHOD'][data['cfdi_metodopago']])
|
||||||
|
|
||||||
obj = SATMonedas.get(SATMonedas.key==currency)
|
data['cfdi_moneda'] = str(SATMonedas.get_by_key(currency))
|
||||||
data['cfdi_moneda'] = str(obj)
|
|
||||||
data['cfdi_tipocambio'] = util.format_currency(
|
data['cfdi_tipocambio'] = util.format_currency(
|
||||||
data['cfdi_tipocambio'], currency, 4)
|
data['cfdi_tipocambio'], currency, 4)
|
||||||
data['cfdi_totalenletras'] = util.to_letters(
|
data['cfdi_totalenletras'] = util.to_letters(
|
||||||
|
@ -3725,60 +3779,30 @@ class Facturas(BaseModel):
|
||||||
|
|
||||||
for node in xml:
|
for node in xml:
|
||||||
if 'Emisor' in node.tag:
|
if 'Emisor' in node.tag:
|
||||||
d = util.get_dict(node.attrib)
|
d = {f'emisor_{k.lower()}': v for k, v in node.attrib.items()}
|
||||||
data.update(
|
data.update(d)
|
||||||
{f'emisor_{k.lower()}': v for k, v in d.items()}
|
|
||||||
)
|
|
||||||
elif 'Receptor' in node.tag:
|
elif 'Receptor' in node.tag:
|
||||||
d = util.get_dict(node.attrib)
|
d = {f'receptor_{k.lower()}': v for k, v in node.attrib.items()}
|
||||||
data.update(
|
data.update(d)
|
||||||
{f'receptor_{k.lower()}': v for k, v in d.items()}
|
|
||||||
)
|
|
||||||
elif 'Conceptos' in node.tag:
|
elif 'Conceptos' in node.tag:
|
||||||
data['conceptos'] = []
|
data['conceptos'] = []
|
||||||
for subnode in node:
|
for subnode in node:
|
||||||
d = util.get_dict(subnode.attrib)
|
concepto = self._get_concepto(self, subnode, currency)
|
||||||
concepto = {
|
|
||||||
'clave': f"{d['noidentificacion']}<BR>({d['claveprodserv']})",
|
|
||||||
'descripcion': self._get_description(self, subnode, d),
|
|
||||||
'unidad': f"{d['unidad']}<BR>({d['claveunidad']})",
|
|
||||||
'cantidad': d['cantidad'],
|
|
||||||
'valorunitario': util.format_currency(d['valorunitario'], currency),
|
|
||||||
'importe': util.format_currency(d['importe'], currency),
|
|
||||||
}
|
|
||||||
data['conceptos'].append(concepto)
|
data['conceptos'].append(concepto)
|
||||||
elif 'Impuestos' in node.tag:
|
elif 'Impuestos' in node.tag:
|
||||||
for subnode in node:
|
for subnode in node:
|
||||||
if 'Traslados' in subnode.tag:
|
self._get_tax(self, subnode, data, currency)
|
||||||
for t in subnode:
|
|
||||||
d = util.get_dict(t.attrib)
|
|
||||||
name = v_tax.get(d['impuesto'])
|
|
||||||
tasa = FORMAT.format(float(d['tasaocuota']))
|
|
||||||
title = f"Traslado {name} {tasa}"
|
|
||||||
importe = util.format_currency(d['importe'], currency)
|
|
||||||
data['totales'].append((title, importe))
|
|
||||||
elif 'Retenciones' in subnode.tag:
|
|
||||||
for r in subnode:
|
|
||||||
d = util.get_dict(r.attrib)
|
|
||||||
name = v_tax.get(d['impuesto'])
|
|
||||||
tasa = FORMAT.format(float(d['tasaocuota']))
|
|
||||||
title = f"Retención {name} {tasa}"
|
|
||||||
importe = util.format_currency(d['importe'], currency)
|
|
||||||
data['totales'].append((title, importe))
|
|
||||||
elif 'Complemento' in node.tag:
|
elif 'Complemento' in node.tag:
|
||||||
for subnode in node:
|
for sn in node:
|
||||||
if 'TimbreFiscalDigital' in subnode.tag:
|
if 'TimbreFiscalDigital' in sn.tag:
|
||||||
d = util.get_dict(subnode.attrib)
|
d = {f'timbre_{k.lower()}': v for k, v in sn.attrib.items()}
|
||||||
data.update(
|
data.update(d)
|
||||||
{f'timbre_{k.lower()}': v for k, v in d.items()}
|
|
||||||
)
|
|
||||||
|
|
||||||
obj = SATRegimenes.get(SATRegimenes.key==data['emisor_regimenfiscal'])
|
|
||||||
data['emisor_regimenfiscal'] = str(obj)
|
|
||||||
data['emisor_logo'] = data['emisor_rfc'].lower()
|
data['emisor_logo'] = data['emisor_rfc'].lower()
|
||||||
|
data['emisor_regimenfiscal'] = str(
|
||||||
obj = SATUsoCfdi.get(SATUsoCfdi.key==data['receptor_usocfdi'])
|
SATRegimenes.get_by_key(data['emisor_regimenfiscal']))
|
||||||
data['receptor_usocfdi'] = str(obj)
|
data['receptor_usocfdi'] = str(
|
||||||
|
SATUsoCfdi.get_by_key(data['receptor_usocfdi']))
|
||||||
|
|
||||||
data['totales'].append((
|
data['totales'].append((
|
||||||
'Total', util.format_currency(data['cfdi_total'], currency)))
|
'Total', util.format_currency(data['cfdi_total'], currency)))
|
||||||
|
@ -3786,7 +3810,16 @@ class Facturas(BaseModel):
|
||||||
data['timbre_cadenaoriginal'] = f"""||{data['timbre_version']}|
|
data['timbre_cadenaoriginal'] = f"""||{data['timbre_version']}|
|
||||||
{data['timbre_uuid']}|{data['timbre_fechatimbrado']}|
|
{data['timbre_uuid']}|{data['timbre_fechatimbrado']}|
|
||||||
{data['timbre_sellocfd']}|{data['timbre_nocertificadosat']}||"""
|
{data['timbre_sellocfd']}|{data['timbre_nocertificadosat']}||"""
|
||||||
# ~ print(data)
|
|
||||||
|
qr_data = (
|
||||||
|
f"https://verificacfdi.facturaelectronica.sat.gob.mx/"
|
||||||
|
f"default.aspx?&id={data['timbre_uuid']}&re={data['emisor_rfc']}"
|
||||||
|
f"&rr={data['receptor_rfc']}&tt={data['cfdi_total']}"
|
||||||
|
f"&fe={data['cfdi_sello'][-8:]}"
|
||||||
|
)
|
||||||
|
cbb = util.get_qr(qr_data, False)
|
||||||
|
data['cbb'] = f'data:image/png;base64,{cbb}'
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -47,13 +47,15 @@ except ImportError:
|
||||||
|
|
||||||
|
|
||||||
DEBUG = DEBUG
|
DEBUG = DEBUG
|
||||||
VERSION = '1.25.0'
|
VERSION = '1.26.0'
|
||||||
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
|
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
|
||||||
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
|
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
|
||||||
|
|
||||||
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, '..', 'static'))
|
||||||
|
# ~ 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_MEDIA = os.path.abspath(os.path.join(BASE_DIR, '..', 'docs'))
|
||||||
|
|
||||||
|
@ -185,3 +187,21 @@ DEFAULT_SAT_NOMINA = {
|
||||||
API = 'https://api.empresalibre.net{}'
|
API = 'https://api.empresalibre.net{}'
|
||||||
|
|
||||||
CURRENCY_MN = 'MXN'
|
CURRENCY_MN = 'MXN'
|
||||||
|
|
||||||
|
# ~ v2
|
||||||
|
EXT = {
|
||||||
|
'PNG': 'png',
|
||||||
|
}
|
||||||
|
MXN = 'MXN'
|
||||||
|
PATHS = {
|
||||||
|
'STATIC': path_static,
|
||||||
|
}
|
||||||
|
VALUES_PDF = {
|
||||||
|
'CANCEL': {True: 'inline', False: 'none'},
|
||||||
|
'TYPE': {'I': 'Ingreso', 'E': 'Egreso', 'T': 'Traslado'},
|
||||||
|
'TAX': {'001': 'ISR', '002': 'IVA', '003': 'IEPS'},
|
||||||
|
'METHOD': {
|
||||||
|
'PUE': 'Pago en una sola exhibición',
|
||||||
|
'PPD': 'Pago en parcialidades o diferido',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -129,11 +129,12 @@ table.subtotal td{
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: rgb(204,204,204);
|
|
||||||
font-family: 'Avenir Next';
|
font-family: 'Avenir Next';
|
||||||
}
|
}
|
||||||
|
|
||||||
#plantilla {
|
#plantilla {
|
||||||
|
border: 1px solid #fff;
|
||||||
|
border-color: rgb(204,204,204);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
@ -435,26 +436,26 @@ table.subtotal td{
|
||||||
float: left;
|
float: left;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
line-height: 12px;
|
line-height: 12px;
|
||||||
width: 85%;
|
width: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tipocomite, .tipoproceso, .idcontabilidad{
|
.tipocomite, .tipoproceso, .idcontabilidad{
|
||||||
float: right;
|
float: right;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
line-height: 12px;
|
line-height: 12px;
|
||||||
width: 30%;
|
width: 20%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sello{
|
.sello{
|
||||||
margin: 20px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sello .cbb{
|
.sello .cbb{
|
||||||
border: 1px solid #000;
|
border: 1px solid #000;
|
||||||
height: auto;
|
height: auto;
|
||||||
margin-top: 20px;
|
margin-top: 10px;
|
||||||
margin-right: 5%;
|
margin-right: 1%;
|
||||||
width: 15%;
|
width: 18%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sello .cadenas-sello{
|
.sello .cadenas-sello{
|
||||||
|
@ -462,7 +463,7 @@ table.subtotal td{
|
||||||
float: right;
|
float: right;
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
line-height: 16px;
|
line-height: 15px;
|
||||||
width: 79%;
|
width: 79%;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
|
@ -479,20 +480,17 @@ table.subtotal td{
|
||||||
color: #7d1916;
|
color: #7d1916;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sello .cadena-original{
|
.cadena-original{
|
||||||
color: #000;
|
color: #000;
|
||||||
float: right;
|
float: right;
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
line-height: 16px;
|
line-height: 15px;
|
||||||
width: 100%;
|
width: 99%;
|
||||||
margin: 10px;
|
margin: 5px;
|
||||||
margin-left: 5px;
|
|
||||||
margin-right: 2px;
|
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
.cadena-original div{
|
||||||
.sello .cadena-original div{
|
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
background-color: #dbc5c6;
|
background-color: #dbc5c6;
|
||||||
color: #7d1916;
|
color: #7d1916;
|
||||||
|
@ -502,7 +500,7 @@ table.subtotal td{
|
||||||
background-color: #dbc5c6;
|
background-color: #dbc5c6;
|
||||||
color: #7d1916;
|
color: #7d1916;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
line-height: 16px;
|
line-height: 15px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-right: 2px;
|
margin: 5px;
|
||||||
}
|
}
|
|
@ -204,7 +204,7 @@ var emisor_otros_datos= [
|
||||||
{template: 'Generales', type: 'section'},
|
{template: 'Generales', type: 'section'},
|
||||||
{cols: [
|
{cols: [
|
||||||
{view: 'search', id: 'emisor_logo', icon: 'file-image-o',
|
{view: 'search', id: 'emisor_logo', icon: 'file-image-o',
|
||||||
name: 'emisor_logo', label: 'Logotipo: '},
|
name: 'emisor_logo', label: 'Logotipo: ', placeholder: 'Solo formato PNG'},
|
||||||
{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: '},
|
||||||
]},
|
]},
|
||||||
|
@ -244,7 +244,7 @@ var emisor_otros_datos= [
|
||||||
{view: 'text', id: 'token_timbrado',
|
{view: 'text', id: 'token_timbrado',
|
||||||
name: 'token_timbrado', label: 'Token de Timbrado: '},
|
name: 'token_timbrado', label: 'Token de Timbrado: '},
|
||||||
{view: 'text', id: 'token_soporte',
|
{view: 'text', id: 'token_soporte',
|
||||||
name: 'token_soporte', label: 'Token para Respaldos: '},
|
name: 'token_soporte', label: 'Token de Soporte: '},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/css/${rfc}.css">
|
<link rel="stylesheet" type="text/css" href="/static/css/${rfc}.css">
|
||||||
<title>Empresa Libre</title>
|
<title>Empresa Libre</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body onload="getQR();">
|
||||||
<div id="plantilla">
|
<div id="plantilla">
|
||||||
<div class="cancelada" style="display: ${cancelada}">
|
<div class="cancelada" style="display: ${cancelada}">
|
||||||
Cancelada
|
Cancelada
|
||||||
|
@ -122,6 +122,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
% endfor
|
% endfor
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
<div class="condiciones-pago">
|
<div class="condiciones-pago">
|
||||||
${cfdi_condicionespago}
|
${cfdi_condicionespago}
|
||||||
|
@ -153,8 +154,7 @@
|
||||||
|
|
||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
<div class="sello">
|
<div class="sello">
|
||||||
<img class="cbb" src="img/cbb.png" />
|
<img id="id_cbb" class="cbb" src="${cbb}" />
|
||||||
|
|
||||||
<div class="cadenas-sello">
|
<div class="cadenas-sello">
|
||||||
<div class="cadena">
|
<div class="cadena">
|
||||||
Sello Digital del CFDI:
|
Sello Digital del CFDI:
|
||||||
|
@ -171,14 +171,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sello">
|
<div class="clear"></div>
|
||||||
<div class="cadena-original">
|
<div class="cadena-original">
|
||||||
Cadena original del complemento de certificación digital del SAT:
|
Cadena original del complemento de certificación digital del SAT:
|
||||||
<div>
|
<div>
|
||||||
${timbre_cadenaoriginal}
|
${timbre_cadenaoriginal}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
<div class="rfc-pac">
|
<div class="rfc-pac">
|
||||||
|
|
Loading…
Reference in New Issue