Add new PAC
This commit is contained in:
commit
77b35f8322
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -1,3 +1,29 @@
|
|||
|
||||
v 1.33.0 [22-ene-2020]
|
||||
----------------------
|
||||
- Mejora: Cambio del mensaje para cuando se intenta dar de alta un cliente ya existente.
|
||||
- Mejora: Solo los admins pueden ver la nómina.
|
||||
- Se agrega un segundo PAC
|
||||
- Se actualizan los catálogos del SAT
|
||||
|
||||
* IMPORTANTE:
|
||||
Es necesario actualizar los catálogos del SAT
|
||||
```
|
||||
git pull origin master
|
||||
|
||||
cd source/app/models
|
||||
|
||||
python main.py -bk
|
||||
|
||||
python main.py -us
|
||||
```
|
||||
|
||||
|
||||
v 1.32.0 [05-ene-2020]
|
||||
----------------------
|
||||
- Mejora: Recuperar facturas no aceptadas para cancelación por el receptor
|
||||
|
||||
|
||||
v 1.31.2 [28-oct-2019]
|
||||
----------------------
|
||||
- Error: Al generar PDF con tags en las series
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from .comercio import PACComercioDigital
|
|
@ -0,0 +1,338 @@
|
|||
#!/usr/bin/env python
|
||||
# ~
|
||||
# ~ PAC
|
||||
# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net
|
||||
# ~
|
||||
# ~ This program is free software: you can redistribute it and/or modify
|
||||
# ~ it under the terms of the GNU General Public License as published by
|
||||
# ~ the Free Software Foundation, either version 3 of the License, or
|
||||
# ~ (at your option) any later version.
|
||||
# ~
|
||||
# ~ This program is distributed in the hope that it will be useful,
|
||||
# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# ~ GNU General Public License for more details.
|
||||
# ~
|
||||
# ~ You should have received a copy of the GNU General Public License
|
||||
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
import lxml.etree as ET
|
||||
import requests
|
||||
from requests.exceptions import ConnectionError
|
||||
|
||||
from .conf import DEBUG, AUTH
|
||||
|
||||
|
||||
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
||||
LOG_DATE = '%d/%m/%Y %H:%M:%S'
|
||||
logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m')
|
||||
logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m')
|
||||
logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m')
|
||||
logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
logging.getLogger('requests').setLevel(logging.ERROR)
|
||||
|
||||
|
||||
TIMEOUT = 10
|
||||
|
||||
|
||||
class PACComercioDigital(object):
|
||||
ws = 'https://{}.comercio-digital.mx/{}'
|
||||
api = 'https://app2.comercio-digital.mx/{}'
|
||||
URL = {
|
||||
'timbra': ws.format('ws', 'timbre/timbrarV5.aspx'),
|
||||
'cancel': ws.format('cancela', 'cancela3/cancelarUuid'),
|
||||
'cancelxml': ws.format('cancela', 'cancela3/cancelarXml'),
|
||||
'client': api.format('x3/altaEmpresa'),
|
||||
'saldo': api.format('x3/saldo'),
|
||||
'timbres': api.format('x3/altaTimbres'),
|
||||
}
|
||||
CODES = {
|
||||
'000': '000 Exitoso',
|
||||
'004': '004 RFC {} ya esta dado de alta con Estatus=A',
|
||||
'704': '704 Usuario Invalido',
|
||||
}
|
||||
NS_CFDI = {
|
||||
'cfdi': 'http://www.sat.gob.mx/cfd/3',
|
||||
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
|
||||
}
|
||||
|
||||
if DEBUG:
|
||||
ws = 'https://pruebas.comercio-digital.mx/{}'
|
||||
URL = {
|
||||
'timbra': ws.format('timbre/timbrarV5.aspx'),
|
||||
'cancel': ws.format('cancela3/cancelarUuid'),
|
||||
'cancelxml': ws.format('cancela3/cancelarXml'),
|
||||
'client': api.format('x3/altaEmpresa'),
|
||||
'saldo': api.format('x3/saldo'),
|
||||
'timbres': api.format('x3/altaTimbres'),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.error = ''
|
||||
self.cfdi_uuid = ''
|
||||
self.date_stamped = ''
|
||||
|
||||
def _error(self, msg):
|
||||
self.error = str(msg)
|
||||
log.error(msg)
|
||||
return
|
||||
|
||||
def _post(self, url, data, headers={}):
|
||||
result = None
|
||||
headers['host'] = url.split('/')[2]
|
||||
headers['Content-type'] = 'text/plain'
|
||||
headers['Connection'] = 'Keep-Alive'
|
||||
|
||||
try:
|
||||
result = requests.post(url, data=data, headers=headers, timeout=TIMEOUT)
|
||||
except ConnectionError as e:
|
||||
self._error(e)
|
||||
|
||||
return result
|
||||
|
||||
def _validate_cfdi(self, xml):
|
||||
"""
|
||||
Comercio Digital solo soporta la declaración con doble comilla
|
||||
"""
|
||||
tree = ET.fromstring(xml.encode())
|
||||
xml = ET.tostring(tree,
|
||||
pretty_print=True, doctype='<?xml version="1.0" encoding="utf-8"?>')
|
||||
return xml
|
||||
|
||||
def stamp(self, cfdi, auth={}):
|
||||
if DEBUG or not auth:
|
||||
auth = AUTH
|
||||
|
||||
url = self.URL['timbra']
|
||||
headers = {
|
||||
'usrws': auth['user'],
|
||||
'pwdws': auth['pass'],
|
||||
'tipo': 'XML',
|
||||
}
|
||||
cfdi = self._validate_cfdi(cfdi)
|
||||
result = self._post(url, cfdi, headers)
|
||||
|
||||
if result is None:
|
||||
return ''
|
||||
|
||||
if result.status_code != 200:
|
||||
return ''
|
||||
|
||||
if 'errmsg' in result.headers:
|
||||
self._error(result.headers['errmsg'])
|
||||
return ''
|
||||
|
||||
xml = result.content
|
||||
tree = ET.fromstring(xml)
|
||||
self.cfdi_uuid = tree.xpath(
|
||||
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)',
|
||||
namespaces=self.NS_CFDI)
|
||||
self.date_stamped = tree.xpath(
|
||||
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@FechaTimbrado)',
|
||||
namespaces=self.NS_CFDI)
|
||||
|
||||
return xml.decode()
|
||||
|
||||
def _get_data_cancel(self, cfdi, info, auth):
|
||||
NS_CFDI = {
|
||||
'cfdi': 'http://www.sat.gob.mx/cfd/3',
|
||||
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
|
||||
}
|
||||
tree = ET.fromstring(cfdi)
|
||||
tipo = tree.xpath(
|
||||
'string(//cfdi:Comprobante/@TipoDeComprobante)',
|
||||
namespaces=NS_CFDI)
|
||||
total = tree.xpath(
|
||||
'string(//cfdi:Comprobante/@Total)',
|
||||
namespaces=NS_CFDI)
|
||||
rfc_emisor = tree.xpath(
|
||||
'string(//cfdi:Comprobante/cfdi:Emisor/@Rfc)',
|
||||
namespaces=NS_CFDI)
|
||||
rfc_receptor = tree.xpath(
|
||||
'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)',
|
||||
namespaces=NS_CFDI)
|
||||
uid = tree.xpath(
|
||||
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)',
|
||||
namespaces=NS_CFDI)
|
||||
data = (
|
||||
f"USER={auth['user']}",
|
||||
f"PWDW={auth['pass']}",
|
||||
f"RFCE={rfc_emisor}",
|
||||
f"UUID={uid}",
|
||||
f"PWDK={info['pass']}",
|
||||
f"KEYF={info['key']}",
|
||||
f"CERT={info['cer']}",
|
||||
f"TIPO={info['tipo']}",
|
||||
f"ACUS=SI",
|
||||
f"RFCR={rfc_receptor}",
|
||||
f"TIPOC={tipo}",
|
||||
f"TOTAL={total}",
|
||||
)
|
||||
return '\n'.join(data)
|
||||
|
||||
def cancel(self, cfdi, info, auth={}):
|
||||
if not auth:
|
||||
auth = AUTH
|
||||
url = self.URL['cancel']
|
||||
data = self._get_data_cancel(cfdi, info, auth)
|
||||
|
||||
result = self._post(url, data)
|
||||
|
||||
if result is None:
|
||||
return ''
|
||||
|
||||
if result.status_code != 200:
|
||||
return ''
|
||||
|
||||
if result.headers['codigo'] != '000':
|
||||
self._error(result.headers['errmsg'])
|
||||
return ''
|
||||
|
||||
return result.content
|
||||
|
||||
def _get_headers_cancel_xml(self, cfdi, info, auth):
|
||||
NS_CFDI = {
|
||||
'cfdi': 'http://www.sat.gob.mx/cfd/3',
|
||||
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
|
||||
}
|
||||
tree = ET.fromstring(cfdi)
|
||||
tipo = tree.xpath(
|
||||
'string(//cfdi:Comprobante/@TipoDeComprobante)',
|
||||
namespaces=NS_CFDI)
|
||||
total = tree.xpath(
|
||||
'string(//cfdi:Comprobante/@Total)',
|
||||
namespaces=NS_CFDI)
|
||||
rfc_receptor = tree.xpath(
|
||||
'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)',
|
||||
namespaces=NS_CFDI)
|
||||
|
||||
headers = {
|
||||
'usrws': auth['user'],
|
||||
'pwdws': auth['pass'],
|
||||
'rfcr': rfc_receptor,
|
||||
'total': total,
|
||||
'tipocfdi': tipo,
|
||||
}
|
||||
headers.update(info)
|
||||
|
||||
return headers
|
||||
|
||||
def cancel_xml(self, cfdi, xml, info, auth={}):
|
||||
if not auth:
|
||||
auth = AUTH
|
||||
url = self.URL['cancelxml']
|
||||
headers = self._get_headers_cancel_xml(cfdi, info, auth)
|
||||
result = self._post(url, xml, headers)
|
||||
|
||||
if result is None:
|
||||
return ''
|
||||
|
||||
if result.status_code != 200:
|
||||
return ''
|
||||
|
||||
if result.headers['codigo'] != '000':
|
||||
self._error(result.headers['errmsg'])
|
||||
return ''
|
||||
|
||||
return result.content
|
||||
|
||||
def _get_data_client(self, auth, values):
|
||||
data = [f"usr_ws={auth['user']}", f"pwd_ws={auth['pass']}"]
|
||||
fields = (
|
||||
'rfc_contribuyente',
|
||||
'nombre_contribuyente',
|
||||
'calle',
|
||||
'noExterior',
|
||||
'noInterior',
|
||||
'colonia',
|
||||
'localidad',
|
||||
'municipio',
|
||||
'estado',
|
||||
'pais',
|
||||
'cp',
|
||||
'contacto',
|
||||
'telefono',
|
||||
'email',
|
||||
'rep_nom',
|
||||
'rep_rfc',
|
||||
'email_fact',
|
||||
'pwd_asignado',
|
||||
)
|
||||
data += [f"{k}={values[k]}" for k in fields]
|
||||
|
||||
return '\n'.join(data)
|
||||
|
||||
def client_add(self, data):
|
||||
auth = AUTH
|
||||
url = self.URL['client']
|
||||
data = self._get_data_client(auth, data)
|
||||
|
||||
result = self._post(url, data)
|
||||
|
||||
if result is None:
|
||||
return False
|
||||
|
||||
if result.status_code != 200:
|
||||
self._error(f'Code: {result.status_code}')
|
||||
return False
|
||||
|
||||
if result.text != self.CODES['000']:
|
||||
self._error(result.text)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def client_balance(self, data):
|
||||
url = self.URL['saldo']
|
||||
host = url.split('/')[2]
|
||||
headers = {
|
||||
'Content-type': 'text/plain',
|
||||
'Host': host,
|
||||
'Connection' : 'Keep-Alive',
|
||||
}
|
||||
try:
|
||||
result = requests.get(url, params=data, headers=headers, timeout=TIMEOUT)
|
||||
except ConnectionError as e:
|
||||
self._error(e)
|
||||
return ''
|
||||
|
||||
if result.status_code != 200:
|
||||
return ''
|
||||
|
||||
if result.text == self.CODES['704']:
|
||||
self._error(result.text)
|
||||
return ''
|
||||
|
||||
return result.text
|
||||
|
||||
def client_add_timbres(self, data, auth={}):
|
||||
if not auth:
|
||||
auth = AUTH
|
||||
url = self.URL['timbres']
|
||||
data = '\n'.join((
|
||||
f"usr_ws={auth['user']}",
|
||||
f"pwd_ws={auth['pass']}",
|
||||
f"rfc_recibir={data['rfc']}",
|
||||
f"num_timbres={data['timbres']}"
|
||||
))
|
||||
|
||||
result = self._post(url, data)
|
||||
|
||||
if result is None:
|
||||
return False
|
||||
|
||||
if result.status_code != 200:
|
||||
self._error(f'Code: {result.status_code}')
|
||||
return False
|
||||
|
||||
if result.text != self.CODES['000']:
|
||||
self._error(result.text)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python
|
||||
# ~
|
||||
# ~ PAC
|
||||
# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net
|
||||
# ~
|
||||
# ~ This program is free software: you can redistribute it and/or modify
|
||||
# ~ it under the terms of the GNU General Public License as published by
|
||||
# ~ the Free Software Foundation, either version 3 of the License, or
|
||||
# ~ (at your option) any later version.
|
||||
# ~
|
||||
# ~ This program is distributed in the hope that it will be useful,
|
||||
# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# ~ GNU General Public License for more details.
|
||||
# ~
|
||||
# ~ You should have received a copy of the GNU General Public License
|
||||
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# ~ Siempre consulta la documentación de Finkok
|
||||
# ~ AUTH = Puedes usar credenciales genericas para timbrar, o exclusivas para
|
||||
# ~ cada emisor
|
||||
# ~ RESELLER = Algunos procesos como agregar emisores, solo pueden ser usadas
|
||||
# ~ con una cuenta de reseller
|
||||
|
||||
|
||||
DEBUG = True
|
||||
|
||||
|
||||
AUTH = {
|
||||
'user': '',
|
||||
'pass': '',
|
||||
}
|
||||
|
||||
|
||||
if DEBUG:
|
||||
AUTH = {
|
||||
'user': 'AAA010101AAA',
|
||||
'pass': 'PWD',
|
||||
}
|
|
@ -589,7 +589,7 @@ def timbra_xml(xml, auth):
|
|||
|
||||
if not DEBUG and not auth:
|
||||
msg = 'Sin datos para timbrar'
|
||||
result = {'ok': True, 'error': msg}
|
||||
result = {'ok': False, 'error': msg}
|
||||
return result
|
||||
|
||||
result = {'ok': True, 'error': ''}
|
||||
|
@ -2738,521 +2738,6 @@ def sync_cfdi(auth, files):
|
|||
return
|
||||
|
||||
|
||||
class ImportFacturaLibreGambas(object):
|
||||
|
||||
def __init__(self, conexion, rfc):
|
||||
self._rfc = rfc
|
||||
self._con = None
|
||||
self._cursor = None
|
||||
self._error = ''
|
||||
self._is_connect = self._connect(conexion)
|
||||
self._clientes = []
|
||||
self._clientes_rfc = []
|
||||
|
||||
@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 not DEBUG:
|
||||
if obj['rfc'] != self._rfc:
|
||||
self._error = 'Los datos no corresponden al RFC: {}'.format(self._rfc)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _connect(self, conexion):
|
||||
import psycopg2
|
||||
import psycopg2.extras
|
||||
try:
|
||||
self._con = psycopg2.connect(conexion)
|
||||
self._cursor = self._con.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
||||
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):
|
||||
try:
|
||||
self._cursor.close()
|
||||
self._con.close()
|
||||
except:
|
||||
pass
|
||||
return
|
||||
|
||||
def import_data(self):
|
||||
data = {}
|
||||
tables = (
|
||||
('receptores', 'Socios'),
|
||||
('cfdifacturas', 'Facturas'),
|
||||
('categorias', 'Categorias'),
|
||||
('productos', 'Productos'),
|
||||
('tickets', 'Tickets'),
|
||||
)
|
||||
for source, target in tables:
|
||||
data[target] = self._get_table(source)
|
||||
|
||||
data['Socios'] += self._clientes
|
||||
|
||||
return data
|
||||
|
||||
def _get_table(self, table):
|
||||
return getattr(self, '_{}'.format(table))()
|
||||
|
||||
def _tickets(self):
|
||||
sql = "SELECT * FROM tickets"
|
||||
self._cursor.execute(sql)
|
||||
rows = self._cursor.fetchall()
|
||||
|
||||
fields = (
|
||||
('serie', 'serie'),
|
||||
('folio', 'folio'),
|
||||
('fecha', 'fecha'),
|
||||
('formadepago', 'forma_pago'),
|
||||
('subtotal', 'subtotal'),
|
||||
('descuento', 'descuento'),
|
||||
('total', 'total'),
|
||||
('notas', 'notas'),
|
||||
('factura', 'factura'),
|
||||
('cancelada', 'cancelado'),
|
||||
('vendedor', 'vendedor'),
|
||||
)
|
||||
data = []
|
||||
totals = len(rows)
|
||||
for i, row in enumerate(rows):
|
||||
msg = '\tImportando ticket {} de {}'.format(i+1, totals)
|
||||
log.info(msg)
|
||||
|
||||
new = {t: row[s] for s, t in fields}
|
||||
|
||||
new['notas'] = ''
|
||||
new['fecha'] = new['fecha'].replace(microsecond=0)
|
||||
new['estatus'] = 'Generado'
|
||||
if new['cancelado']:
|
||||
new['estatus'] = 'Cancelado'
|
||||
new['factura'] = self._get_invoice_ticket(new['factura'])
|
||||
|
||||
new['details'] = self._get_details_ticket(row['id'])
|
||||
new['taxes'] = self._get_taxes_ticket(row['id'])
|
||||
data.append(new)
|
||||
|
||||
return data
|
||||
|
||||
def _get_invoice_ticket(self, invoice):
|
||||
if not invoice:
|
||||
return None
|
||||
|
||||
sql = "SELECT serie, folio FROM cfdifacturas WHERE id=%s"
|
||||
self._cursor.execute(sql, [invoice])
|
||||
row = self._cursor.fetchone()
|
||||
if row is None:
|
||||
return {}
|
||||
|
||||
return dict(row)
|
||||
|
||||
def _get_details_ticket(self, id):
|
||||
sql = "SELECT * FROM t_detalle WHERE id_cfdi=%s"
|
||||
self._cursor.execute(sql, [id])
|
||||
rows = self._cursor.fetchall()
|
||||
|
||||
fields = (
|
||||
('descripcion', 'descripcion'),
|
||||
('cantidad', 'cantidad'),
|
||||
('valorunitario', 'valor_unitario'),
|
||||
('importe', 'importe'),
|
||||
('precio', 'precio_final'),
|
||||
)
|
||||
|
||||
data = []
|
||||
for row in rows:
|
||||
new = {t: row[s] for s, t in fields if row[s]}
|
||||
data.append(new)
|
||||
|
||||
return data
|
||||
|
||||
def _get_taxes_ticket(self, id):
|
||||
sql = "SELECT * FROM t_impuestos WHERE id_cfdi=%s"
|
||||
self._cursor.execute(sql, [id])
|
||||
rows = self._cursor.fetchall()
|
||||
|
||||
tasas = {
|
||||
'0': 0.0,
|
||||
'16': 0.16,
|
||||
'16.00': 0.16,
|
||||
'0.16': 0.16,
|
||||
'11': 0.11,
|
||||
'-10': 0.10,
|
||||
'-2': 0.02,
|
||||
'-0.5': 0.005,
|
||||
'-2/3': 0.106667,
|
||||
'-10.6667': 0.106667,
|
||||
'-10.6666': 0.106667,
|
||||
'-10.666666': 0.106667,
|
||||
'-10.66660': 0.106667,
|
||||
}
|
||||
|
||||
data = []
|
||||
for row in rows:
|
||||
filtro = {
|
||||
'name': row['impuesto'],
|
||||
'tasa': tasas[row['tasa']],
|
||||
'tipo': row['tipo'][0],
|
||||
}
|
||||
new = {
|
||||
'import': row['importe'],
|
||||
'filter': filtro
|
||||
}
|
||||
data.append(new)
|
||||
|
||||
return data
|
||||
|
||||
def _productos(self):
|
||||
UNIDADES = {
|
||||
'k': 'KGM',
|
||||
'kg': 'KGM',
|
||||
'kg.': 'KGM',
|
||||
'pieza': 'H87',
|
||||
'pza': 'H87',
|
||||
'pz': 'H87',
|
||||
'bulto': 'H87',
|
||||
'b': 'H87',
|
||||
'exb': 'H87',
|
||||
'ex': 'H87',
|
||||
'caja': 'XBX',
|
||||
'c': 'XBX',
|
||||
'rollo': 'XRO',
|
||||
'tira': 'SR',
|
||||
't': 'SR',
|
||||
'cono': 'XAJ',
|
||||
'paquete': 'XPK',
|
||||
'pq': 'XPK',
|
||||
}
|
||||
sql = "SELECT * FROM productos"
|
||||
self._cursor.execute(sql)
|
||||
rows = self._cursor.fetchall()
|
||||
|
||||
fields = (
|
||||
('id_categoria', 'categoria'),
|
||||
('noidentificacion', 'clave'),
|
||||
('descripcion', 'descripcion'),
|
||||
# ~ ('unidad', 'unidad'),
|
||||
('id_unidad', 'unidad'),
|
||||
# ~ ('costo', 'ultimo_costo'),
|
||||
('valorunitario', 'valor_unitario'),
|
||||
# ~ ('existencia', 'existencia'),
|
||||
# ~ ('minimo', 'minimo'),
|
||||
('inventario', 'inventario'),
|
||||
('codigobarras', 'codigo_barras'),
|
||||
('cuentapredial', 'cuenta_predial'),
|
||||
)
|
||||
data = []
|
||||
|
||||
sql = """
|
||||
SELECT nombre, tasa, tipo
|
||||
FROM impuestos
|
||||
WHERE id=%s
|
||||
"""
|
||||
totals = len(rows)
|
||||
for i, row in enumerate(rows):
|
||||
msg = '\tImportando producto {} de {}'.format(i+1, totals)
|
||||
log.info(msg)
|
||||
# ~ print (i, dict(row))
|
||||
new = {t: row[s] for s, t in fields}
|
||||
|
||||
# ~ print (new['unidad'])
|
||||
if new['unidad'] == 2:
|
||||
new['unidad'] = 'servicio'
|
||||
|
||||
u = new['unidad'].lower().strip()
|
||||
if u in ('sin',):
|
||||
continue
|
||||
if not u:
|
||||
u = 'pieza'
|
||||
|
||||
if not new['categoria']:
|
||||
new['categoria'] = None
|
||||
new['codigo_barras'] = new['codigo_barras'] or ''
|
||||
new['cuenta_predial'] = new['cuenta_predial'] or ''
|
||||
new['descripcion'] = ' '.join(new['descripcion'].split())
|
||||
new['clave_sat'] = DEFAULT_SAT_PRODUCTO
|
||||
|
||||
new['unidad'] = UNIDADES.get(u, new['unidad'])
|
||||
self._cursor.execute(sql, [row['id_impuesto1']])
|
||||
impuestos = self._cursor.fetchall()
|
||||
new['impuestos'] = tuple(impuestos)
|
||||
data.append(new)
|
||||
|
||||
return data
|
||||
|
||||
def _categorias(self):
|
||||
sql = "SELECT * FROM categorias ORDER BY id_padre"
|
||||
self._cursor.execute(sql)
|
||||
rows = self._cursor.fetchall()
|
||||
|
||||
fields = (
|
||||
('id', 'id'),
|
||||
('categoria', 'categoria'),
|
||||
('id_padre', 'padre'),
|
||||
)
|
||||
data = []
|
||||
|
||||
for row in rows:
|
||||
new = {t: row[s] for s, t in fields}
|
||||
if new['padre'] == 0:
|
||||
new['padre'] = None
|
||||
data.append(new)
|
||||
|
||||
return data
|
||||
|
||||
def _get_cliente(self, invoice):
|
||||
sql = "SELECT rfc, nombre FROM receptores WHERE id=%s"
|
||||
self._cursor.execute(sql, [invoice['id_cliente']])
|
||||
obj = self._cursor.fetchone()
|
||||
if not obj is None:
|
||||
data = {
|
||||
'rfc': obj['rfc'],
|
||||
'slug': to_slug(obj['nombre']),
|
||||
}
|
||||
return data
|
||||
|
||||
if not invoice['xml']:
|
||||
return {}
|
||||
|
||||
doc = parse_xml(invoice['xml'])
|
||||
version = doc.attrib['version']
|
||||
node = doc.find('{}Receptor'.format(PRE[version]))
|
||||
rfc = node.attrib['rfc']
|
||||
nombre = node.attrib['nombre']
|
||||
|
||||
tipo_persona = 1
|
||||
if rfc == 'XEXX010101000':
|
||||
tipo_persona = 4
|
||||
elif rfc == 'XAXX010101000':
|
||||
tipo_persona = 3
|
||||
elif len(rfc) == 12:
|
||||
tipo_persona = 2
|
||||
|
||||
data = {
|
||||
'tipo_persona': tipo_persona,
|
||||
'rfc': rfc,
|
||||
'nombre': nombre,
|
||||
'slug': to_slug(nombre),
|
||||
'es_cliente': True,
|
||||
'es_activo': False,
|
||||
}
|
||||
if not rfc in self._clientes_rfc:
|
||||
self._clientes_rfc.append(rfc)
|
||||
self._clientes.append(data)
|
||||
|
||||
data = {
|
||||
'rfc': data['rfc'],
|
||||
'slug': data['slug'],
|
||||
}
|
||||
return data
|
||||
|
||||
def _get_detalles(self, id):
|
||||
sql = "SELECT * FROM cfdidetalle WHERE id_cfdi=%s"
|
||||
self._cursor.execute(sql, [id])
|
||||
rows = self._cursor.fetchall()
|
||||
|
||||
fields = (
|
||||
('categoria', 'categoria'),
|
||||
('cantidad', 'cantidad'),
|
||||
('unidad', 'unidad'),
|
||||
('noidentificacion', 'clave'),
|
||||
('descripcion', 'descripcion'),
|
||||
('valorunitario', 'valor_unitario'),
|
||||
('importe', 'importe'),
|
||||
('numero', 'pedimento'),
|
||||
('fecha', 'fecha_pedimento'),
|
||||
('aduana', 'aduana'),
|
||||
('cuentapredial', 'cuenta_predial'),
|
||||
('descuento', 'descuento'),
|
||||
('precio', 'precio_final'),
|
||||
)
|
||||
|
||||
data = []
|
||||
for row in rows:
|
||||
new = {t: row[s] for s, t in fields if row[s]}
|
||||
data.append(new)
|
||||
|
||||
return data
|
||||
|
||||
def _get_impuestos(self, id):
|
||||
sql = "SELECT * FROM cfdiimpuestos WHERE id_cfdi=%s"
|
||||
self._cursor.execute(sql, [id])
|
||||
rows = self._cursor.fetchall()
|
||||
|
||||
tasas = {
|
||||
'0': 0.0,
|
||||
'16': 0.16,
|
||||
'16.00': 0.16,
|
||||
'11': 0.11,
|
||||
'-10': 0.10,
|
||||
'-2': 0.02,
|
||||
'-0.5': 0.005,
|
||||
'-2/3': 0.106667,
|
||||
'-10.6666': 0.106667,
|
||||
'-10.666666': 0.106667,
|
||||
'-10.66660': 0.106667,
|
||||
'-4': 0.04,
|
||||
}
|
||||
|
||||
data = []
|
||||
for row in rows:
|
||||
filtro = {
|
||||
'name': row['impuesto'],
|
||||
'tasa': tasas[row['tasa']],
|
||||
'tipo': row['tipo'][0],
|
||||
}
|
||||
new = {
|
||||
'importe': row['importe'],
|
||||
'filtro': filtro
|
||||
}
|
||||
data.append(new)
|
||||
|
||||
return data
|
||||
|
||||
def _cfdifacturas(self):
|
||||
sql = "SELECT * FROM cfdifacturas"
|
||||
self._cursor.execute(sql)
|
||||
rows = self._cursor.fetchall()
|
||||
fields = (
|
||||
('version', 'version'),
|
||||
('serie', 'serie'),
|
||||
('folio', 'folio'),
|
||||
('fecha', 'fecha'),
|
||||
('fecha_timbrado', 'fecha_timbrado'),
|
||||
('formadepago', 'forma_pago'),
|
||||
('condicionesdepago', 'condiciones_pago'),
|
||||
('subtotal', 'subtotal'),
|
||||
('descuento', 'descuento'),
|
||||
('tipocambio', 'tipo_cambio'),
|
||||
('moneda', 'moneda'),
|
||||
('total', 'total'),
|
||||
('tipodecomprobante', 'tipo_comprobante'),
|
||||
('metododepago', 'metodo_pago'),
|
||||
('lugarexpedicion', 'lugar_expedicion'),
|
||||
('totalimpuestosretenidos', 'total_retenciones'),
|
||||
('totalimpuestostrasladados', 'total_traslados'),
|
||||
('xml', 'xml'),
|
||||
('id_cliente', 'cliente'),
|
||||
('notas', 'notas'),
|
||||
('uuid', 'uuid'),
|
||||
('cancelada', 'cancelada'),
|
||||
)
|
||||
data = []
|
||||
totals = len(rows)
|
||||
for i, row in enumerate(rows):
|
||||
msg = '\tImportando factura {} de {}'.format(i+1, totals)
|
||||
log.info(msg)
|
||||
|
||||
new = {t: row[s] for s, t in fields}
|
||||
|
||||
for _, f in fields:
|
||||
new[f] = new[f] or ''
|
||||
|
||||
|
||||
new['fecha'] = new['fecha'].replace(microsecond=0)
|
||||
if new['fecha_timbrado']:
|
||||
new['fecha_timbrado'] = new['fecha_timbrado'].replace(microsecond=0)
|
||||
else:
|
||||
new['fecha_timbrado'] = None
|
||||
|
||||
new['estatus'] = 'Timbrada'
|
||||
if new['cancelada']:
|
||||
new['estatus'] = 'Cancelada'
|
||||
|
||||
if not new['uuid']:
|
||||
new['uuid'] = None
|
||||
elif new['uuid'] in('ok', '123', '??', 'X'):
|
||||
new['uuid'] = None
|
||||
new['estatus'] = 'Cancelada'
|
||||
new['cancelada'] = True
|
||||
|
||||
if new['xml'] is None:
|
||||
new['xml'] = ''
|
||||
|
||||
new['pagada'] = True
|
||||
new['total_mn'] = round(row['tipocambio'] * row['total'], 2)
|
||||
new['detalles'] = self._get_detalles(row['id'])
|
||||
new['impuestos'] = self._get_impuestos(row['id'])
|
||||
new['cliente'] = self._get_cliente(row)
|
||||
data.append(new)
|
||||
return data
|
||||
|
||||
def _receptores(self):
|
||||
sql = "SELECT * FROM receptores"
|
||||
self._cursor.execute(sql)
|
||||
rows = self._cursor.fetchall()
|
||||
|
||||
fields = (
|
||||
('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'),
|
||||
)
|
||||
data = []
|
||||
|
||||
sql1 = "SELECT correo FROM correos WHERE id_padre=%s"
|
||||
sql2 = "SELECT telefono FROM telefonos WHERE id_padre=%s"
|
||||
totals = len(rows)
|
||||
for i, row in enumerate(rows):
|
||||
msg = '\tImportando cliente {} de {}'.format(i+1, totals)
|
||||
log.info(msg)
|
||||
new = {t: row[s] for s, t in fields}
|
||||
new['slug'] = to_slug(new['nombre'])
|
||||
new['es_cliente'] = True
|
||||
if new['fecha_alta'] is None:
|
||||
new['fecha_alta'] = str(now())
|
||||
else:
|
||||
new['fecha_alta'] = str(new['fecha_alta'])
|
||||
|
||||
for _, f in fields:
|
||||
new[f] = new[f] or ''
|
||||
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, (row['id'],))
|
||||
tmp = self._cursor.fetchall()
|
||||
if tmp:
|
||||
new['correo_facturas'] = ', '.join([r[0] for r in tmp])
|
||||
|
||||
self._cursor.execute(sql2, (row['id'],))
|
||||
tmp = self._cursor.fetchall()
|
||||
if tmp:
|
||||
new['telefonos'] = ', '.join([r[0] for r in tmp])
|
||||
|
||||
data.append(new)
|
||||
return data
|
||||
|
||||
|
||||
class ImportFacturaLibre(object):
|
||||
|
||||
def __init__(self, path, rfc):
|
||||
|
|
|
@ -19,10 +19,12 @@
|
|||
import base64
|
||||
import collections
|
||||
import datetime
|
||||
import getpass
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import smtplib
|
||||
import sqlite3
|
||||
|
@ -48,6 +50,9 @@ from dateutil import parser
|
|||
|
||||
import seafileapi
|
||||
|
||||
from settings import DEBUG, DB_COMPANIES, PATHS
|
||||
from .comercio import PACComercioDigital
|
||||
|
||||
|
||||
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
||||
LOG_DATE = '%d/%m/%Y %H:%M:%S'
|
||||
|
@ -61,7 +66,15 @@ logging.getLogger('peewee').setLevel(logging.WARNING)
|
|||
|
||||
TIMEOUT = 10
|
||||
PATH_INVOICES = 'facturas'
|
||||
PG_DUMP = 'pg_dump -U postgres'
|
||||
PSQL = 'psql -U postgres'
|
||||
if DEBUG:
|
||||
PG_DUMP = 'pg_dump -h localhost -U postgres'
|
||||
PSQL = 'psql -h localhost -U postgres'
|
||||
|
||||
PACS = {
|
||||
'comercio': PACComercioDigital
|
||||
}
|
||||
|
||||
#~ https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37
|
||||
class CaseInsensitiveDict(collections.MutableMapping):
|
||||
|
@ -259,6 +272,16 @@ def _call(args):
|
|||
return subprocess.check_output(args, shell=True).decode()
|
||||
|
||||
|
||||
def _run(args, wait=False):
|
||||
result = ''
|
||||
cmd = shlex.split(args)
|
||||
if wait:
|
||||
result = subprocess.run(cmd, shell=True, check=True).stdout.decode()
|
||||
else:
|
||||
subprocess.run(cmd)
|
||||
return result
|
||||
|
||||
|
||||
def _join(*paths):
|
||||
return os.path.join(*paths)
|
||||
|
||||
|
@ -332,62 +355,142 @@ def to_zip(files):
|
|||
return zip_buffer.getvalue()
|
||||
|
||||
|
||||
def db_delete(user, path):
|
||||
dt = datetime.datetime.now().strftime('%y%m%d_%H%M')
|
||||
path_bk = _join(path, 'tmp', '{}_{}.bk'.format(user, dt))
|
||||
args = 'pg_dump -U postgres -Fc {} > "{}"'.format(user, path_bk)
|
||||
try:
|
||||
_call(args)
|
||||
except:
|
||||
pass
|
||||
def dumps(data):
|
||||
return json.dumps(data, default=str)
|
||||
|
||||
args = 'psql -U postgres -c "DROP DATABASE {0};"'.format(user)
|
||||
try:
|
||||
_call(args)
|
||||
except:
|
||||
pass
|
||||
|
||||
args = 'psql -U postgres -c "DROP ROLE {0};"'.format(user)
|
||||
try:
|
||||
_call(args)
|
||||
except:
|
||||
pass
|
||||
def loads(data):
|
||||
return json.loads(data)
|
||||
|
||||
|
||||
def json_loads(path):
|
||||
return json.loads(open(path, 'r').read())
|
||||
|
||||
|
||||
def _validate_db_rfc():
|
||||
con = sqlite3.connect(DB_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 _sql_companies(sql, args=()):
|
||||
_validate_db_rfc()
|
||||
con = sqlite3.connect(DB_COMPANIES)
|
||||
cursor = con.cursor()
|
||||
try:
|
||||
cursor.execute(sql, args)
|
||||
data = cursor.fetchall()
|
||||
except sqlite3.IntegrityError as e:
|
||||
log.error(e)
|
||||
return False
|
||||
|
||||
con.commit()
|
||||
cursor.close()
|
||||
con.close()
|
||||
return data
|
||||
|
||||
|
||||
def get_data_con(rfc):
|
||||
data = rfc_get(rfc)[0][0]
|
||||
return loads(data)
|
||||
|
||||
|
||||
def rfc_get(rfc=''):
|
||||
if rfc:
|
||||
sql = "SELECT con FROM names WHERE rfc = ?"
|
||||
w = (rfc,)
|
||||
else:
|
||||
w = ()
|
||||
sql = "SELECT * FROM names"
|
||||
data = _sql_companies(sql, w)
|
||||
return data
|
||||
|
||||
|
||||
def rfc_exists(rfc):
|
||||
sql = "SELECT rfc FROM names WHERE rfc = ?"
|
||||
data = _sql_companies(sql, (rfc,))
|
||||
if isinstance(data, bool):
|
||||
return
|
||||
return bool(data)
|
||||
|
||||
|
||||
def rfc_add(rfc, con):
|
||||
sql = "INSERT INTO names VALUES (?, ?)"
|
||||
data = _sql_companies(sql, (rfc.upper(), dumps(con)))
|
||||
return True
|
||||
|
||||
|
||||
def db_create(user):
|
||||
args = f'{PSQL} -c "CREATE ROLE {user} WITH LOGIN ENCRYPTED PASSWORD \'{user}\';"'
|
||||
_run(args)
|
||||
args = f'{PSQL} -c "CREATE DATABASE {user} WITH OWNER {user};"'
|
||||
_run(args)
|
||||
return True
|
||||
|
||||
|
||||
def db_delete(user, path, no_database=False):
|
||||
sql = "DELETE FROM names WHERE rfc = ?"
|
||||
data = _sql_companies(sql, (user,))
|
||||
|
||||
if no_database:
|
||||
return True
|
||||
|
||||
user = user.replace('&', '').lower()
|
||||
dt = now().strftime('%y%m%d_%H%M')
|
||||
path_bk = _join(path, f'{user}_{dt}.bk')
|
||||
|
||||
args = f'{PG_DUMP} -d {user} -Fc -f "{path_bk}"'
|
||||
_run(args)
|
||||
|
||||
args = f'{PSQL} -c "DROP DATABASE {user};"'
|
||||
_run(args)
|
||||
|
||||
args = f'{PSQL} -c "DROP ROLE {user};"'
|
||||
_run(args)
|
||||
return True
|
||||
|
||||
|
||||
def _get_pass(rfc):
|
||||
return rfc
|
||||
|
||||
|
||||
def _backup_db(rfc, data, path_bk, is_mv, url_seafile):
|
||||
if data['type'] != 'postgres':
|
||||
return
|
||||
|
||||
log.info('Generando backup de: {}'.format(rfc))
|
||||
bk_name = '{}.bk'.format(rfc.lower())
|
||||
path_db = _join(path_bk, bk_name)
|
||||
args = 'pg_dump -U postgres -Fc {} > "{}"'.format(data['name'], path_db)
|
||||
result = _call(args)
|
||||
def _backup_db(rfc, is_mv, url_seafile):
|
||||
log.info(f'Generando backup de: {rfc.upper()}')
|
||||
bk_name = f'{rfc}.bk'
|
||||
path = _join(PATHS['BK'], bk_name)
|
||||
args = f'{PG_DUMP} -d {rfc} -Fc -f "{path}"'
|
||||
_run(args)
|
||||
log.info('\tBackup local generado...')
|
||||
|
||||
if is_mv:
|
||||
path_target = _join(Path.home(), PATH_INVOICES)
|
||||
if Path(path_target).exists():
|
||||
path_target = _validate_path_local()
|
||||
if path_target:
|
||||
path_target = _join(path_target, bk_name)
|
||||
shutil.copy(path_db, path_target)
|
||||
shutil.copy(path, path_target)
|
||||
else:
|
||||
log.error('\tNo existe la carpeta compartida...')
|
||||
|
||||
sql = 'select correo_timbrado, token_soporte from emisor;'
|
||||
args = 'psql -U postgres -d {} -Atc "{}"'.format(data['name'], sql)
|
||||
result = _call(args)
|
||||
if not result:
|
||||
log.error('\tSin datos para backup remoto')
|
||||
return
|
||||
# ~ sql = 'select correo_timbrado, token_soporte from emisor;'
|
||||
# ~ args = 'psql -U postgres -d {} -Atc "{}"'.format(data['name'], sql)
|
||||
# ~ result = _call(args)
|
||||
# ~ if not result:
|
||||
# ~ log.error('\tSin datos para backup remoto')
|
||||
# ~ return
|
||||
|
||||
# ~ data = result.strip().split('|')
|
||||
# ~ if not data[1]:
|
||||
# ~ log.error('\tSin token de soporte')
|
||||
# ~ return
|
||||
|
||||
data = result.strip().split('|')
|
||||
if not data[1]:
|
||||
log.error('\tSin token de soporte')
|
||||
return
|
||||
# ~ email = data[0]
|
||||
# ~ uuid = data[1]
|
||||
# ~ email = 'hola@elmau.net'
|
||||
|
@ -401,22 +504,47 @@ def _backup_db(rfc, data, path_bk, is_mv, url_seafile):
|
|||
return
|
||||
|
||||
|
||||
def db_backup(path_companies, path_bk, is_mv, url_seafile):
|
||||
con = sqlite3.connect(path_companies)
|
||||
cursor = con.cursor()
|
||||
sql = "SELECT * FROM names"
|
||||
cursor.execute(sql)
|
||||
rows = cursor.fetchall()
|
||||
if rows is None:
|
||||
def db_backup(is_mv, url_seafile):
|
||||
data = rfc_get()
|
||||
if not len(data):
|
||||
msg = 'Sin bases de datos a respaldar'
|
||||
log.info(msg)
|
||||
return
|
||||
cursor.close()
|
||||
con.close()
|
||||
|
||||
for rfc, data in rows:
|
||||
_backup_db(rfc, json.loads(data), path_bk, is_mv, url_seafile)
|
||||
for rfc, _ in data:
|
||||
_backup_db(rfc.lower(), is_mv, url_seafile)
|
||||
|
||||
return
|
||||
|
||||
def _validate_path_local():
|
||||
path_bk = _join(str(Path.home()), PATHS['LOCAL'])
|
||||
if not os.path.isdir(path_bk):
|
||||
path_bk = ''
|
||||
return path_bk
|
||||
|
||||
|
||||
def db_backup_local():
|
||||
path_bk = _validate_path_local()
|
||||
if not path_bk:
|
||||
msg = 'No existe la carpeta local'
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
data = rfc_get()
|
||||
if not len(data):
|
||||
msg = 'Sin bases de datos a respaldar'
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
for row in data:
|
||||
user = row[0].lower()
|
||||
db = loads(row[1])['name']
|
||||
path = _join(path_bk, '{}.bk'.format(user))
|
||||
args = f'{PG_DUMP} -d {user} -Fc -f "{path}"'
|
||||
_run(args)
|
||||
|
||||
msg = 'Bases de datos respaldadas correctamente'
|
||||
result = {'ok': True, 'msg': msg}
|
||||
return result
|
||||
|
||||
|
||||
def now():
|
||||
return datetime.datetime.now().replace(microsecond=0)
|
||||
|
@ -424,3 +552,42 @@ def now():
|
|||
|
||||
def get_days(date):
|
||||
return (now() - date).days
|
||||
|
||||
|
||||
def get_pass():
|
||||
pass1 = getpass.getpass('Introduce la contraseña: ')
|
||||
pass2 = getpass.getpass('Confirma la contraseña: ')
|
||||
|
||||
if pass1 != pass2:
|
||||
msg = 'Las contraseñas son diferentes'
|
||||
return False, msg
|
||||
|
||||
password = pass1.strip()
|
||||
if not password:
|
||||
msg = 'La contraseña es necesaria'
|
||||
return False, msg
|
||||
|
||||
return True, password
|
||||
|
||||
|
||||
def xml_stamp(xml, auth, name):
|
||||
if not DEBUG and not auth:
|
||||
msg = 'Sin datos para timbrar'
|
||||
result = {'ok': False, 'error': msg}
|
||||
return result
|
||||
|
||||
result = {'ok': True, 'error': ''}
|
||||
auth = {'user': auth['USER'], 'pass': auth['PASS']}
|
||||
|
||||
pac = PACS[name]()
|
||||
xml_stamped = pac.stamp(xml, auth)
|
||||
|
||||
if not xml_stamped:
|
||||
result['ok'] = False
|
||||
result['error'] = pac.error
|
||||
return result
|
||||
|
||||
result['xml'] = xml_stamped
|
||||
result['uuid'] = pac.cfdi_uuid
|
||||
result['fecha'] = pac.date_stamped
|
||||
return result
|
||||
|
|
|
@ -34,19 +34,18 @@ class StorageEngine(object):
|
|||
return main.CfdiNomina.get_by(values)
|
||||
|
||||
def empresa_agregar(self, values):
|
||||
return main.empresa_agregar(values['alta_rfc'], False)
|
||||
# ~ return main.empresa_agregar(values['alta_rfc'], False)
|
||||
return main._new_client(values['alta_rfc'], False)
|
||||
|
||||
def empresa_borrar(self, values):
|
||||
return main.empresa_borrar(values['rfc'])
|
||||
# ~ return main.empresa_borrar(values['rfc'])
|
||||
return main._delete_client(values['rfc'], False, False)
|
||||
|
||||
def respaldar_dbs(self):
|
||||
return main.respaldar_dbs()
|
||||
|
||||
def _get_empresas(self, values):
|
||||
return main.get_empresas()
|
||||
|
||||
def get_values(self, table, values=None, session=None):
|
||||
if table in ('allusuarios', 'usuarioupdate'):
|
||||
if table in ('allusuarios', 'usuarioupdate', 'main'):
|
||||
return getattr(self, '_get_{}'.format(table))(values, session)
|
||||
return getattr(self, '_get_{}'.format(table))(values)
|
||||
|
||||
|
@ -71,8 +70,8 @@ class StorageEngine(object):
|
|||
def _get_importinvoice(self, values):
|
||||
return main.import_invoice()
|
||||
|
||||
def _get_main(self, values):
|
||||
return main.config_main()
|
||||
def _get_main(self, values, session):
|
||||
return main.config_main(session['userobj'])
|
||||
|
||||
def _get_configtimbrar(self, values):
|
||||
return main.config_timbrar()
|
||||
|
@ -463,3 +462,7 @@ class StorageEngine(object):
|
|||
def nomina(self, values, user):
|
||||
return main.CfdiNomina.post(values, user)
|
||||
|
||||
# Companies only in MV
|
||||
def _get_empresas(self, values):
|
||||
return main.companies_get()
|
||||
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
# ~ You should have received a copy of the GNU General Public License
|
||||
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import argparse
|
||||
from decimal import Decimal
|
||||
import sqlite3
|
||||
import click
|
||||
from peewee import *
|
||||
from playhouse.fields import PasswordField, ManyToManyField
|
||||
from playhouse.shortcuts import case, SQL, cast
|
||||
|
@ -31,7 +31,7 @@ if __name__ == '__main__':
|
|||
|
||||
from controllers import util
|
||||
|
||||
from settings import log, DEBUG, COMPANIES, VERSION, PATH_CP, PRE, CURRENT_CFDI, \
|
||||
from settings import log, COMPANIES, VERSION, PATH_CP, PRE, CURRENT_CFDI, \
|
||||
INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \
|
||||
CANCEL_SIGNATURE, PUBLIC, DEFAULT_SERIE_TICKET, CURRENT_CFDI_NOMINA, \
|
||||
DEFAULT_SAT_NOMINA, DECIMALES_TAX, TITLE_APP, MV, DECIMALES_PRECIOS, \
|
||||
|
@ -40,6 +40,7 @@ from settings import log, DEBUG, COMPANIES, VERSION, PATH_CP, PRE, CURRENT_CFDI,
|
|||
# ~ v2
|
||||
from controllers import utils
|
||||
from settings import (
|
||||
DEBUG,
|
||||
DB_COMPANIES,
|
||||
EXT,
|
||||
IS_MV,
|
||||
|
@ -249,14 +250,18 @@ def get_doc(type_doc, id, rfc):
|
|||
return data, file_name, content_type
|
||||
|
||||
|
||||
def config_main():
|
||||
def config_main(user):
|
||||
try:
|
||||
obj = Emisor.select()[0]
|
||||
except IndexError:
|
||||
obj = None
|
||||
|
||||
punto_de_venta = Configuracion.get_bool('chk_usar_punto_de_venta')
|
||||
|
||||
nomina = Configuracion.get_bool('chk_usar_nomina')
|
||||
if not user.es_admin:
|
||||
nomina = False
|
||||
|
||||
data = {
|
||||
'empresa': get_title_app(3),
|
||||
'punto_de_venta': punto_de_venta,
|
||||
|
@ -426,6 +431,7 @@ class Configuracion(BaseModel):
|
|||
'chk_config_pagos',
|
||||
'chk_config_divisas',
|
||||
'chk_cfg_pays_data_bank',
|
||||
'chk_usar_nomina',
|
||||
)
|
||||
data = (Configuracion
|
||||
.select()
|
||||
|
@ -434,6 +440,9 @@ class Configuracion(BaseModel):
|
|||
values = {r.clave: util.get_bool(r.valor) for r in data}
|
||||
|
||||
fields = (
|
||||
'txt_ticket_printer',
|
||||
'txt_config_nomina_serie',
|
||||
'txt_config_nomina_folio',
|
||||
'txt_config_cfdipay_serie',
|
||||
'txt_config_cfdipay_folio',
|
||||
)
|
||||
|
@ -475,6 +484,17 @@ class Configuracion(BaseModel):
|
|||
values = {r.clave: util.get_bool(r.valor) for r in data}
|
||||
return values
|
||||
|
||||
@classmethod
|
||||
def get_value(cls, key, default=''):
|
||||
value = default
|
||||
data = (Configuracion
|
||||
.select(Configuracion.valor)
|
||||
.where(Configuracion.clave == key)
|
||||
)
|
||||
if data:
|
||||
value = data[0].valor
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def get_(cls, keys):
|
||||
if isinstance(keys, str):
|
||||
|
@ -535,7 +555,6 @@ class Configuracion(BaseModel):
|
|||
'chk_ticket_direct_print',
|
||||
'chk_ticket_edit_cant',
|
||||
'chk_ticket_total_up',
|
||||
'chk_usar_nomina',
|
||||
)
|
||||
data = (Configuracion
|
||||
.select()
|
||||
|
@ -543,15 +562,10 @@ class Configuracion(BaseModel):
|
|||
)
|
||||
values = {r.clave: util.get_bool(r.valor) for r in data}
|
||||
fields = (
|
||||
'txt_ticket_printer',
|
||||
'txt_config_nomina_serie',
|
||||
'txt_config_nomina_folio',
|
||||
'txt_config_cfdipay_serie',
|
||||
'txt_config_cfdipay_folio',
|
||||
('lst_pac', 'default'),
|
||||
)
|
||||
# ~ tp = 'txt_ticket_printer'
|
||||
for f in fields:
|
||||
values[f] = Configuracion.get_(f)
|
||||
for k, d in fields:
|
||||
values[k] = Configuracion.get_value(k, d)
|
||||
return values
|
||||
|
||||
if keys['fields'] == 'path_cer':
|
||||
|
@ -2632,10 +2646,17 @@ class Socios(BaseModel):
|
|||
def add(cls, values):
|
||||
accounts = util.loads(values.pop('accounts', '[]'))
|
||||
fields = cls._clean(cls, values)
|
||||
|
||||
w = ((Socios.rfc==fields['rfc']) & (Socios.slug==fields['slug']))
|
||||
if Socios.select().where(w).exists():
|
||||
msg = 'Ya existe el RFC y Razón Social'
|
||||
data = {'ok': False, 'row': {}, 'new': True, 'msg': msg}
|
||||
return data
|
||||
|
||||
try:
|
||||
obj = Socios.create(**fields)
|
||||
except IntegrityError as e:
|
||||
msg = 'Ya existe el RFC y Razón Social'
|
||||
msg = 'Ocurrio un error, al dar de alta el emisor'
|
||||
data = {'ok': False, 'row': {}, 'new': True, 'msg': msg}
|
||||
return data
|
||||
|
||||
|
@ -4987,10 +5008,15 @@ class Facturas(BaseModel):
|
|||
obj.save()
|
||||
|
||||
enviar_correo = util.get_bool(Configuracion.get_('correo_directo'))
|
||||
pac = Configuracion.get_('lst_pac').lower()
|
||||
|
||||
anticipo = False
|
||||
msg = 'Factura timbrada correctamente'
|
||||
result = util.timbra_xml(obj.xml, auth)
|
||||
if pac:
|
||||
result = utils.xml_stamp(obj.xml, auth, pac)
|
||||
else:
|
||||
result = util.timbra_xml(obj.xml, auth)
|
||||
|
||||
if result['ok']:
|
||||
obj.xml = result['xml']
|
||||
obj.uuid = result['uuid']
|
||||
|
@ -8996,7 +9022,7 @@ def _init_values(rfc):
|
|||
pass
|
||||
|
||||
if not Certificado.select().count():
|
||||
Certificado.create(rfc=rfc)
|
||||
Certificado.create(rfc=rfc.upper())
|
||||
|
||||
log.info('Valores iniciales insertados...')
|
||||
return
|
||||
|
@ -9234,64 +9260,6 @@ def _migrate_tables(rfc=''):
|
|||
return
|
||||
|
||||
|
||||
def _agregar_superusuario():
|
||||
args = util.get_con()
|
||||
if not args:
|
||||
return
|
||||
|
||||
conectar(args)
|
||||
usuario = input('Introduce el nuevo nombre para el superusuario: ').strip()
|
||||
if not usuario:
|
||||
msg = 'El nombre de usuario es requerido'
|
||||
log.erro(msg)
|
||||
return
|
||||
ok, contraseña = util.get_pass()
|
||||
if not ok:
|
||||
log.error(contraseña)
|
||||
return
|
||||
try:
|
||||
obj = Usuarios.create(
|
||||
usuario=usuario, contraseña=contraseña, es_superusuario=True)
|
||||
except IntegrityError:
|
||||
msg = 'El usuario ya existe'
|
||||
log.error(msg)
|
||||
return
|
||||
|
||||
log.info('SuperUsuario creado correctamente...')
|
||||
return
|
||||
|
||||
|
||||
def _cambiar_contraseña():
|
||||
args = util.get_con()
|
||||
if not args:
|
||||
return
|
||||
|
||||
conectar(args)
|
||||
usuario = input('Introduce el nombre de usuario: ').strip()
|
||||
if not usuario:
|
||||
msg = 'El nombre de usuario es requerido'
|
||||
log.error(msg)
|
||||
return
|
||||
|
||||
try:
|
||||
obj = Usuarios.get(usuario=usuario)
|
||||
except Usuarios.DoesNotExist:
|
||||
msg = 'El usuario no existe'
|
||||
log.error(msg)
|
||||
return
|
||||
|
||||
ok, contraseña = util.get_pass()
|
||||
if not ok:
|
||||
log.error(contraseña)
|
||||
return
|
||||
|
||||
obj.contraseña = contraseña
|
||||
obj.save()
|
||||
|
||||
log.info('Contraseña cambiada correctamente...')
|
||||
return
|
||||
|
||||
|
||||
def _add_emisor(rfc, args):
|
||||
util._valid_db_companies()
|
||||
con = sqlite3.connect(COMPANIES)
|
||||
|
@ -9349,49 +9317,20 @@ def _iniciar_bd():
|
|||
return
|
||||
|
||||
|
||||
def _agregar_rfc(no_bd):
|
||||
rfc = input('Introduce el nuevo RFC: ').strip().upper()
|
||||
if not rfc:
|
||||
msg = 'El RFC es requerido'
|
||||
log.error(msg)
|
||||
return
|
||||
|
||||
datos = input('Introduce los datos de conexión: ').strip()
|
||||
if not datos:
|
||||
msg = 'Los datos de conexión son requeridos'
|
||||
log.error(msg)
|
||||
return
|
||||
|
||||
opt = util.parse_con(datos)
|
||||
if not opt:
|
||||
log.error('Datos de conexión incompletos')
|
||||
return
|
||||
|
||||
args = opt.copy()
|
||||
if conectar(args):
|
||||
if _add_emisor(rfc, util.dumps(opt)):
|
||||
if no_bd:
|
||||
log.info('RFC agregado correctamente...')
|
||||
return
|
||||
_crear_tablas(rfc)
|
||||
log.info('RFC agregado correctamente...')
|
||||
return
|
||||
|
||||
log.error('No se pudo agregar el RFC')
|
||||
return
|
||||
|
||||
|
||||
def _borrar_rfc():
|
||||
rfc = input('Introduce el RFC a borrar: ').strip().upper()
|
||||
rfc = input('Introduce el RFC a borrar: ').strip().lower()
|
||||
if not rfc:
|
||||
msg = 'El RFC es requerido'
|
||||
log.error(msg)
|
||||
return
|
||||
|
||||
confirm = input('¿Estás seguro de borrar el RFC?')
|
||||
confirm = input('¿Estás seguro de borrar el RFC? [si]')
|
||||
if confirm != 'si':
|
||||
log.info('Proceso cancelado...')
|
||||
return
|
||||
|
||||
if _delete_emisor(rfc):
|
||||
util.delete_db(rfc.lower())
|
||||
if _delete_emisor(rfc.upper()):
|
||||
utils.db_delete(rfc, PATHS['BK'])
|
||||
log.info('RFC borrado correctamente...')
|
||||
return
|
||||
|
||||
|
@ -9460,9 +9399,7 @@ def empresa_agregar(rfc, no_bd):
|
|||
|
||||
|
||||
def empresa_borrar(rfc):
|
||||
if _delete_emisor(rfc):
|
||||
# ~ util.delete_db(rfc.lower())
|
||||
utils.db_delete(rfc.lower(), PATHS['DOCS'])
|
||||
utils.db_delete(rfc.lower(), PATHS['BK'])
|
||||
return True
|
||||
|
||||
|
||||
|
@ -9471,21 +9408,8 @@ def respaldar_dbs():
|
|||
msg = 'Solo MV'
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
result = util.validate_path_bk()
|
||||
if not result['ok']:
|
||||
return result
|
||||
path_bk = result['msg']
|
||||
|
||||
data = util.get_rfcs()
|
||||
if not len(data):
|
||||
msg = 'Sin bases de datos a respaldar'
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
for row in data:
|
||||
util.respaldar_db(row, path_bk)
|
||||
|
||||
msg = 'Bases de datos respaldadas correctamente'
|
||||
return {'ok': True, 'msg': msg}
|
||||
result = utils.db_backup_local()
|
||||
return result
|
||||
|
||||
|
||||
def _importar_valores(archivo='', rfc=''):
|
||||
|
@ -9515,7 +9439,7 @@ def _importar_valores(archivo='', rfc=''):
|
|||
try:
|
||||
with database_proxy.atomic() as txn:
|
||||
table.create(**r)
|
||||
except IntegrityError:
|
||||
except:
|
||||
pass
|
||||
|
||||
log.info('Importación terminada...')
|
||||
|
@ -9776,37 +9700,6 @@ def _importar_factura_libre(archivo):
|
|||
return
|
||||
|
||||
|
||||
def _importar_factura_libre_gambas(conexion):
|
||||
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)
|
||||
|
||||
log.info('Importando datos...')
|
||||
app = util.ImportFacturaLibreGambas(conexion, rfc)
|
||||
if not app.is_connect:
|
||||
log.error('\t{}'.format(app._error))
|
||||
return
|
||||
|
||||
data = app.import_data()
|
||||
|
||||
_importar_socios(data['Socios'])
|
||||
_importar_facturas(data['Facturas'])
|
||||
_importar_categorias(data['Categorias'])
|
||||
_importar_productos_gambas(data['Productos'])
|
||||
_import_tickets(data['Tickets'])
|
||||
|
||||
log.info('Importación terminada...')
|
||||
return
|
||||
|
||||
|
||||
def _exist_ticket(row):
|
||||
filters = (
|
||||
(Tickets.serie==row['serie']) &
|
||||
|
@ -9856,44 +9749,6 @@ def _import_tickets(rows):
|
|||
return
|
||||
|
||||
|
||||
def _importar_productos_gambas(rows):
|
||||
log.info('Importando productos...')
|
||||
|
||||
KEYS = {
|
||||
'Exento': '000',
|
||||
'ISR': '001',
|
||||
'IVA': '002',
|
||||
}
|
||||
|
||||
totals = len(rows)
|
||||
for i, row in enumerate(rows):
|
||||
msg = '\tGuardando producto {} de {}'.format(i+1, totals)
|
||||
log.info(msg)
|
||||
|
||||
source_taxes = row.pop('impuestos')
|
||||
row['unidad'] = SATUnidades.get(SATUnidades.key==row['unidad'])
|
||||
taxes = []
|
||||
for tax in source_taxes:
|
||||
w = {
|
||||
'key': KEYS[tax[0]],
|
||||
'name': tax[0],
|
||||
'tasa': float(tax[1]),
|
||||
'tipo': tax[2][0],
|
||||
}
|
||||
taxes.append(SATImpuestos.get_o_crea(w))
|
||||
|
||||
with database_proxy.transaction():
|
||||
try:
|
||||
obj = Productos.create(**row)
|
||||
obj.impuestos = taxes
|
||||
except IntegrityError as e:
|
||||
msg = '\tProducto ya existe'
|
||||
log.info(msg)
|
||||
|
||||
log.info('Importación terminada...')
|
||||
return
|
||||
|
||||
|
||||
def _importar_productos(archivo):
|
||||
rfc = input('Introduce el RFC: ').strip().upper()
|
||||
if not rfc:
|
||||
|
@ -10074,183 +9929,406 @@ def _exportar_documentos():
|
|||
return
|
||||
|
||||
|
||||
def _test():
|
||||
rfc = input('Introduce el RFC: ').strip().upper()
|
||||
# ~ v2
|
||||
|
||||
def companies_get():
|
||||
data = utils.rfc_get()
|
||||
rows = []
|
||||
for row in data:
|
||||
rows.append({'delete': '-', 'rfc': row[0].upper()})
|
||||
return tuple(rows)
|
||||
|
||||
|
||||
def _list_clients():
|
||||
rows = utils.rfc_get()
|
||||
for row in rows:
|
||||
msg = f'RFC: {row[0].upper()}'
|
||||
print(msg)
|
||||
return
|
||||
|
||||
|
||||
def _new_client(rfc, no_database):
|
||||
rfc = rfc.lower()
|
||||
if not rfc:
|
||||
msg = 'El RFC es requerido'
|
||||
log.error('Falta el RFC')
|
||||
return
|
||||
|
||||
if utils.rfc_exists(rfc):
|
||||
msg = 'El RFC ya esta dado de alta'
|
||||
log.error(msg)
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
user = rfc.replace('&', '').lower()
|
||||
if not no_database:
|
||||
if not utils.db_create(user):
|
||||
msg = 'No se pudo crear la base de datos'
|
||||
log.error(msg)
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
args = {
|
||||
'type': 'postgres',
|
||||
'name': user,
|
||||
'user': user,
|
||||
'password': user,
|
||||
}
|
||||
if not conectar(args.copy()):
|
||||
msg = 'No se pudo conectar a la base de datos'
|
||||
log.error(msg)
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
if not utils.rfc_add(rfc, args):
|
||||
msg = 'No se pudo guardar el nuevo emisor'
|
||||
log.error(msg)
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
if not no_database:
|
||||
if not _crear_tablas(rfc):
|
||||
msg = 'No se pudo crear las tablas'
|
||||
log.error(msg)
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
desconectar()
|
||||
msg = 'Emisor dado de alta correctamente'
|
||||
row = {'delete': '-', 'rfc': rfc.upper()}
|
||||
result = {'ok': True, 'msg': msg, 'row': row}
|
||||
return result
|
||||
|
||||
|
||||
def _delete_client(rfc, no_database, ask=True):
|
||||
rfc = rfc.lower()
|
||||
if not rfc:
|
||||
log.error('Falta el RFC')
|
||||
return
|
||||
|
||||
if not utils.rfc_exists(rfc):
|
||||
msg = 'El RFC no esta dado de alta'
|
||||
log.error(msg)
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
if ask:
|
||||
confirm = input('¿Estás seguro de borrar el RFC? [si]')
|
||||
if confirm != 'si':
|
||||
log.info('Proceso cancelado...')
|
||||
return False
|
||||
|
||||
if utils.db_delete(rfc, PATHS['BK'], no_database):
|
||||
log.info('RFC borrado correctamente...')
|
||||
result = True
|
||||
else:
|
||||
log.error('No se pudo borrar el RFC')
|
||||
result = False
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _update_sat():
|
||||
clients = utils.rfc_get()
|
||||
tables = utils.json_loads(PATHS['SAT'])
|
||||
for rfc, con in clients:
|
||||
log.info(f'Importando datos en: {rfc.upper()}')
|
||||
conectar(utils.loads(con))
|
||||
for table in tables:
|
||||
t = globals()[table['tabla']]
|
||||
with database_proxy.atomic():
|
||||
for r in table['datos']:
|
||||
try:
|
||||
t.get_or_create(**r)
|
||||
except:
|
||||
pass
|
||||
log.info(f"\tTabla importada: {table['tabla']}")
|
||||
desconectar()
|
||||
log.info('Importación terminada...')
|
||||
return
|
||||
|
||||
|
||||
def _new_superuser(rfc):
|
||||
rfc = rfc.lower()
|
||||
if not rfc:
|
||||
log.error('Falta el RFC')
|
||||
return
|
||||
|
||||
if not utils.rfc_exists(rfc):
|
||||
msg = 'El RFC no esta dado de alta'
|
||||
log.error(msg)
|
||||
return
|
||||
|
||||
args = util.get_con(rfc)
|
||||
if not args:
|
||||
args = utils.get_data_con(rfc)
|
||||
|
||||
user = input('Introduce el nuevo nombre para el superusuario: ').strip()
|
||||
if not user:
|
||||
msg = 'El nombre de usuario es requerido'
|
||||
log.error(msg)
|
||||
return
|
||||
|
||||
ok, password = utils.get_pass()
|
||||
if not ok:
|
||||
log.error(password)
|
||||
return
|
||||
|
||||
conectar(args)
|
||||
try:
|
||||
obj = Usuarios.create(
|
||||
usuario=user, contraseña=password, es_superusuario=True)
|
||||
msg = 'SuperUsuario creado correctamente...'
|
||||
log.info(msg)
|
||||
except IntegrityError:
|
||||
msg = 'El usuario ya existe'
|
||||
log.error(msg)
|
||||
|
||||
desconectar()
|
||||
return
|
||||
|
||||
|
||||
def _change_pass(rfc):
|
||||
rfc = rfc.lower()
|
||||
if not rfc:
|
||||
log.error('Falta el RFC')
|
||||
return
|
||||
|
||||
if not utils.rfc_exists(rfc):
|
||||
msg = 'El RFC no esta dado de alta'
|
||||
log.error(msg)
|
||||
return
|
||||
|
||||
args = utils.get_data_con(rfc)
|
||||
conectar(args)
|
||||
|
||||
user = input('Introduce el nombre de usuario: ').strip()
|
||||
if not user:
|
||||
msg = 'El nombre de usuario es requerido'
|
||||
log.error(msg)
|
||||
desconectar()
|
||||
return
|
||||
|
||||
try:
|
||||
obj = Usuarios.get(usuario=user)
|
||||
except Usuarios.DoesNotExist:
|
||||
msg = 'El usuario no existe'
|
||||
log.error(msg)
|
||||
desconectar()
|
||||
return
|
||||
|
||||
ok, password = utils.get_pass()
|
||||
if not ok:
|
||||
log.error(password)
|
||||
desconectar()
|
||||
return
|
||||
|
||||
obj.contraseña = password
|
||||
obj.save()
|
||||
|
||||
desconectar()
|
||||
log.info('Contraseña cambiada correctamente...')
|
||||
return
|
||||
|
||||
|
||||
def _process_command_line_arguments():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Empresa Libre')
|
||||
parser.add_argument('-lc', '--list-clients', dest='list_clients',
|
||||
action='store_true', default=False, required=False)
|
||||
parser.add_argument('-nc', '--new-client', dest='new_client',
|
||||
action='store_true', default=False, required=False)
|
||||
parser.add_argument('-dc', '--delete-client', dest='delete_client',
|
||||
action='store_true', default=False, required=False)
|
||||
parser.add_argument('-ndb', '--no-database', dest='no_database',
|
||||
action='store_true', default=False, required=False)
|
||||
parser.add_argument('-m', '--migrate', dest='migrate',
|
||||
action='store_true', default=False, required=False)
|
||||
parser.add_argument('-us', '--update-sat', dest='update_sat',
|
||||
action='store_true', default=False, required=False)
|
||||
parser.add_argument('-ns', '--new-superuser', dest='new_superuser',
|
||||
action='store_true', default=False, required=False)
|
||||
parser.add_argument('-cp', '--change-pass', dest='change_pass',
|
||||
action='store_true', default=False, required=False)
|
||||
parser.add_argument('-bk', '--backup', dest='backup',
|
||||
action='store_true', default=False, required=False)
|
||||
|
||||
parser.add_argument('-r', '--rfc', dest='rfc', default='')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main(args):
|
||||
if args.list_clients:
|
||||
_list_clients()
|
||||
return
|
||||
|
||||
if args.new_client:
|
||||
_new_client(args.rfc, args.no_database)
|
||||
return
|
||||
|
||||
if args.delete_client:
|
||||
_delete_client(args.rfc, args.no_database)
|
||||
return
|
||||
|
||||
if args.migrate:
|
||||
_migrate_tables(args.rfc)
|
||||
return
|
||||
|
||||
if args.update_sat:
|
||||
_update_sat()
|
||||
return
|
||||
|
||||
if args.new_superuser:
|
||||
_new_superuser(args.rfc)
|
||||
return
|
||||
|
||||
if args.change_pass:
|
||||
_change_pass(args.rfc)
|
||||
return
|
||||
|
||||
if args.backup:
|
||||
utils.db_backup(IS_MV, URL['SEAFILE'])
|
||||
|
||||
return
|
||||
|
||||
|
||||
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
||||
help_create_tables = 'Crea las tablas en la base de datos'
|
||||
help_migrate_db = 'Migra las tablas en la base de datos'
|
||||
help_superuser = 'Crea un nuevo super usuario'
|
||||
help_change_pass = 'Cambia la contraseña a un usuario'
|
||||
help_rfc = 'Agrega un nuevo RFC'
|
||||
help_br = 'Elimina un RFC'
|
||||
help_lr = 'Listar RFCs'
|
||||
# ~ CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
||||
# ~ help_create_tables = 'Crea las tablas en la base de datos'
|
||||
# ~ help_migrate_db = 'Migra las tablas en la base de datos'
|
||||
# ~ help_superuser = 'Crea un nuevo super usuario'
|
||||
# ~ help_change_pass = 'Cambia la contraseña a un usuario'
|
||||
# ~ help_rfc = 'Agrega un nuevo RFC'
|
||||
# ~ help_br = 'Elimina un RFC'
|
||||
# ~ help_lr = 'Listar RFCs'
|
||||
|
||||
@click.command(context_settings=CONTEXT_SETTINGS)
|
||||
@click.option('-bd', '--iniciar-bd',help=help_create_tables,
|
||||
is_flag=True, default=False)
|
||||
@click.option('-m', '--migrar-bd', help=help_migrate_db,
|
||||
is_flag=True, default=False)
|
||||
@click.option('-ns', '--nuevo-superusuario', help=help_superuser,
|
||||
is_flag=True, default=False)
|
||||
@click.option('-cc', '--cambiar-contraseña', help=help_change_pass,
|
||||
is_flag=True, default=False)
|
||||
@click.option('-ar', '--agregar-rfc', help=help_rfc, 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('-i', '--importar-valores', is_flag=True, default=False)
|
||||
@click.option('-f', '--archivo')
|
||||
@click.option('-c', '--conexion')
|
||||
@click.option('-fl', '--factura-libre', is_flag=True, default=False)
|
||||
@click.option('-flg', '--factura-libre-gambas', is_flag=True, default=False)
|
||||
@click.option('-t', '--test', is_flag=True, default=False)
|
||||
@click.option('-gap', '--generar-archivo-productos', is_flag=True, default=False)
|
||||
@click.option('-ip', '--importar-productos', is_flag=True, default=False)
|
||||
@click.option('-bk', '--backup-dbs', is_flag=True, default=False)
|
||||
@click.option('-n', '--no-bd', is_flag=True, default=False)
|
||||
@click.option('-a', '--alta', is_flag=True, default=False)
|
||||
@click.option('-r', '--rfc')
|
||||
@click.option('-d', '--detalle', is_flag=True, default=False)
|
||||
@click.option('-id', '--importar-directorio')
|
||||
@click.option('-ed', '--exportar_documentos', is_flag=True, default=False)
|
||||
def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña,
|
||||
agregar_rfc, borrar_rfc, listar_rfc, importar_valores, archivo, conexion,
|
||||
factura_libre, factura_libre_gambas, test, generar_archivo_productos,
|
||||
importar_productos, backup_dbs, no_bd, alta, rfc, detalle,
|
||||
importar_directorio, exportar_documentos):
|
||||
# ~ @click.command(context_settings=CONTEXT_SETTINGS)
|
||||
# ~ @click.option('-bd', '--iniciar-bd',help=help_create_tables,
|
||||
# ~ is_flag=True, default=False)
|
||||
# ~ @click.option('-m', '--migrar-bd', help=help_migrate_db,
|
||||
# ~ is_flag=True, default=False)
|
||||
# ~ @click.option('-ns', '--nuevo-superusuario', help=help_superuser,
|
||||
# ~ is_flag=True, default=False)
|
||||
# ~ @click.option('-cc', '--cambiar-contraseña', help=help_change_pass,
|
||||
# ~ 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('-i', '--importar-valores', is_flag=True, default=False)
|
||||
# ~ @click.option('-f', '--archivo')
|
||||
# ~ @click.option('-c', '--conexion')
|
||||
# ~ @click.option('-fl', '--factura-libre', is_flag=True, default=False)
|
||||
# ~ @click.option('-t', '--test', is_flag=True, default=False)
|
||||
# ~ @click.option('-gap', '--generar-archivo-productos', is_flag=True, default=False)
|
||||
# ~ @click.option('-ip', '--importar-productos', is_flag=True, default=False)
|
||||
# ~ @click.option('-bk', '--backup-dbs', is_flag=True, default=False)
|
||||
# ~ @click.option('-n', '--no-bd', is_flag=True, default=False)
|
||||
# ~ @click.option('-a', '--alta', is_flag=True, default=False)
|
||||
# ~ @click.option('-r', '--rfc')
|
||||
# ~ @click.option('-d', '--detalle', is_flag=True, default=False)
|
||||
# ~ @click.option('-id', '--importar-directorio')
|
||||
# ~ @click.option('-ed', '--exportar_documentos', is_flag=True, default=False)
|
||||
# ~ def main2(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña,
|
||||
# ~ borrar_rfc, listar_rfc, importar_valores, archivo, conexion,
|
||||
# ~ factura_libre, test, generar_archivo_productos,
|
||||
# ~ importar_productos, backup_dbs, no_bd, alta, rfc, detalle,
|
||||
# ~ importar_directorio, exportar_documentos):
|
||||
|
||||
opt = locals()
|
||||
# ~ opt = locals()
|
||||
|
||||
if opt['test']:
|
||||
_test()
|
||||
sys.exit(0)
|
||||
# ~ if opt['alta']:
|
||||
# ~ if not opt['rfc']:
|
||||
# ~ msg = 'Falta el RFC'
|
||||
# ~ raise click.ClickException(msg)
|
||||
# ~ _new_client(opt['rfc'], no_bd)
|
||||
# ~ sys.exit(0)
|
||||
|
||||
if opt['alta']:
|
||||
if not opt['rfc']:
|
||||
msg = 'Falta el RFC'
|
||||
raise click.ClickException(msg)
|
||||
empresa_agregar(opt['rfc'], no_bd)
|
||||
sys.exit(0)
|
||||
# ~ if opt['iniciar_bd']:
|
||||
# ~ _iniciar_bd()
|
||||
# ~ sys.exit(0)
|
||||
|
||||
if opt['iniciar_bd']:
|
||||
_iniciar_bd()
|
||||
sys.exit(0)
|
||||
# ~ if opt['migrar_bd']:
|
||||
# ~ _migrate_tables(rfc)
|
||||
# ~ sys.exit(0)
|
||||
|
||||
if opt['migrar_bd']:
|
||||
_migrate_tables(rfc)
|
||||
sys.exit(0)
|
||||
# ~ if opt['nuevo_superusuario']:
|
||||
# ~ _agregar_superusuario()
|
||||
# ~ sys.exit(0)
|
||||
|
||||
if opt['nuevo_superusuario']:
|
||||
_agregar_superusuario()
|
||||
sys.exit(0)
|
||||
# ~ if opt['cambiar_contraseña']:
|
||||
# ~ _cambiar_contraseña()
|
||||
# ~ sys.exit(0)
|
||||
|
||||
if opt['cambiar_contraseña']:
|
||||
_cambiar_contraseña()
|
||||
sys.exit(0)
|
||||
# ~ if opt['borrar_rfc']:
|
||||
# ~ _borrar_rfc()
|
||||
# ~ sys.exit(0)
|
||||
|
||||
if opt['agregar_rfc']:
|
||||
_agregar_rfc(no_bd)
|
||||
sys.exit(0)
|
||||
# ~ if opt['listar_rfc']:
|
||||
# ~ _listar_rfc(opt['detalle'])
|
||||
# ~ sys.exit(0)
|
||||
|
||||
if opt['borrar_rfc']:
|
||||
_borrar_rfc()
|
||||
sys.exit(0)
|
||||
# ~ if opt['importar_valores']:
|
||||
# ~ if not opt['archivo']:
|
||||
# ~ msg = 'Falta la ruta del archivo importar'
|
||||
# ~ raise click.ClickException(msg)
|
||||
# ~ if not util.is_file(opt['archivo']):
|
||||
# ~ msg = 'No es un archivo'
|
||||
# ~ raise click.ClickException(msg)
|
||||
|
||||
if opt['listar_rfc']:
|
||||
_listar_rfc(opt['detalle'])
|
||||
sys.exit(0)
|
||||
# ~ _importar_valores(opt['archivo'])
|
||||
# ~ sys.exit(0)
|
||||
|
||||
if opt['importar_valores']:
|
||||
if not opt['archivo']:
|
||||
msg = 'Falta la ruta del archivo importar'
|
||||
raise click.ClickException(msg)
|
||||
if not util.is_file(opt['archivo']):
|
||||
msg = 'No es un archivo'
|
||||
raise click.ClickException(msg)
|
||||
# ~ 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_valores(opt['archivo'])
|
||||
sys.exit(0)
|
||||
# ~ _importar_factura_libre(opt['archivo'])
|
||||
# ~ 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)
|
||||
# ~ if opt['generar_archivo_productos']:
|
||||
# ~ 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)
|
||||
# ~ _generar_archivo_productos(opt['archivo'])
|
||||
# ~ sys.exit(0)
|
||||
|
||||
if opt['factura_libre_gambas']:
|
||||
if not opt['conexion']:
|
||||
msg = 'Falta los datos de conexión'
|
||||
raise click.ClickException(msg)
|
||||
_importar_factura_libre_gambas(opt['conexion'])
|
||||
sys.exit(0)
|
||||
# ~ if opt['importar_productos']:
|
||||
# ~ if not opt['archivo']:
|
||||
# ~ msg = 'Falta la ruta del archivo'
|
||||
# ~ 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 != '.txt':
|
||||
# ~ msg = 'No es un archivo de texto'
|
||||
# ~ raise click.ClickException(msg)
|
||||
|
||||
if opt['generar_archivo_productos']:
|
||||
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_productos(opt['archivo'])
|
||||
# ~ sys.exit(0)
|
||||
|
||||
_generar_archivo_productos(opt['archivo'])
|
||||
sys.exit(0)
|
||||
# ~ if opt['importar_directorio']:
|
||||
# ~ _import_from_folder(opt['importar_directorio'])
|
||||
# ~ sys.exit(0)
|
||||
|
||||
if opt['importar_productos']:
|
||||
if not opt['archivo']:
|
||||
msg = 'Falta la ruta del archivo'
|
||||
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 != '.txt':
|
||||
msg = 'No es un archivo de texto'
|
||||
raise click.ClickException(msg)
|
||||
# ~ if opt['backup_dbs']:
|
||||
# ~ utils.db_backup(DB_COMPANIES, PATHS['BK'], IS_MV, URL['SEAFILE'])
|
||||
# ~ sys.exit(0)
|
||||
|
||||
_importar_productos(opt['archivo'])
|
||||
sys.exit(0)
|
||||
# ~ if opt['exportar_documentos']:
|
||||
# ~ _exportar_documentos()
|
||||
|
||||
if opt['importar_directorio']:
|
||||
_import_from_folder(opt['importar_directorio'])
|
||||
sys.exit(0)
|
||||
|
||||
if opt['backup_dbs']:
|
||||
# ~ util.backup_dbs()
|
||||
utils.db_backup(DB_COMPANIES, PATHS['BK'], IS_MV, URL['SEAFILE'])
|
||||
sys.exit(0)
|
||||
|
||||
if opt['exportar_documentos']:
|
||||
_exportar_documentos()
|
||||
|
||||
return
|
||||
# ~ return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
args = _process_command_line_arguments()
|
||||
main(args)
|
||||
# ~ main2()
|
||||
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ except ImportError:
|
|||
|
||||
|
||||
DEBUG = DEBUG
|
||||
VERSION = '1.32.0'
|
||||
VERSION = '1.33.0'
|
||||
EMAIL_SUPPORT = ('soporte@empresalibre.mx',)
|
||||
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
|
||||
|
||||
|
@ -200,6 +200,8 @@ CURRENCY_MN = 'MXN'
|
|||
IS_MV = MV
|
||||
DB_COMPANIES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'rfc.db'))
|
||||
path_bk = os.path.join(path_docs, 'tmp')
|
||||
path_local = 'facturas'
|
||||
path_sat = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'valores_iniciales.json'))
|
||||
|
||||
EXT = {
|
||||
'CSS': 'css',
|
||||
|
@ -218,6 +220,8 @@ PATHS = {
|
|||
'USER': path_user_template,
|
||||
'LOGOS': path_user_logos,
|
||||
'BK': path_bk,
|
||||
'LOCAL': path_local,
|
||||
'SAT': path_sat,
|
||||
}
|
||||
|
||||
VALUES_PDF = {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -120,6 +120,7 @@ var controllers = {
|
|||
$$('txt_config_cfdipay_serie').attachEvent('onKeyPress', txt_config_cfdipay_serie_press)
|
||||
$$('txt_config_cfdipay_folio').attachEvent('onKeyPress', txt_config_cfdipay_folio_press)
|
||||
$$('chk_usar_nomina').attachEvent('onItemClick', chk_config_item_click)
|
||||
$$('lst_pac').attachEvent('onChange', lst_pac_on_change)
|
||||
|
||||
$$('cmd_subir_bdfl').attachEvent('onItemClick', cmd_subir_bdfl_click)
|
||||
$$('cmd_subir_cfdixml').attachEvent('onItemClick', cmd_subir_cfdixml_click)
|
||||
|
@ -464,7 +465,11 @@ function get_config_values(opt){
|
|||
success: function(text, data, xhr) {
|
||||
var values = data.json()
|
||||
Object.keys(values).forEach(function(key){
|
||||
$$(key).setValue(values[key])
|
||||
if(key=='lst_pac'){
|
||||
set_value(key, values[key])
|
||||
}else{
|
||||
$$(key).setValue(values[key])
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -2534,3 +2539,32 @@ function opt_make_pdf_from_on_change(new_value, old_value){
|
|||
}
|
||||
|
||||
|
||||
function lst_pac_on_change(nv, ov){
|
||||
if(nv=='default'){
|
||||
webix.ajax().del('/config', {id: 'lst_pac'}, function(text, xml, xhr){
|
||||
var msg = 'PAC predeterminado establecido correctamente'
|
||||
if(xhr.status == 200){
|
||||
msg_ok(msg)
|
||||
}else{
|
||||
msg = 'No se pudo eliminar'
|
||||
msg_error(msg)
|
||||
}
|
||||
})
|
||||
}else{
|
||||
webix.ajax().post('/config', {'lst_pac': nv}, {
|
||||
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 = 'PAC establecido correctamente'
|
||||
msg_ok(msg)
|
||||
}else{
|
||||
msg_error(values.msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -579,3 +579,12 @@ function lst_clear(lst){
|
|||
function lst_parse(lst, values){
|
||||
lst.getList().parse(values)
|
||||
}
|
||||
|
||||
|
||||
function set_value(control, value){
|
||||
obj = $$(control)
|
||||
obj.blockEvent()
|
||||
obj.setValue(value)
|
||||
obj.unblockEvent()
|
||||
}
|
||||
|
||||
|
|
|
@ -643,6 +643,12 @@ var options_templates = [
|
|||
{}]
|
||||
|
||||
|
||||
var options_pac = [
|
||||
{id: 'default', value: 'Predeterminado'},
|
||||
{id: 'comercio', value: 'Comercio Digital'},
|
||||
]
|
||||
|
||||
|
||||
var options_admin_otros = [
|
||||
{maxHeight: 15},
|
||||
{template: 'Facturación', type: 'section'},
|
||||
|
@ -674,6 +680,13 @@ var options_admin_otros = [
|
|||
{view: 'checkbox', id: 'chk_config_decimales_precios', labelWidth: 0,
|
||||
labelRight: 'Precios con 4 decimales'}, {},
|
||||
]},
|
||||
{maxHeight: 15},
|
||||
{cols: [{maxWidth: 15},
|
||||
{view: 'richselect', id: 'lst_pac', name: 'lst_pac', width: 300,
|
||||
label: 'PAC: ', value: 'default', required: false,
|
||||
options: options_pac},
|
||||
{},
|
||||
]},
|
||||
{maxHeight: 20},
|
||||
{template: 'Ayudas varias', type: 'section'},
|
||||
{cols: [{maxWidth: 15},
|
||||
|
@ -700,15 +713,6 @@ var options_admin_otros = [
|
|||
labelRight: 'Mostrar total arriba'},
|
||||
{}]},
|
||||
{maxHeight: 20},
|
||||
{template: 'Nómina', type: 'section'},
|
||||
{cols: [{maxWidth: 15},
|
||||
{view: 'checkbox', id: 'chk_usar_nomina', labelWidth: 0,
|
||||
labelRight: 'Usar timbrado de Nómina'},
|
||||
{view: 'text', id: 'txt_config_nomina_serie', name: 'config_nomina_serie',
|
||||
label: 'Serie', labelWidth: 50, labelAlign: 'right'},
|
||||
{view: 'text', id: 'txt_config_nomina_folio', name: 'config_nomina_folio',
|
||||
label: 'Folio', labelWidth: 50, labelAlign: 'right'},
|
||||
{}]},
|
||||
{}]
|
||||
|
||||
|
||||
|
@ -738,12 +742,15 @@ var options_admin_products = [
|
|||
|
||||
var options_admin_complements = [
|
||||
{maxHeight: 20},
|
||||
{template: 'Complemento de Nómina', type: 'section'},
|
||||
{cols: [{maxWidth: 15},
|
||||
{view: 'checkbox', id: 'chk_config_ine', labelWidth: 0,
|
||||
labelRight: 'Usar el complemento INE'},
|
||||
{view: 'checkbox', id: 'chk_config_edu', labelWidth: 0,
|
||||
labelRight: 'Usar el complemento EDU'},
|
||||
{}]},
|
||||
{view: 'checkbox', id: 'chk_usar_nomina', labelWidth: 0,
|
||||
labelRight: 'Usar complemento de Nómina'},
|
||||
{view: 'text', id: 'txt_config_nomina_serie', name: 'config_nomina_serie',
|
||||
label: 'Serie', labelWidth: 50, labelAlign: 'right'},
|
||||
{view: 'text', id: 'txt_config_nomina_folio', name: 'config_nomina_folio',
|
||||
label: 'Folio', labelWidth: 50, labelAlign: 'right'},
|
||||
{maxWidth: 15}]},
|
||||
{maxHeight: 20},
|
||||
{template: 'Complemento de Pagos', type: 'section'},
|
||||
{cols: [{maxWidth: 15},
|
||||
|
@ -762,6 +769,18 @@ var options_admin_complements = [
|
|||
{view: 'checkbox', id: 'chk_config_divisas', labelWidth: 0,
|
||||
labelRight: 'Usar complemento de divisas'},
|
||||
{maxWidth: 15}]},
|
||||
{maxHeight: 20},
|
||||
{template: 'Complemento INE', type: 'section'},
|
||||
{cols: [{maxWidth: 15},
|
||||
{view: 'checkbox', id: 'chk_config_ine', labelWidth: 0,
|
||||
labelRight: 'Usar el complemento INE'},
|
||||
{maxWidth: 15}]},
|
||||
{maxHeight: 20},
|
||||
{template: 'Complemento para escuelas EDU', type: 'section'},
|
||||
{cols: [{maxWidth: 15},
|
||||
{view: 'checkbox', id: 'chk_config_edu', labelWidth: 0,
|
||||
labelRight: 'Usar el complemento EDU'},
|
||||
{maxWidth: 15}]},
|
||||
]
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue