Merge branch 'develop'
Generar PDF 3.2 y 3.3. Mostrar CFDI relacionados en PDF
This commit is contained in:
commit
4a0089ae76
|
@ -117,7 +117,7 @@ class CFDI(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
def _relacionados(self, datos):
|
def _relacionados(self, datos):
|
||||||
if not datos['tipo'] or not datos['cfdis']:
|
if not datos or not datos['tipo'] or not datos['cfdis']:
|
||||||
return
|
return
|
||||||
|
|
||||||
node_name = '{}:CfdiRelacionados'.format(self._pre)
|
node_name = '{}:CfdiRelacionados'.format(self._pre)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import datetime
|
||||||
import getpass
|
import getpass
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
import locale
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -853,7 +854,7 @@ def to_letters(value, moneda):
|
||||||
'USD': 'dólar',
|
'USD': 'dólar',
|
||||||
'EUR': 'euro',
|
'EUR': 'euro',
|
||||||
}
|
}
|
||||||
return NumLet(value, monedas[moneda]).letras
|
return NumLet(value, monedas.get(moneda, moneda)).letras
|
||||||
|
|
||||||
|
|
||||||
def get_qr(data):
|
def get_qr(data):
|
||||||
|
@ -863,8 +864,17 @@ def get_qr(data):
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
def _comprobante(values, options):
|
def _get_relacionados(doc, version):
|
||||||
data = CaseInsensitiveDict(values)
|
node = doc.find('{}CfdiRelacionados'.format(PRE[version]))
|
||||||
|
if node is None:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
uuids = ['UUID: {}'.format(n.attrib['UUID']) for n in node.getchildren()]
|
||||||
|
return '\n'.join(uuids)
|
||||||
|
|
||||||
|
|
||||||
|
def _comprobante(doc, options):
|
||||||
|
data = CaseInsensitiveDict(doc.attrib.copy())
|
||||||
del data['certificado']
|
del data['certificado']
|
||||||
|
|
||||||
data['totalenletras'] = to_letters(float(data['total']), data['moneda'])
|
data['totalenletras'] = to_letters(float(data['total']), data['moneda'])
|
||||||
|
@ -883,21 +893,49 @@ def _comprobante(values, options):
|
||||||
data['condicionesdepago'] = \
|
data['condicionesdepago'] = \
|
||||||
'Condiciones de pago: {}'.format(data['condicionesdepago'])
|
'Condiciones de pago: {}'.format(data['condicionesdepago'])
|
||||||
data['moneda'] = options['moneda']
|
data['moneda'] = options['moneda']
|
||||||
|
data['tiporelacion'] = options.get('tiporelacion', '')
|
||||||
|
data['relacionados'] = _get_relacionados(doc, data['version'])
|
||||||
|
else:
|
||||||
|
fields = {
|
||||||
|
'formaDePago': 'Forma de Pago: {}\n',
|
||||||
|
'metodoDePago': 'Método de pago: {}\n',
|
||||||
|
'condicionesDePago': 'Condiciones de Pago: {}\n',
|
||||||
|
'NumCtaPago': 'Número de Cuenta de Pago: {}\n',
|
||||||
|
'Moneda': 'Moneda: {}\n',
|
||||||
|
'TipoCambio': 'Tipo de Cambio: {}',
|
||||||
|
}
|
||||||
|
datos = ''
|
||||||
|
for k, v in fields.items():
|
||||||
|
if k in data:
|
||||||
|
datos += v.format(data[k])
|
||||||
|
data['datos'] = datos
|
||||||
|
|
||||||
|
fecha = parser.parse(data['fecha'])
|
||||||
|
try:
|
||||||
|
locale.setlocale(locale.LC_TIME, "es_MX.UTF-8")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
data['fechaformato'] = fecha.strftime('%A, %d de %B de %Y')
|
||||||
|
|
||||||
data['tipocambio'] = 'Tipo de Cambio: $ {:0.2f}'.format(
|
data['tipocambio'] = 'Tipo de Cambio: $ {:0.2f}'.format(
|
||||||
float(data['tipocambio']))
|
float(data['tipocambio']))
|
||||||
if 'serie' in data:
|
|
||||||
data['folio'] = '{}-{}'.format(data['serie'], data['folio'])
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def _emisor(doc, version, values):
|
def _emisor(doc, version, values):
|
||||||
node = doc.find('{}Emisor'.format(PRE[version]))
|
emisor = doc.find('{}Emisor'.format(PRE[version]))
|
||||||
data = CaseInsensitiveDict(node.attrib.copy())
|
data = CaseInsensitiveDict(emisor.attrib.copy())
|
||||||
node = node.find('{}DomicilioFiscal'.format(PRE[version]))
|
node = emisor.find('{}DomicilioFiscal'.format(PRE[version]))
|
||||||
if not node is None:
|
if not node is None:
|
||||||
data.update(CaseInsensitiveDict(node.attrib.copy()))
|
data.update(CaseInsensitiveDict(node.attrib.copy()))
|
||||||
data['regimenfiscal'] = values['regimenfiscal']
|
|
||||||
|
if version == '3.2':
|
||||||
|
node = emisor.find('{}RegimenFiscal'.format(PRE[version]))
|
||||||
|
if not node is None:
|
||||||
|
data['regimenfiscal'] = node.attrib['Regimen']
|
||||||
|
else:
|
||||||
|
data['regimenfiscal'] = values['regimenfiscal']
|
||||||
|
|
||||||
path = _join(PATH_MEDIA, 'logos', '{}.png'.format(data['rfc'].lower()))
|
path = _join(PATH_MEDIA, 'logos', '{}.png'.format(data['rfc'].lower()))
|
||||||
if is_file(path):
|
if is_file(path):
|
||||||
|
@ -912,6 +950,10 @@ def _receptor(doc, version, values):
|
||||||
node = node.find('{}Domicilio'.format(PRE[version]))
|
node = node.find('{}Domicilio'.format(PRE[version]))
|
||||||
if not node is None:
|
if not node is None:
|
||||||
data.update(node.attrib.copy())
|
data.update(node.attrib.copy())
|
||||||
|
|
||||||
|
if version == '3.2':
|
||||||
|
return data
|
||||||
|
|
||||||
data['usocfdi'] = values['usocfdi']
|
data['usocfdi'] = values['usocfdi']
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -1021,7 +1063,7 @@ def _timbre(doc, version, values):
|
||||||
def get_data_from_xml(invoice, values):
|
def get_data_from_xml(invoice, values):
|
||||||
data = {'cancelada': invoice.cancelada}
|
data = {'cancelada': invoice.cancelada}
|
||||||
doc = parse_xml(invoice.xml)
|
doc = parse_xml(invoice.xml)
|
||||||
data['comprobante'] = _comprobante(doc.attrib.copy(), values)
|
data['comprobante'] = _comprobante(doc, values)
|
||||||
version = data['comprobante']['version']
|
version = data['comprobante']['version']
|
||||||
data['emisor'] = _emisor(doc, version, values)
|
data['emisor'] = _emisor(doc, version, values)
|
||||||
data['receptor'] = _receptor(doc, version, values)
|
data['receptor'] = _receptor(doc, version, values)
|
||||||
|
@ -1111,6 +1153,15 @@ def upload_file(rfc, opt, file_obj):
|
||||||
|
|
||||||
name = '{}_3.3.ods'.format(rfc.lower())
|
name = '{}_3.3.ods'.format(rfc.lower())
|
||||||
path = _join(PATH_MEDIA, 'templates', name)
|
path = _join(PATH_MEDIA, 'templates', name)
|
||||||
|
elif opt == 'txt_plantilla_factura_32':
|
||||||
|
tmp = file_obj.filename.split('.')
|
||||||
|
ext = tmp[-1].lower()
|
||||||
|
if ext != 'ods':
|
||||||
|
msg = 'Extensión de archivo incorrecta, selecciona un archivo ODS'
|
||||||
|
return {'status': 'server', 'name': msg, 'ok': False}
|
||||||
|
|
||||||
|
name = '{}_3.2.ods'.format(rfc.lower())
|
||||||
|
path = _join(PATH_MEDIA, 'templates', name)
|
||||||
|
|
||||||
if save_file(path, file_obj.file.read()):
|
if save_file(path, file_obj.file.read()):
|
||||||
return {'status': 'server', 'name': file_obj.filename, 'ok': True}
|
return {'status': 'server', 'name': file_obj.filename, 'ok': True}
|
||||||
|
@ -1182,25 +1233,46 @@ def get_bool(value):
|
||||||
|
|
||||||
class ImportFacturaLibre(object):
|
class ImportFacturaLibre(object):
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path, rfc):
|
||||||
|
self._rfc = rfc
|
||||||
self._con = None
|
self._con = None
|
||||||
self._cursor = None
|
self._cursor = None
|
||||||
self._is_connect = self._connect(path)
|
self._is_connect = self._connect(path)
|
||||||
self._clientes = []
|
self._clientes = []
|
||||||
self._clientes_rfc = []
|
self._clientes_rfc = []
|
||||||
|
self._error = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def error(self):
|
||||||
|
return self._error
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_connect(self):
|
def is_connect(self):
|
||||||
return self._is_connect
|
return self._is_connect
|
||||||
|
|
||||||
|
def _validate_rfc(self):
|
||||||
|
sql = "SELECT rfc FROM emisor LIMIT 1"
|
||||||
|
self._cursor.execute(sql)
|
||||||
|
obj = self._cursor.fetchone()
|
||||||
|
if obj is None:
|
||||||
|
self._error = 'No se encontró al emisor: {}'.format(self._rfc)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if obj['rfc'] != self._rfc:
|
||||||
|
self._error = 'Los datos no corresponden al emisor: {}'.format(self._rfc)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _connect(self, path):
|
def _connect(self, path):
|
||||||
try:
|
try:
|
||||||
self._con = sqlite3.connect(path)
|
self._con = sqlite3.connect(path)
|
||||||
self._con.row_factory = sqlite3.Row
|
self._con.row_factory = sqlite3.Row
|
||||||
self._cursor = self._con.cursor()
|
self._cursor = self._con.cursor()
|
||||||
return True
|
return self._validate_rfc()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(e)
|
log.error(e)
|
||||||
|
self._error = 'No se pudo conectar a la base de datos'
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
|
|
@ -14,6 +14,9 @@ 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_validartimbrar(self, values):
|
||||||
|
return main.validar_timbrar()
|
||||||
|
|
||||||
def _get_preproductos(self, values):
|
def _get_preproductos(self, values):
|
||||||
return main.PreFacturasDetalle.facturar(values['id'])
|
return main.PreFacturasDetalle.facturar(values['id'])
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,40 @@ def upload_file(rfc, opt, file_obj):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def validar_timbrar():
|
||||||
|
try:
|
||||||
|
obj = Emisor.select()[0]
|
||||||
|
except IndexError:
|
||||||
|
msg = 'Es necesario agregar los datos del emisor'
|
||||||
|
return {'ok': False, 'msg': msg}
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj = Folios.select()[0]
|
||||||
|
except IndexError:
|
||||||
|
msg = 'Es necesaria al menos una serie de folios'
|
||||||
|
return {'ok': False, 'msg': msg}
|
||||||
|
|
||||||
|
msg = 'Es necesario configurar un certificado de sellos'
|
||||||
|
try:
|
||||||
|
obj = Certificado.select()[0]
|
||||||
|
except IndexError:
|
||||||
|
return {'ok': False, 'msg': msg}
|
||||||
|
|
||||||
|
if not obj.serie:
|
||||||
|
return {'ok': False, 'msg': msg}
|
||||||
|
|
||||||
|
dias = obj.hasta - util.now()
|
||||||
|
if dias.days < 0:
|
||||||
|
msg = 'El certificado ha vencido, es necesario cargar uno nuevo'
|
||||||
|
return {'ok': False, 'msg': msg}
|
||||||
|
|
||||||
|
msg = ''
|
||||||
|
if dias.days < 15:
|
||||||
|
msg = 'El certificado vence en: {} días.'.format(dias.days)
|
||||||
|
|
||||||
|
return {'ok': True, 'msg': msg}
|
||||||
|
|
||||||
|
|
||||||
class Configuracion(BaseModel):
|
class Configuracion(BaseModel):
|
||||||
clave = TextField(unique=True)
|
clave = TextField(unique=True)
|
||||||
valor = TextField(default='')
|
valor = TextField(default='')
|
||||||
|
@ -288,7 +322,11 @@ class Emisor(BaseModel):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_regimenes(cls):
|
def get_regimenes(cls):
|
||||||
obj = Emisor.select()[0]
|
try:
|
||||||
|
obj = Emisor.select()[0]
|
||||||
|
except IndexError:
|
||||||
|
return ()
|
||||||
|
|
||||||
rows = [{'id': row.key, 'value': row.name} for row in obj.regimenes]
|
rows = [{'id': row.key, 'value': row.name} for row in obj.regimenes]
|
||||||
return tuple(rows)
|
return tuple(rows)
|
||||||
|
|
||||||
|
@ -1248,6 +1286,10 @@ class Facturas(BaseModel):
|
||||||
|
|
||||||
def _get_not_in_xml(self, invoice):
|
def _get_not_in_xml(self, invoice):
|
||||||
values = {}
|
values = {}
|
||||||
|
|
||||||
|
if invoice.version == '3.2':
|
||||||
|
return values
|
||||||
|
|
||||||
obj = SATRegimenes.get(SATRegimenes.key==invoice.regimen_fiscal)
|
obj = SATRegimenes.get(SATRegimenes.key==invoice.regimen_fiscal)
|
||||||
values['regimenfiscal'] = str(obj)
|
values['regimenfiscal'] = str(obj)
|
||||||
|
|
||||||
|
@ -1267,6 +1309,12 @@ class Facturas(BaseModel):
|
||||||
obj = SATMonedas.get(SATMonedas.key==invoice.moneda)
|
obj = SATMonedas.get(SATMonedas.key==invoice.moneda)
|
||||||
values['moneda'] = str(obj)
|
values['moneda'] = str(obj)
|
||||||
|
|
||||||
|
if invoice.tipo_relacion:
|
||||||
|
obj = SATTipoRelacion.get(SATTipoRelacion.key==invoice.tipo_relacion)
|
||||||
|
values['tiporelacion'] = str(obj)
|
||||||
|
|
||||||
|
print ('\nTR', invoice.tipo_relacion)
|
||||||
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -2695,9 +2743,9 @@ def _importar_factura_libre(archivo):
|
||||||
conectar(args)
|
conectar(args)
|
||||||
|
|
||||||
log.info('Importando datos...')
|
log.info('Importando datos...')
|
||||||
app = util.ImportFacturaLibre(archivo)
|
app = util.ImportFacturaLibre(archivo, rfc)
|
||||||
if not app.is_connect:
|
if not app.is_connect:
|
||||||
log.error('\tNo se pudo conectar a la base de datos')
|
log.error('\t{}'.format(app.error))
|
||||||
return
|
return
|
||||||
|
|
||||||
data = app.import_data()
|
data = app.import_data()
|
||||||
|
|
|
@ -29,6 +29,7 @@ var controllers = {
|
||||||
//~ Opciones
|
//~ Opciones
|
||||||
tb_options = $$('tab_options').getTabbar()
|
tb_options = $$('tab_options').getTabbar()
|
||||||
tb_options.attachEvent('onChange', tab_options_change)
|
tb_options.attachEvent('onChange', tab_options_change)
|
||||||
|
$$('txt_plantilla_factura_32').attachEvent('onItemClick', txt_plantilla_factura_32_click)
|
||||||
$$('txt_plantilla_factura_33').attachEvent('onItemClick', txt_plantilla_factura_33_click)
|
$$('txt_plantilla_factura_33').attachEvent('onItemClick', txt_plantilla_factura_33_click)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -392,9 +393,9 @@ function cmd_subir_certificado_click(){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var rfc = $$('form_cert').getValues()['cert_rfc']
|
var serie = $$('form_cert').getValues()['cert_serie']
|
||||||
|
|
||||||
if(rfc){
|
if(serie){
|
||||||
msg = 'Ya existe un certificado guardado<BR><BR>¿Deseas reemplazarlo?'
|
msg = 'Ya existe un certificado guardado<BR><BR>¿Deseas reemplazarlo?'
|
||||||
webix.confirm({
|
webix.confirm({
|
||||||
title: 'Certificado Existente',
|
title: 'Certificado Existente',
|
||||||
|
@ -686,7 +687,7 @@ function txt_plantilla_factura_33_click(e){
|
||||||
id: 'win_template',
|
id: 'win_template',
|
||||||
modal: true,
|
modal: true,
|
||||||
position: 'center',
|
position: 'center',
|
||||||
head: 'Subir Plantilla',
|
head: 'Subir Plantilla 3.3 ODT',
|
||||||
body: {
|
body: {
|
||||||
view: 'form',
|
view: 'form',
|
||||||
elements: body_elements,
|
elements: body_elements,
|
||||||
|
@ -706,6 +707,44 @@ function txt_plantilla_factura_33_click(e){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function txt_plantilla_factura_32_click(e){
|
||||||
|
|
||||||
|
var body_elements = [
|
||||||
|
{cols: [{width: 100}, {view: 'uploader', id: 'up_template', autosend: true, link: 'lst_files',
|
||||||
|
value: 'Seleccionar archivo', upload: '/files/txt_plantilla_factura_32',
|
||||||
|
width: 200}, {width: 100}]},
|
||||||
|
{view: 'list', id: 'lst_files', type: 'uploader', autoheight:true,
|
||||||
|
borderless: true},
|
||||||
|
{},
|
||||||
|
{cols: [{}, {view: 'button', label: 'Cerrar', autowidth: true,
|
||||||
|
click:("$$('win_template').close();")}, {}]}
|
||||||
|
]
|
||||||
|
|
||||||
|
var w = webix.ui({
|
||||||
|
view: 'window',
|
||||||
|
id: 'win_template',
|
||||||
|
modal: true,
|
||||||
|
position: 'center',
|
||||||
|
head: 'Subir Plantilla 3.2 ODT',
|
||||||
|
body: {
|
||||||
|
view: 'form',
|
||||||
|
elements: body_elements,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
w.show()
|
||||||
|
|
||||||
|
$$('up_template').attachEvent('onUploadComplete', function(response){
|
||||||
|
if(response.ok){
|
||||||
|
$$('txt_plantilla_factura_32').setValue(response.name)
|
||||||
|
msg_sucess('Plantilla cargada correctamente')
|
||||||
|
}else{
|
||||||
|
msg_error(response.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function tab_options_change(nv, ov){
|
function tab_options_change(nv, ov){
|
||||||
var cv = {
|
var cv = {
|
||||||
Plantillas: 'templates',
|
Plantillas: 'templates',
|
||||||
|
|
|
@ -95,6 +95,19 @@ function default_config(){
|
||||||
get_regimen_fiscal()
|
get_regimen_fiscal()
|
||||||
table_pt.clear()
|
table_pt.clear()
|
||||||
table_totals.clear()
|
table_totals.clear()
|
||||||
|
|
||||||
|
webix.ajax().sync().get('/values/validartimbrar', function(text, data){
|
||||||
|
var values = data.json()
|
||||||
|
if(!values.ok){
|
||||||
|
msg_error(values.msg)
|
||||||
|
$$('cmd_timbrar').disable()
|
||||||
|
}else{
|
||||||
|
if(values.msg){
|
||||||
|
msg_error(values.msg)
|
||||||
|
}
|
||||||
|
$$('cmd_timbrar').enable()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue