diff --git a/source/app/controllers/cfdi_xml.py b/source/app/controllers/cfdi_xml.py
index fb61c4b..dee3bfa 100644
--- a/source/app/controllers/cfdi_xml.py
+++ b/source/app/controllers/cfdi_xml.py
@@ -117,7 +117,7 @@ class CFDI(object):
return
def _relacionados(self, datos):
- if not datos['tipo'] or not datos['cfdis']:
+ if not datos or not datos['tipo'] or not datos['cfdis']:
return
node_name = '{}:CfdiRelacionados'.format(self._pre)
diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py
index 70266d7..a73090d 100644
--- a/source/app/controllers/util.py
+++ b/source/app/controllers/util.py
@@ -4,6 +4,7 @@ import datetime
import getpass
import hashlib
import json
+import locale
import mimetypes
import os
import re
@@ -853,7 +854,7 @@ def to_letters(value, moneda):
'USD': 'dólar',
'EUR': 'euro',
}
- return NumLet(value, monedas[moneda]).letras
+ return NumLet(value, monedas.get(moneda, moneda)).letras
def get_qr(data):
@@ -863,8 +864,17 @@ def get_qr(data):
return path
-def _comprobante(values, options):
- data = CaseInsensitiveDict(values)
+def _get_relacionados(doc, version):
+ 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']
data['totalenletras'] = to_letters(float(data['total']), data['moneda'])
@@ -883,21 +893,49 @@ def _comprobante(values, options):
data['condicionesdepago'] = \
'Condiciones de pago: {}'.format(data['condicionesdepago'])
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(
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]))
+ emisor = doc.find('{}Emisor'.format(PRE[version]))
+ data = CaseInsensitiveDict(emisor.attrib.copy())
+ node = emisor.find('{}DomicilioFiscal'.format(PRE[version]))
if not node is None:
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()))
if is_file(path):
@@ -912,6 +950,10 @@ def _receptor(doc, version, values):
node = node.find('{}Domicilio'.format(PRE[version]))
if not node is None:
data.update(node.attrib.copy())
+
+ if version == '3.2':
+ return data
+
data['usocfdi'] = values['usocfdi']
return data
@@ -1021,7 +1063,7 @@ def _timbre(doc, version, values):
def get_data_from_xml(invoice, values):
data = {'cancelada': invoice.cancelada}
doc = parse_xml(invoice.xml)
- data['comprobante'] = _comprobante(doc.attrib.copy(), values)
+ data['comprobante'] = _comprobante(doc, values)
version = data['comprobante']['version']
data['emisor'] = _emisor(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())
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()):
return {'status': 'server', 'name': file_obj.filename, 'ok': True}
@@ -1182,25 +1233,46 @@ def get_bool(value):
class ImportFacturaLibre(object):
- def __init__(self, path):
+ def __init__(self, path, rfc):
+ self._rfc = rfc
self._con = None
self._cursor = None
self._is_connect = self._connect(path)
self._clientes = []
self._clientes_rfc = []
+ self._error = ''
+
+ @property
+ def error(self):
+ return self._error
@property
def is_connect(self):
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):
try:
self._con = sqlite3.connect(path)
self._con.row_factory = sqlite3.Row
self._cursor = self._con.cursor()
- return True
+ return self._validate_rfc()
except Exception as e:
log.error(e)
+ self._error = 'No se pudo conectar a la base de datos'
return False
def close(self):
diff --git a/source/app/models/db.py b/source/app/models/db.py
index c53fdfc..b7db743 100644
--- a/source/app/models/db.py
+++ b/source/app/models/db.py
@@ -14,6 +14,9 @@ class StorageEngine(object):
def get_values(self, table, values=None):
return getattr(self, '_get_{}'.format(table))(values)
+ def _get_validartimbrar(self, values):
+ return main.validar_timbrar()
+
def _get_preproductos(self, values):
return main.PreFacturasDetalle.facturar(values['id'])
diff --git a/source/app/models/main.py b/source/app/models/main.py
index 6048a89..2a353a1 100644
--- a/source/app/models/main.py
+++ b/source/app/models/main.py
@@ -67,6 +67,40 @@ def upload_file(rfc, opt, file_obj):
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):
clave = TextField(unique=True)
valor = TextField(default='')
@@ -288,7 +322,11 @@ class Emisor(BaseModel):
@classmethod
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]
return tuple(rows)
@@ -1248,6 +1286,10 @@ class Facturas(BaseModel):
def _get_not_in_xml(self, invoice):
values = {}
+
+ if invoice.version == '3.2':
+ return values
+
obj = SATRegimenes.get(SATRegimenes.key==invoice.regimen_fiscal)
values['regimenfiscal'] = str(obj)
@@ -1267,6 +1309,12 @@ class Facturas(BaseModel):
obj = SATMonedas.get(SATMonedas.key==invoice.moneda)
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
@classmethod
@@ -2695,9 +2743,9 @@ def _importar_factura_libre(archivo):
conectar(args)
log.info('Importando datos...')
- app = util.ImportFacturaLibre(archivo)
+ app = util.ImportFacturaLibre(archivo, rfc)
if not app.is_connect:
- log.error('\tNo se pudo conectar a la base de datos')
+ log.error('\t{}'.format(app.error))
return
data = app.import_data()
diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js
index 130e675..408e280 100644
--- a/source/static/js/controller/admin.js
+++ b/source/static/js/controller/admin.js
@@ -29,6 +29,7 @@ var controllers = {
//~ Opciones
tb_options = $$('tab_options').getTabbar()
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)
}
}
@@ -392,9 +393,9 @@ function cmd_subir_certificado_click(){
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
¿Deseas reemplazarlo?'
webix.confirm({
title: 'Certificado Existente',
@@ -686,7 +687,7 @@ function txt_plantilla_factura_33_click(e){
id: 'win_template',
modal: true,
position: 'center',
- head: 'Subir Plantilla',
+ head: 'Subir Plantilla 3.3 ODT',
body: {
view: 'form',
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){
var cv = {
Plantillas: 'templates',
diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js
index 9bf0193..f486f06 100644
--- a/source/static/js/controller/invoices.js
+++ b/source/static/js/controller/invoices.js
@@ -95,6 +95,19 @@ function default_config(){
get_regimen_fiscal()
table_pt.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()
+ }
+ })
}