commit
60528d8be1
|
@ -8,7 +8,7 @@
|
|||
|
||||
Este proyecto está en continuo desarrollo, contratar un esquema de soporte,
|
||||
nos ayuda a continuar su desarrollo. Ponte en contacto con nosotros para
|
||||
contratar.
|
||||
contratar: administracion@empresalibre.net
|
||||
|
||||
|
||||
### Requerimientos:
|
||||
|
@ -16,6 +16,9 @@ contratar.
|
|||
* Servidor web, recomendado Nginx
|
||||
* uwsgi
|
||||
* python3
|
||||
* xsltproc
|
||||
* openssl
|
||||
* xmlsec
|
||||
|
||||
Debería de funcionar con cualquier combinación servidor-wsgi que soporte
|
||||
aplicaciones Python.
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
from peewee import SqliteDatabase
|
||||
|
||||
|
||||
DEBUG = True
|
||||
ID_SUPPORT = ''
|
||||
|
||||
DATABASE = None
|
||||
if DEBUG:
|
||||
DATABASE = SqliteDatabase('empresalibre.sqlite')
|
||||
|
|
|
@ -371,7 +371,7 @@ class Finkok(object):
|
|||
if os.path.isfile(file_xml):
|
||||
root = etree.parse(file_xml).getroot()
|
||||
else:
|
||||
root = etree.fromstring(file_xml)
|
||||
root = etree.fromstring(file_xml.encode())
|
||||
|
||||
xml = etree.tostring(root)
|
||||
|
||||
|
@ -385,8 +385,12 @@ class Finkok(object):
|
|||
'store_pending': True,
|
||||
}
|
||||
|
||||
result = client.service.cancel_signature(**args)
|
||||
return result
|
||||
try:
|
||||
result = client.service.cancel_signature(**args)
|
||||
return result
|
||||
except Fault as e:
|
||||
self.error = str(e)
|
||||
return ''
|
||||
|
||||
def get_acuse(self, rfc, uuids, type_acuse='C'):
|
||||
for u in uuids:
|
||||
|
@ -443,21 +447,41 @@ class Finkok(object):
|
|||
return result
|
||||
|
||||
def add_token(self, rfc, email):
|
||||
"""
|
||||
"""Agrega un nuevo token al cliente para timbrado.
|
||||
Se requiere cuenta de reseller para usar este método
|
||||
|
||||
Args:
|
||||
rfc (str): El RFC del cliente, ya debe existir
|
||||
email (str): El correo del cliente, funciona como USER al timbrar
|
||||
|
||||
Returns:
|
||||
dict
|
||||
'username': 'username',
|
||||
'status': True or False
|
||||
'name': 'name',
|
||||
'success': True or False
|
||||
'token': 'Token de timbrado',
|
||||
'message': None
|
||||
"""
|
||||
auth = AUTH['RESELLER']
|
||||
|
||||
method = 'util'
|
||||
client = Client(
|
||||
URL[method], transport=self._transport, plugins=self._plugins)
|
||||
args = {
|
||||
'username': AUTH['USER'],
|
||||
'password': AUTH['PASS'],
|
||||
'username': auth['USER'],
|
||||
'password': auth['PASS'],
|
||||
'name': rfc,
|
||||
'token_username': email,
|
||||
'taxpayer_id': rfc,
|
||||
'status': True,
|
||||
}
|
||||
result = client.service.add_token(**args)
|
||||
try:
|
||||
result = client.service.add_token(**args)
|
||||
except Fault as e:
|
||||
self.error = str(e)
|
||||
return ''
|
||||
|
||||
return result
|
||||
|
||||
def get_date(self):
|
||||
|
@ -477,17 +501,31 @@ class Finkok(object):
|
|||
return result.datetime
|
||||
|
||||
def add_client(self, rfc, type_user=False):
|
||||
"""
|
||||
"""Agrega un nuevo cliente para timbrado.
|
||||
Se requiere cuenta de reseller para usar este método
|
||||
type_user: False == 'P' == Prepago or True == 'O' == On demand
|
||||
|
||||
Args:
|
||||
rfc (str): El RFC del nuevo cliente
|
||||
|
||||
Kwargs:
|
||||
type_user (bool): False == 'P' == Prepago or True == 'O' == On demand
|
||||
|
||||
Returns:
|
||||
dict
|
||||
'message':
|
||||
'Account Created successfully'
|
||||
'Account Already exists'
|
||||
'success': True or False
|
||||
"""
|
||||
auth = AUTH['RESELLER']
|
||||
|
||||
tu = {False: 'P', True: 'O'}
|
||||
method = 'client'
|
||||
client = Client(
|
||||
URL[method], transport=self._transport, plugins=self._plugins)
|
||||
args = {
|
||||
'reseller_username': AUTH['USER'],
|
||||
'reseller_password': AUTH['PASS'],
|
||||
'reseller_username': auth['USER'],
|
||||
'reseller_password': auth['PASS'],
|
||||
'taxpayer_id': rfc,
|
||||
'type_user': tu[type_user],
|
||||
'added': datetime.datetime.now().isoformat()[:19],
|
||||
|
@ -505,13 +543,15 @@ class Finkok(object):
|
|||
Se requiere cuenta de reseller para usar este método
|
||||
status = 'A' or 'S'
|
||||
"""
|
||||
auth = AUTH['RESELLER']
|
||||
|
||||
sv = {False: 'S', True: 'A'}
|
||||
method = 'client'
|
||||
client = Client(
|
||||
URL[method], transport=self._transport, plugins=self._plugins)
|
||||
args = {
|
||||
'reseller_username': AUTH['USER'],
|
||||
'reseller_password': AUTH['PASS'],
|
||||
'reseller_username': auth['USER'],
|
||||
'reseller_password': auth['PASS'],
|
||||
'taxpayer_id': rfc,
|
||||
'status': sv[status],
|
||||
}
|
||||
|
@ -524,15 +564,35 @@ class Finkok(object):
|
|||
return result
|
||||
|
||||
def get_client(self, rfc):
|
||||
"""
|
||||
"""Regresa el estatus del cliente
|
||||
.
|
||||
Se requiere cuenta de reseller para usar este método
|
||||
|
||||
Args:
|
||||
rfc (str): El RFC del emisor
|
||||
|
||||
Returns:
|
||||
dict
|
||||
'message': None,
|
||||
'users': {
|
||||
'ResellerUser': [
|
||||
{
|
||||
'status': 'A',
|
||||
'counter': 0,
|
||||
'taxpayer_id': '',
|
||||
'credit': 0
|
||||
}
|
||||
]
|
||||
} or None si no existe
|
||||
"""
|
||||
auth = AUTH['RESELLER']
|
||||
|
||||
method = 'client'
|
||||
client = Client(
|
||||
URL[method], transport=self._transport, plugins=self._plugins)
|
||||
args = {
|
||||
'reseller_username': AUTH['USER'],
|
||||
'reseller_password': AUTH['PASS'],
|
||||
'reseller_username': auth['USER'],
|
||||
'reseller_password': auth['PASS'],
|
||||
'taxpayer_id': rfc,
|
||||
}
|
||||
|
||||
|
@ -548,15 +608,30 @@ class Finkok(object):
|
|||
return result
|
||||
|
||||
def assign_client(self, rfc, credit):
|
||||
"""
|
||||
"""Agregar credito a un emisor
|
||||
|
||||
Se requiere cuenta de reseller para usar este método
|
||||
|
||||
Args:
|
||||
rfc (str): El RFC del emisor, debe existir
|
||||
credit (int): Cantidad de folios a agregar
|
||||
|
||||
Returns:
|
||||
dict
|
||||
'success': True or False,
|
||||
'credit': nuevo credito despues de agregar or None
|
||||
'message':
|
||||
'Success, added {credit} of credit to {RFC}'
|
||||
'RFC no encontrado'
|
||||
"""
|
||||
auth = AUTH['RESELLER']
|
||||
|
||||
method = 'client'
|
||||
client = Client(
|
||||
URL[method], transport=self._transport, plugins=self._plugins)
|
||||
args = {
|
||||
'username': AUTH['USER'],
|
||||
'password': AUTH['PASS'],
|
||||
'username': auth['USER'],
|
||||
'password': auth['PASS'],
|
||||
'taxpayer_id': rfc,
|
||||
'credit': credit,
|
||||
}
|
||||
|
@ -577,7 +652,7 @@ def _get_data_sat(path):
|
|||
if os.path.isfile(path):
|
||||
tree = etree.parse(path).getroot()
|
||||
else:
|
||||
tree = etree.fromstring(path)
|
||||
tree = etree.fromstring(path.encode())
|
||||
|
||||
data = {}
|
||||
emisor = escape(
|
||||
|
@ -602,7 +677,7 @@ def _get_data_sat(path):
|
|||
def get_status_sat(xml):
|
||||
data = _get_data_sat(xml)
|
||||
if not data:
|
||||
return
|
||||
return 'XML inválido'
|
||||
|
||||
URL = 'https://consultaqr.facturaelectronica.sat.gob.mx/ConsultaCFDIService.svc?wsdl'
|
||||
client = Client(URL, transport=Transport(cache=SqliteCache()))
|
||||
|
|
|
@ -33,7 +33,8 @@ from dateutil import parser
|
|||
|
||||
from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice
|
||||
from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \
|
||||
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE
|
||||
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \
|
||||
PATH_XMLSEC, TEMPLATE_CANCEL
|
||||
|
||||
|
||||
#~ def _get_hash(password):
|
||||
|
@ -52,7 +53,7 @@ def _get_md5(data):
|
|||
return hashlib.md5(data.encode()).hexdigest()
|
||||
|
||||
|
||||
def _save_temp(data, modo='wb'):
|
||||
def save_temp(data, modo='wb'):
|
||||
path = tempfile.mkstemp()[1]
|
||||
with open(path, modo) as f:
|
||||
f.write(data)
|
||||
|
@ -276,21 +277,22 @@ def to_slug(string):
|
|||
|
||||
class Certificado(object):
|
||||
|
||||
def __init__(self, key, cer):
|
||||
self._key = key
|
||||
self._cer = cer
|
||||
def __init__(self, paths):
|
||||
self._path_key = paths['path_key']
|
||||
self._path_cer = paths['path_cer']
|
||||
self._modulus = ''
|
||||
self._save_files()
|
||||
#~ self._save_files()
|
||||
self.error = ''
|
||||
|
||||
def _save_files(self):
|
||||
try:
|
||||
self._path_key = _save_temp(self._key)
|
||||
self._path_cer = _save_temp(self._cer)
|
||||
except:
|
||||
self._path_key = ''
|
||||
self._path_cer = ''
|
||||
return
|
||||
#~ def _save_files(self):
|
||||
#~ try:
|
||||
#~ self._path_key = _save_temp(bytes(self._key))
|
||||
#~ self._path_cer = _save_temp(bytes(self._cer))
|
||||
#~ except Exception as e:
|
||||
#~ log.error(e)
|
||||
#~ self._path_key = ''
|
||||
#~ self._path_cer = ''
|
||||
#~ return
|
||||
|
||||
def _kill(self, path):
|
||||
try:
|
||||
|
@ -341,8 +343,7 @@ class Certificado(object):
|
|||
hasta = parser.parse(dates[1].split('=')[1])
|
||||
self._modulus = _call(args.format(self._path_cer, 'modulus'))
|
||||
|
||||
data['cer'] = self._cer
|
||||
data['cer_tmp'] = None
|
||||
data['cer'] = read_file(self._path_cer)
|
||||
data['cer_pem'] = cer_pem
|
||||
data['cer_txt'] = cer_txt.replace('\n', '')
|
||||
data['serie'] = serie
|
||||
|
@ -365,7 +366,8 @@ class Certificado(object):
|
|||
'pass:"{}" -out "{}"'
|
||||
_call(args.format(tmp_cer, tmp_key, rfc,
|
||||
hashlib.md5(rfc.encode()).hexdigest(), tmp_p12))
|
||||
data = open(tmp_p12, 'rb').read()
|
||||
#~ data = open(tmp_p12, 'rb').read()
|
||||
data = read_file(tmp_p12)
|
||||
|
||||
self._kill(tmp_cer)
|
||||
self._kill(tmp_key)
|
||||
|
@ -396,18 +398,20 @@ class Certificado(object):
|
|||
self._path_key, password, _get_md5(rfc))
|
||||
key_enc = _call(args)
|
||||
|
||||
data['key'] = self._key
|
||||
data['key_tmp'] = None
|
||||
data['key'] = read_file(self._path_key)
|
||||
data['key_enc'] = key_enc
|
||||
data['p12'] = self._get_p12(password, rfc)
|
||||
return data
|
||||
|
||||
def validate(self, password, rfc):
|
||||
if not self._path_key or not self._path_cer:
|
||||
self.error = 'Error al cargar el certificado'
|
||||
self.error = 'Error en las rutas temporales del certificado'
|
||||
return {}
|
||||
|
||||
data = self._get_info_cer(rfc)
|
||||
if not data:
|
||||
return {}
|
||||
|
||||
llave = self._get_info_key(password, data['rfc'])
|
||||
if not llave:
|
||||
return {}
|
||||
|
@ -432,9 +436,9 @@ def make_xml(data, certificado):
|
|||
data = {
|
||||
'xsltproc': PATH_XSLTPROC,
|
||||
'xslt': _join(PATH_XSLT, 'cadena.xslt'),
|
||||
'xml': _save_temp(xml, 'w'),
|
||||
'xml': save_temp(xml, 'w'),
|
||||
'openssl': PATH_OPENSSL,
|
||||
'key': _save_temp(certificado.key_enc, 'w'),
|
||||
'key': save_temp(certificado.key_enc, 'w'),
|
||||
'pass': _get_md5(certificado.rfc)
|
||||
}
|
||||
args = '"{xsltproc}" "{xslt}" "{xml}" | ' \
|
||||
|
@ -473,6 +477,11 @@ def timbra_xml(xml, auth):
|
|||
return result
|
||||
|
||||
|
||||
def get_sat(xml):
|
||||
from .pac import get_status_sat
|
||||
return get_status_sat(xml)
|
||||
|
||||
|
||||
class LIBO(object):
|
||||
HOST = 'localhost'
|
||||
PORT = '8100'
|
||||
|
@ -1036,6 +1045,59 @@ def get_path_temp():
|
|||
return tempfile.mkstemp()[1]
|
||||
|
||||
|
||||
def get_date(value, next_day=False):
|
||||
d = parser.parse(value)
|
||||
if next_day:
|
||||
return d + datetime.timedelta(days=1)
|
||||
return d
|
||||
|
||||
|
||||
def cancel_cfdi(uuid, pk12, rfc, auth):
|
||||
from .pac import Finkok as PAC
|
||||
|
||||
template = read_file(TEMPLATE_CANCEL, 'r')
|
||||
data = {
|
||||
'rfc': rfc,
|
||||
'fecha': datetime.datetime.now().isoformat()[:19],
|
||||
'uuid': str(uuid).upper(),
|
||||
}
|
||||
template = template.format(**data)
|
||||
|
||||
data = {
|
||||
'xmlsec': PATH_XMLSEC,
|
||||
'pk12': save_temp(pk12),
|
||||
'pass': _get_md5(rfc),
|
||||
'template': save_temp(template, 'w'),
|
||||
}
|
||||
args = '"{xmlsec}" --sign --pkcs12 "{pk12}" --pwd {pass} ' \
|
||||
'"{template}"'.format(**data)
|
||||
xml_sign = _call(args)
|
||||
|
||||
if DEBUG:
|
||||
auth = {}
|
||||
else:
|
||||
if not auth:
|
||||
msg = 'Sin datos para cancelar'
|
||||
result = {'ok': False, 'error': msg}
|
||||
return result
|
||||
|
||||
msg = 'Factura cancelada correctamente'
|
||||
data = {'ok': True, 'msg': msg, 'row': {'estatus': 'Cancelada'}}
|
||||
pac = PAC(auth)
|
||||
result = pac.cancel_signature(xml_sign)
|
||||
if result:
|
||||
codes = {None: '',
|
||||
'Could not get UUID Text': 'UUID no encontrado'}
|
||||
if not result['CodEstatus'] is None:
|
||||
data['ok'] = False
|
||||
data['msg'] = codes.get(result['CodEstatus'], result['CodEstatus'])
|
||||
else:
|
||||
data['ok'] = False
|
||||
data['msg'] = pac.error
|
||||
|
||||
return data, result
|
||||
|
||||
|
||||
class ImportFacturaLibre(object):
|
||||
|
||||
def __init__(self, path):
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
socket = 127.0.0.1:3033
|
||||
uid = nginx
|
||||
gid = nginx
|
||||
#~ Establece una ruta accesible para nginx o el servidor web que uses
|
||||
chdir = /srv/app/empresa-libre/app
|
||||
wsgi-file = main.py
|
||||
callable = app
|
||||
|
@ -10,4 +11,5 @@ processes = 4
|
|||
threads = 4
|
||||
thunder-lock = true
|
||||
#~ stats = 127.0.0.1:9191
|
||||
#~ Establece una ruta accesible para nginx o el servidor web que uses
|
||||
logger = file:/srv/log/empresalibre-uwsgi.log
|
||||
|
|
|
@ -45,7 +45,6 @@ api.add_route('/products', AppProducts(db))
|
|||
api.add_route('/invoices', AppInvoices(db))
|
||||
|
||||
|
||||
|
||||
if DEBUG:
|
||||
api.add_sink(static, '/static')
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
[uwsgi]
|
||||
http = 127.0.0.1:8000
|
||||
#~ http = 37.228.132.181:9000
|
||||
wsgi-file = main.py
|
||||
callable = app
|
||||
master = true
|
||||
|
|
|
@ -20,8 +20,8 @@ class StorageEngine(object):
|
|||
def add_config(self, values):
|
||||
return main.Configuracion.add(values)
|
||||
|
||||
def add_cert(self, file_object):
|
||||
return main.Certificado.add(file_object)
|
||||
def add_cert(self, file_obj):
|
||||
return main.Certificado.add(file_obj)
|
||||
|
||||
def validate_cert(self, values, session):
|
||||
return main.Certificado.validate(values, session)
|
||||
|
@ -32,6 +32,15 @@ class StorageEngine(object):
|
|||
def send_email(self, values, session):
|
||||
return main.Facturas.send(values['id'], session['rfc'])
|
||||
|
||||
def _get_cancelinvoice(self, values):
|
||||
return main.Facturas.cancel(values['id'])
|
||||
|
||||
def _get_statussat(self, values):
|
||||
return main.Facturas.get_status_sat(values['id'])
|
||||
|
||||
def _get_filteryears(self, values):
|
||||
return main.Facturas.filter_years()
|
||||
|
||||
def _get_cert(self, values):
|
||||
return main.Certificado.get_data()
|
||||
|
||||
|
|
|
@ -64,6 +64,9 @@ class Configuracion(BaseModel):
|
|||
clave = TextField(unique=True)
|
||||
valor = TextField(default='')
|
||||
|
||||
def __str__(self):
|
||||
return '{} = {}'.format(self.clave, self.valor)
|
||||
|
||||
@classmethod
|
||||
def get_(cls, keys):
|
||||
if keys['fields'] == 'correo':
|
||||
|
@ -74,7 +77,14 @@ class Configuracion(BaseModel):
|
|||
.select()
|
||||
.where(Configuracion.clave.in_(fields))
|
||||
)
|
||||
values = {r.clave: r.valor for r in data}
|
||||
elif keys['fields'] == 'path_cer':
|
||||
fields = ('path_key', 'path_cer')
|
||||
data = (Configuracion
|
||||
.select()
|
||||
.where(Configuracion.clave.in_(fields))
|
||||
)
|
||||
|
||||
values = {r.clave: r.valor for r in data}
|
||||
return values
|
||||
|
||||
@classmethod
|
||||
|
@ -289,10 +299,8 @@ class Emisor(BaseModel):
|
|||
|
||||
class Certificado(BaseModel):
|
||||
key = BlobField(null=True)
|
||||
key_tmp = BlobField(null=True)
|
||||
key_enc = TextField(default='')
|
||||
cer = BlobField(null=True)
|
||||
cer_tmp = BlobField(null=True)
|
||||
cer_pem = TextField(default='')
|
||||
cer_txt = TextField(default='')
|
||||
p12 = BlobField(null=True)
|
||||
|
@ -316,28 +324,26 @@ class Certificado(BaseModel):
|
|||
return row
|
||||
|
||||
def get_(cls):
|
||||
if Certificado.select().count():
|
||||
obj = Certificado.select()[0]
|
||||
else:
|
||||
obj = Certificado()
|
||||
return obj
|
||||
return Certificado.select()[0]
|
||||
|
||||
@classmethod
|
||||
def add(cls, file_object):
|
||||
obj = cls.get_(cls)
|
||||
if file_object.filename.endswith('key'):
|
||||
obj.key_tmp = file_object.file.read()
|
||||
elif file_object.filename.endswith('cer'):
|
||||
obj.cer_tmp = file_object.file.read()
|
||||
obj.save()
|
||||
def add(cls, file_obj):
|
||||
if file_obj.filename.endswith('key'):
|
||||
path_key = util.save_temp(file_obj.file.read())
|
||||
Configuracion.add({'path_key': path_key})
|
||||
elif file_obj.filename.endswith('cer'):
|
||||
path_cer = util.save_temp(file_obj.file.read())
|
||||
Configuracion.add({'path_cer': path_cer})
|
||||
return {'status': 'server'}
|
||||
|
||||
@classmethod
|
||||
def validate(cls, values, session):
|
||||
row = {}
|
||||
result = False
|
||||
|
||||
obj = cls.get_(cls)
|
||||
cert = util.Certificado(obj.key_tmp, obj.cer_tmp)
|
||||
paths = Configuracion.get_({'fields': 'path_cer'})
|
||||
cert = util.Certificado(paths)
|
||||
data = cert.validate(values['contra'], session['rfc'])
|
||||
if data:
|
||||
msg = 'Certificado guardado correctamente'
|
||||
|
@ -352,9 +358,9 @@ class Certificado(BaseModel):
|
|||
}
|
||||
else:
|
||||
msg = cert.error
|
||||
obj.key_tmp = None
|
||||
obj.cer_tmp = None
|
||||
obj.save()
|
||||
|
||||
Configuracion.add({'path_key': ''})
|
||||
Configuracion.add({'path_cer': ''})
|
||||
|
||||
return {'ok': result, 'msg': msg, 'data': row}
|
||||
|
||||
|
@ -666,8 +672,10 @@ class Socios(BaseModel):
|
|||
es_proveedor = BooleanField(default=False)
|
||||
cuenta_cliente = TextField(default='')
|
||||
cuenta_proveedor = TextField(default='')
|
||||
saldo_cliente = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
saldo_proveedor = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
saldo_cliente = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
saldo_proveedor = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
web = TextField(default='')
|
||||
correo_facturas = TextField(default='')
|
||||
forma_pago = ForeignKeyField(SATFormaPago, null=True)
|
||||
|
@ -723,7 +731,7 @@ class Socios(BaseModel):
|
|||
.where((Socios.id==id) & (Socios.es_cliente==True))
|
||||
.dicts()
|
||||
)
|
||||
print (id, row)
|
||||
#~ print (id, row)
|
||||
if len(row):
|
||||
return {'ok': True, 'row': row[0]}
|
||||
return {'ok': False}
|
||||
|
@ -801,12 +809,17 @@ class Productos(BaseModel):
|
|||
clave_sat = TextField(default='')
|
||||
descripcion = TextField(index=True)
|
||||
unidad = ForeignKeyField(SATUnidades)
|
||||
valor_unitario = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
ultimo_costo = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
descuento = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
||||
auto_round=True)
|
||||
ultimo_costo = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
||||
auto_round=True)
|
||||
descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
||||
auto_round=True)
|
||||
inventario = BooleanField(default=False)
|
||||
existencia = DoubleField(default=0.0)
|
||||
minimo = DoubleField(default=0.0)
|
||||
existencia = DecimalField(default=0.0, max_digits=18, decimal_places=2,
|
||||
auto_round=True)
|
||||
minimo = DecimalField(default=0.0, max_digits=18, decimal_places=2,
|
||||
auto_round=True)
|
||||
codigo_barras = TextField(default='')
|
||||
cuenta_predial = TextField(default='')
|
||||
es_activo = BooleanField(default=True)
|
||||
|
@ -818,7 +831,11 @@ class Productos(BaseModel):
|
|||
|
||||
@classmethod
|
||||
def next_key(cls):
|
||||
value = Productos.select(fn.Max(Productos.id)).scalar()
|
||||
value = (Productos
|
||||
.select(fn.Max(Productos.id))
|
||||
.group_by(Productos.id)
|
||||
.order_by(Productos.id)
|
||||
.scalar())
|
||||
if value is None:
|
||||
value = 1
|
||||
else:
|
||||
|
@ -982,28 +999,35 @@ class Facturas(BaseModel):
|
|||
fecha_timbrado = DateTimeField(null=True)
|
||||
forma_pago = TextField(default='')
|
||||
condiciones_pago = TextField(default='')
|
||||
subtotal = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
descuento = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
moneda = TextField(default='MXN')
|
||||
tipo_cambio = DecimalField(default=1.0, decimal_places=6, auto_round=True)
|
||||
total = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
total_mn = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
total = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
total_mn = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
tipo_comprobante = TextField(default='I')
|
||||
metodo_pago = TextField(default='PUE')
|
||||
lugar_expedicion = TextField(default='')
|
||||
confirmacion = TextField(default='')
|
||||
uso_cfdi = TextField(default='')
|
||||
total_retenciones = DecimalField(
|
||||
decimal_places=6, auto_round=True, null=True)
|
||||
max_digits=20, decimal_places=6, auto_round=True, null=True)
|
||||
total_trasladados = DecimalField(
|
||||
decimal_places=6, auto_round=True, null=True)
|
||||
max_digits=20, decimal_places=6, auto_round=True, null=True)
|
||||
xml = TextField(default='')
|
||||
uuid = UUIDField(null=True)
|
||||
estatus = TextField(default='Guardada')
|
||||
estatus_sat = TextField(default='Vigente')
|
||||
regimen_fiscal = TextField(default='')
|
||||
notas = TextField(default='')
|
||||
pagada = BooleanField(default=False)
|
||||
cancelada = BooleanField(default=False)
|
||||
fecha_cancelacion = DateTimeField(null=True)
|
||||
acuse = TextField(default='')
|
||||
donativo = BooleanField(default=False)
|
||||
tipo_relacion = TextField(default='')
|
||||
error = TextField(default='')
|
||||
|
@ -1011,6 +1035,38 @@ class Facturas(BaseModel):
|
|||
class Meta:
|
||||
order_by = ('fecha',)
|
||||
|
||||
@classmethod
|
||||
def cancel(cls, id):
|
||||
msg = 'Factura cancelada correctamente'
|
||||
auth = Emisor.get_auth()
|
||||
certificado = Certificado.select()[0]
|
||||
obj = Facturas.get(Facturas.id==id)
|
||||
data, result = util.cancel_cfdi(
|
||||
obj.uuid, certificado.p12, certificado.rfc, auth)
|
||||
if data['ok']:
|
||||
obj.estatus = 'Cancelada'
|
||||
obj.error = ''
|
||||
obj.cancelada = True
|
||||
obj.fecha_cancelacion = result['Fecha']
|
||||
obj.acuse = result['Acuse']
|
||||
else:
|
||||
obj.error = data['msg']
|
||||
obj.save()
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def filter_years(cls):
|
||||
data = [{'id': -1, 'value': 'Todos'}]
|
||||
rows = (Facturas
|
||||
.select(Facturas.fecha.year)
|
||||
.group_by(Facturas.fecha.year)
|
||||
.order_by(Facturas.fecha.year)
|
||||
.scalar(as_tuple=True)
|
||||
)
|
||||
if not rows is None:
|
||||
data += [{'id': int(row), 'value': int(row)} for row in rows]
|
||||
return tuple(data)
|
||||
|
||||
@classmethod
|
||||
def get_xml(cls, id):
|
||||
obj = Facturas.get(Facturas.id==id)
|
||||
|
@ -1171,10 +1227,27 @@ class Facturas(BaseModel):
|
|||
|
||||
@classmethod
|
||||
def get_(cls, values):
|
||||
if 'start' in values:
|
||||
filters = Facturas.fecha.between(
|
||||
util.get_date(values['start']),
|
||||
util.get_date(values['end'], True)
|
||||
)
|
||||
else:
|
||||
if values['year'] == '-1':
|
||||
fy = (Facturas.fecha.year > 0)
|
||||
else:
|
||||
fy = (Facturas.fecha.year == int(values['year']))
|
||||
if values['month'] == '-1':
|
||||
fm = (Facturas.fecha.month > 0)
|
||||
else:
|
||||
fm = (Facturas.fecha.month == int(values['month']))
|
||||
filters = (fy & fm)
|
||||
|
||||
rows = tuple(Facturas
|
||||
.select(Facturas.id, Facturas.serie, Facturas.folio, Facturas.uuid,
|
||||
Facturas.fecha, Facturas.tipo_comprobante, Facturas.estatus,
|
||||
Facturas.total_mn, Socios.nombre.alias('cliente'))
|
||||
.where(filters)
|
||||
.join(Socios)
|
||||
.switch(Facturas).dicts()
|
||||
)
|
||||
|
@ -1190,6 +1263,8 @@ class Facturas(BaseModel):
|
|||
q.execute()
|
||||
q = FacturasImpuestos.delete().where(FacturasImpuestos.factura==obj)
|
||||
q.execute()
|
||||
q = FacturasRelacionadas.delete().where(FacturasRelacionadas.factura==obj)
|
||||
q.execute()
|
||||
return bool(obj.delete_instance())
|
||||
|
||||
def _get_folio(self, serie):
|
||||
|
@ -1198,6 +1273,8 @@ class Facturas(BaseModel):
|
|||
inicio = (Facturas
|
||||
.select(fn.Max(Facturas.folio))
|
||||
.where(Facturas.serie==serie)
|
||||
.group_by(Facturas.folio)
|
||||
.order_by(Facturas.folio)
|
||||
.scalar())
|
||||
if inicio is None:
|
||||
inicio = inicio_serie
|
||||
|
@ -1437,6 +1514,13 @@ class Facturas(BaseModel):
|
|||
}
|
||||
return util.make_xml(data, certificado)
|
||||
|
||||
@classmethod
|
||||
def get_status_sat(cls, id):
|
||||
obj = Facturas.get(Facturas.id == id)
|
||||
obj.estatus_sat = util.get_sat(obj.xml)
|
||||
obj.save()
|
||||
return obj.estatus_sat
|
||||
|
||||
@classmethod
|
||||
def timbrar(cls, id):
|
||||
obj = Facturas.get(Facturas.id == id)
|
||||
|
@ -1446,7 +1530,7 @@ class Facturas(BaseModel):
|
|||
|
||||
auth = Emisor.get_auth()
|
||||
|
||||
error = False
|
||||
#~ error = False
|
||||
msg = 'Factura timbrada correctamente'
|
||||
result = util.timbra_xml(obj.xml, auth)
|
||||
if result['ok']:
|
||||
|
@ -1455,10 +1539,10 @@ class Facturas(BaseModel):
|
|||
obj.fecha_timbrado = result['fecha']
|
||||
obj.estatus = 'Timbrada'
|
||||
obj.error = ''
|
||||
obj.save()
|
||||
#~ obj.save()
|
||||
row = {'uuid': obj.uuid, 'estatus': 'Timbrada'}
|
||||
else:
|
||||
error = True
|
||||
#~ error = True
|
||||
msg = result['error']
|
||||
obj.estatus = 'Error'
|
||||
obj.error = msg
|
||||
|
@ -1467,25 +1551,69 @@ class Facturas(BaseModel):
|
|||
return {'ok': result['ok'], 'msg': msg, 'row': row}
|
||||
|
||||
|
||||
class PreFacturas(BaseModel):
|
||||
cliente = ForeignKeyField(Socios)
|
||||
serie = TextField(default='PRE')
|
||||
folio = IntegerField(default=0)
|
||||
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
|
||||
forma_pago = TextField(default='')
|
||||
condiciones_pago = TextField(default='')
|
||||
subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
moneda = TextField(default='MXN')
|
||||
tipo_cambio = DecimalField(default=1.0, decimal_places=6, auto_round=True)
|
||||
total = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
total_mn = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
tipo_comprobante = TextField(default='I')
|
||||
metodo_pago = TextField(default='PUE')
|
||||
lugar_expedicion = TextField(default='')
|
||||
uso_cfdi = TextField(default='')
|
||||
total_retenciones = DecimalField(
|
||||
max_digits=20, decimal_places=6, auto_round=True, null=True)
|
||||
total_trasladados = DecimalField(
|
||||
max_digits=20, decimal_places=6, auto_round=True, null=True)
|
||||
estatus = TextField(default='Generada')
|
||||
regimen_fiscal = TextField(default='')
|
||||
notas = TextField(default='')
|
||||
donativo = BooleanField(default=False)
|
||||
tipo_relacion = TextField(default='')
|
||||
|
||||
class Meta:
|
||||
order_by = ('fecha',)
|
||||
|
||||
|
||||
class FacturasRelacionadas(BaseModel):
|
||||
factura = ForeignKeyField(Facturas, related_name='original')
|
||||
factura_origen = ForeignKeyField(Facturas, related_name='relacion')
|
||||
|
||||
class Meta:
|
||||
order_by = ('factura',)
|
||||
indexes = (
|
||||
(('factura', 'factura_origen'), True),
|
||||
)
|
||||
|
||||
class PreFacturasRelacionadas(BaseModel):
|
||||
factura = ForeignKeyField(PreFacturas, related_name='original')
|
||||
factura_origen = ForeignKeyField(PreFacturas, related_name='relacion')
|
||||
|
||||
class Meta:
|
||||
order_by = ('factura',)
|
||||
|
||||
|
||||
class FacturasDetalle(BaseModel):
|
||||
factura = ForeignKeyField(Facturas)
|
||||
producto = ForeignKeyField(Productos, null=True)
|
||||
cantidad = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
valor_unitario = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
descuento = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
precio_final = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
importe = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
cantidad = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
||||
auto_round=True)
|
||||
valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
||||
auto_round=True)
|
||||
descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
||||
auto_round=True)
|
||||
precio_final = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
||||
auto_round=True)
|
||||
importe = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
descripcion = TextField(default='')
|
||||
unidad = TextField(default='')
|
||||
clave = TextField(default='')
|
||||
|
@ -1504,11 +1632,54 @@ class FacturasDetalle(BaseModel):
|
|||
order_by = ('factura',)
|
||||
|
||||
|
||||
class PreFacturasDetalle(BaseModel):
|
||||
factura = ForeignKeyField(PreFacturas)
|
||||
producto = ForeignKeyField(Productos, null=True)
|
||||
cantidad = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
||||
auto_round=True)
|
||||
valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
||||
auto_round=True)
|
||||
descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
||||
auto_round=True)
|
||||
precio_final = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
||||
auto_round=True)
|
||||
importe = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
aduana = TextField(default='')
|
||||
pedimento = TextField(default='')
|
||||
fecha_pedimento = DateField(null=True)
|
||||
alumno = TextField(default='')
|
||||
curp = TextField(default='')
|
||||
nivel = TextField(default='')
|
||||
autorizacion = TextField(default='')
|
||||
cuenta_predial = TextField(default='')
|
||||
|
||||
class Meta:
|
||||
order_by = ('factura',)
|
||||
|
||||
|
||||
class FacturasImpuestos(BaseModel):
|
||||
factura = ForeignKeyField(Facturas)
|
||||
impuesto = ForeignKeyField(SATImpuestos)
|
||||
base = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
importe = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
||||
base = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
importe = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
||||
auto_round=True)
|
||||
|
||||
class Meta:
|
||||
order_by = ('factura',)
|
||||
indexes = (
|
||||
(('factura', 'impuesto'), True),
|
||||
)
|
||||
|
||||
|
||||
class PreFacturasImpuestos(BaseModel):
|
||||
factura = ForeignKeyField(PreFacturas)
|
||||
impuesto = ForeignKeyField(SATImpuestos)
|
||||
base = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
||||
auto_round=True)
|
||||
importe = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
||||
auto_round=True)
|
||||
|
||||
class Meta:
|
||||
order_by = ('factura',)
|
||||
|
@ -1596,12 +1767,14 @@ def test_correo(values):
|
|||
return util.send_mail(data)
|
||||
|
||||
|
||||
def _init_values():
|
||||
def _init_values(rfc):
|
||||
data = (
|
||||
{'clave': 'version', 'valor': VERSION},
|
||||
{'clave': 'rfc_publico', 'valor': 'XAXX010101000'},
|
||||
{'clave': 'rfc_extranjero', 'valor': 'XEXX010101000'},
|
||||
{'clave': 'decimales', 'valor': '2'},
|
||||
{'clave': 'path_key', 'valor': ''},
|
||||
{'clave': 'path_cer', 'valor': ''},
|
||||
)
|
||||
for row in data:
|
||||
try:
|
||||
|
@ -1609,6 +1782,10 @@ def _init_values():
|
|||
Configuracion.create(**row)
|
||||
except IntegrityError:
|
||||
pass
|
||||
|
||||
if not Certificado.select().count():
|
||||
Certificado.create(rfc=rfc)
|
||||
|
||||
log.info('Valores iniciales insertados...')
|
||||
return
|
||||
|
||||
|
@ -1617,6 +1794,8 @@ def _crear_tablas(rfc):
|
|||
tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion,
|
||||
Emisor, Facturas, FacturasDetalle, FacturasImpuestos, Folios,
|
||||
FacturasRelacionadas, Productos,
|
||||
PreFacturas, PreFacturasDetalle, PreFacturasImpuestos,
|
||||
PreFacturasRelacionadas,
|
||||
SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes,
|
||||
SATTipoRelacion, SATUnidades, SATUsoCfdi,
|
||||
Socios, Tags, Usuarios,
|
||||
|
@ -1639,7 +1818,7 @@ def _crear_tablas(rfc):
|
|||
msg = 'El usuario ya existe'
|
||||
log.error(msg)
|
||||
|
||||
_init_values()
|
||||
_init_values(rfc)
|
||||
_importar_valores('', rfc)
|
||||
|
||||
return True
|
||||
|
|
|
@ -11,7 +11,7 @@ from conf import DEBUG
|
|||
|
||||
|
||||
DEBUG = DEBUG
|
||||
VERSION = '0.1.0'
|
||||
VERSION = '0.2.0'
|
||||
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
|
||||
|
||||
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
|
@ -25,6 +25,8 @@ DB_SAT = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'sat.db'))
|
|||
|
||||
IV = 'valores_iniciales.json'
|
||||
INIT_VALUES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', IV))
|
||||
CT = 'cancel_template.xml'
|
||||
TEMPLATE_CANCEL = os.path.abspath(os.path.join(PATH_TEMPLATES, CT))
|
||||
|
||||
PATH_XSLT = os.path.abspath(os.path.join(BASE_DIR, '..', 'xslt'))
|
||||
PATH_BIN = os.path.abspath(os.path.join(BASE_DIR, '..', 'bin'))
|
||||
|
@ -50,6 +52,7 @@ if DEBUG:
|
|||
level=LOG_LEVEL,
|
||||
format_string=format_string).push_application()
|
||||
else:
|
||||
#~ Establece una ruta con acceso para nginx o el servidor web que uses
|
||||
LOG_PATH = '/srv/log/empresalibre.log'
|
||||
RotatingFileHandler(
|
||||
LOG_PATH,
|
||||
|
@ -69,9 +72,11 @@ log = Logger(LOG_NAME)
|
|||
|
||||
PATH_XSLTPROC = 'xsltproc'
|
||||
PATH_OPENSSL = 'openssl'
|
||||
PATH_XMLSEC = 'xmlsec1'
|
||||
if 'win' in sys.platform:
|
||||
PATH_XSLTPROC = os.path.join(PATH_BIN, 'xsltproc.exe')
|
||||
PATH_OPENSSL = os.path.join(PATH_BIN, 'openssl.exe')
|
||||
PATH_XMLSEC = os.path.join(PATH_BIN, 'xmlsec.exe')
|
||||
|
||||
|
||||
PRE = {
|
||||
|
|
|
@ -27,9 +27,7 @@ function get_series(){
|
|||
function get_forma_pago(){
|
||||
webix.ajax().get('/values/formapago', {key: true}, function(text, data){
|
||||
var values = data.json()
|
||||
//~ pre = values[0]
|
||||
$$('lst_forma_pago').getList().parse(values)
|
||||
//~ $$('lst_forma_pago').setValue(pre.id)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -697,7 +695,19 @@ function grid_invoices_click(id, e, node){
|
|||
|
||||
|
||||
function send_cancel(id){
|
||||
show(id)
|
||||
webix.ajax().get('/values/cancelinvoice', {id: id}, function(text, data){
|
||||
var values = data.json()
|
||||
if(values.ok){
|
||||
msg_sucess(values.msg)
|
||||
gi.updateItem(id, values.row)
|
||||
}else{
|
||||
webix.alert({
|
||||
title: 'Error al Cancelar',
|
||||
text: values.msg,
|
||||
type: 'alert-error'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function cmd_invoice_cancelar_click(){
|
||||
|
@ -717,6 +727,11 @@ function cmd_invoice_cancelar_click(){
|
|||
return
|
||||
}
|
||||
|
||||
if(row.estatus == 'Cancelada'){
|
||||
msg_error('La factura ya esta cancelada')
|
||||
return
|
||||
}
|
||||
|
||||
msg = '¿Estás seguro de enviar a cancelar esta factura?<BR><BR> \
|
||||
ESTA ACCIÓN NO SE PUEDE DESHACER'
|
||||
webix.confirm({
|
||||
|
@ -732,3 +747,87 @@ function cmd_invoice_cancelar_click(){
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function get_invoices(rango){
|
||||
if(rango == undefined){
|
||||
var fy = $$('filter_year')
|
||||
var fm = $$('filter_month')
|
||||
|
||||
var y = fy.getValue()
|
||||
var m = fm.getValue()
|
||||
rango = {'year': y, 'month': m}
|
||||
}
|
||||
|
||||
var grid = $$('grid_invoices')
|
||||
webix.ajax().get('/invoices', rango, {
|
||||
error: function(text, data, xhr) {
|
||||
webix.message({type: 'error', text: 'Error al consultar'})
|
||||
},
|
||||
success: function(text, data, xhr) {
|
||||
var values = data.json();
|
||||
grid.clearAll();
|
||||
if (values.ok){
|
||||
grid.parse(values.rows, 'json');
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function filter_year_change(nv, ov){
|
||||
get_invoices()
|
||||
}
|
||||
|
||||
|
||||
function filter_month_change(nv, ov){
|
||||
get_invoices()
|
||||
}
|
||||
|
||||
|
||||
function filter_dates_change(range){
|
||||
if(range.start != null && range.end != null){
|
||||
get_invoices(range)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function cmd_invoice_sat_click(){
|
||||
if(gi.count() == 0){
|
||||
return
|
||||
}
|
||||
|
||||
var row = gi.getSelectedItem()
|
||||
if (row == undefined){
|
||||
msg_error('Selecciona una factura')
|
||||
return
|
||||
}
|
||||
|
||||
if(!row.uuid){
|
||||
msg_error('La factura no esta timbrada, solo es posible consultar \
|
||||
el estatus en el SAT de facturas timbradas')
|
||||
return
|
||||
}
|
||||
|
||||
webix.ajax().get('/values/statussat', {id: row.id}, function(text, data){
|
||||
var values = data.json()
|
||||
show(values)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
function cmd_prefactura_click(){
|
||||
var form = this.getFormView()
|
||||
|
||||
if(!form.validate()) {
|
||||
msg_error('Valores inválidos')
|
||||
return
|
||||
}
|
||||
|
||||
var values = form.getValues()
|
||||
if(!validate_invoice(values)){
|
||||
return
|
||||
}
|
||||
show('PreFactura')
|
||||
}
|
||||
|
|
|
@ -44,8 +44,13 @@ var controllers = {
|
|||
$$('grid_details').attachEvent('onBeforeEditStart', grid_details_before_edit_start)
|
||||
$$('grid_details').attachEvent('onBeforeEditStop', grid_details_before_edit_stop)
|
||||
$$('cmd_invoice_timbrar').attachEvent('onItemClick', cmd_invoice_timbrar_click)
|
||||
$$('cmd_invoice_sat').attachEvent('onItemClick', cmd_invoice_sat_click)
|
||||
$$('cmd_invoice_cancelar').attachEvent('onItemClick', cmd_invoice_cancelar_click)
|
||||
$$('grid_invoices').attachEvent('onItemClick', grid_invoices_click)
|
||||
$$('filter_year').attachEvent('onChange', filter_year_change)
|
||||
$$('filter_month').attachEvent('onChange', filter_month_change)
|
||||
$$('filter_dates').attachEvent('onChange', filter_dates_change)
|
||||
$$('cmd_prefactura').attachEvent('onItemClick', cmd_prefactura_click)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,23 +97,6 @@ function get_products(){
|
|||
}
|
||||
|
||||
|
||||
function get_invoices(){
|
||||
var grid = $$('grid_invoices')
|
||||
webix.ajax().get('/invoices', {}, {
|
||||
error: function(text, data, xhr) {
|
||||
webix.message({type: 'error', text: 'Error al consultar'})
|
||||
},
|
||||
success: function(text, data, xhr) {
|
||||
var values = data.json();
|
||||
grid.clearAll();
|
||||
if (values.ok){
|
||||
grid.parse(values.rows, 'json');
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function menu_user_click(id, e, node){
|
||||
if (id == 1){
|
||||
window.location = '/logout';
|
||||
|
@ -117,6 +105,26 @@ function menu_user_click(id, e, node){
|
|||
}
|
||||
|
||||
|
||||
function current_dates(){
|
||||
var fy = $$('filter_year')
|
||||
var fm = $$('filter_month')
|
||||
var d = new Date()
|
||||
|
||||
fy.blockEvent()
|
||||
fm.blockEvent()
|
||||
|
||||
fm.setValue(d.getMonth() + 1)
|
||||
webix.ajax().sync().get('/values/filteryears', function(text, data){
|
||||
var values = data.json()
|
||||
fy.getList().parse(values)
|
||||
fy.setValue(d.getFullYear())
|
||||
})
|
||||
|
||||
fy.unblockEvent()
|
||||
fm.unblockEvent()
|
||||
}
|
||||
|
||||
|
||||
function multi_change(prevID, nextID){
|
||||
//~ webix.message(nextID)
|
||||
if(nextID == 'app_partners'){
|
||||
|
@ -138,6 +146,7 @@ function multi_change(prevID, nextID){
|
|||
if(nextID == 'app_invoices'){
|
||||
active = $$('multi_invoices').getActiveId()
|
||||
if(active == 'invoices_home'){
|
||||
current_dates()
|
||||
get_invoices()
|
||||
}
|
||||
gi = $$('grid_invoices')
|
||||
|
|
|
@ -14,12 +14,41 @@ var toolbar_invoices = [
|
|||
var toolbar_invoices_util = [
|
||||
{view: 'button', id: 'cmd_invoice_timbrar', label: 'Timbrar',
|
||||
type: 'iconButton', autowidth: true, icon: 'ticket'},
|
||||
{view: 'button', id: 'cmd_invoice_sat', label: 'SAT',
|
||||
type: 'iconButton', autowidth: true, icon: 'check-circle'},
|
||||
{},
|
||||
{view: 'button', id: 'cmd_invoice_cancelar', label: 'Cancelar',
|
||||
type: 'iconButton', autowidth: true, icon: 'ban'},
|
||||
]
|
||||
|
||||
|
||||
var months = [
|
||||
{id: -1, value: 'Todos'},
|
||||
{id: 1, value: 'Enero'},
|
||||
{id: 2, value: 'Febrero'},
|
||||
{id: 3, value: 'Marzo'},
|
||||
{id: 4, value: 'Abril'},
|
||||
{id: 5, value: 'Mayo'},
|
||||
{id: 6, value: 'Junio'},
|
||||
{id: 7, value: 'Julio'},
|
||||
{id: 8, value: 'Agosto'},
|
||||
{id: 9, value: 'Septiembre'},
|
||||
{id: 10, value: 'Octubre'},
|
||||
{id: 11, value: 'Noviembre'},
|
||||
{id: 12, value: 'Diciembre'},
|
||||
]
|
||||
|
||||
|
||||
var toolbar_invoices_filter = [
|
||||
{view: 'richselect', id: 'filter_year', label: 'Año', labelAlign: 'right',
|
||||
labelWidth: 50, width: 150, options: []},
|
||||
{view: 'richselect', id: 'filter_month', label: 'Mes', labelAlign: 'right',
|
||||
labelWidth: 50, width: 200, options: months},
|
||||
{view: 'daterangepicker', id: 'filter_dates', label: 'Fechas',
|
||||
labelAlign: 'right', width: 300},
|
||||
]
|
||||
|
||||
|
||||
function get_icon(tipo){
|
||||
var node = "<img src='/static/img/file-" + tipo + ".png' height='20' width='17' style='margin: 5px 0px'/>"
|
||||
return node
|
||||
|
@ -34,7 +63,8 @@ var grid_invoices_cols = [
|
|||
sort:"int", css: "cell_right"},
|
||||
{id: "uuid", header: ["UUID", {content: "textFilter"}], adjust: "data",
|
||||
sort:"string", hidden:true},
|
||||
{id: "fecha", header: ["Fecha y Hora"], adjust: "data", sort:"string"},
|
||||
{id: "fecha", header: ["Fecha y Hora"],
|
||||
adjust: "data", sort: "date"},
|
||||
{id: "tipo_comprobante", header: ["Tipo", {content: "selectFilter"}],
|
||||
adjust: 'header', sort: 'string'},
|
||||
{id: "estatus", header: ["Estatus", {content: "selectFilter"}],
|
||||
|
@ -228,6 +258,7 @@ var body_regimen_fiscal = {
|
|||
|
||||
|
||||
var controls_generate = [
|
||||
{minHeight: 15, maxHeight: 15},
|
||||
{cols: [ {rows:[
|
||||
{view: 'fieldset', label: 'Buscar Cliente', body: {rows: [
|
||||
{cols: [
|
||||
|
@ -272,26 +303,86 @@ var controls_generate = [
|
|||
{view: 'label', label: 'Detalle', height: 30, align: 'left'},
|
||||
grid_details,
|
||||
{minHeight: 15, maxHeight: 15},
|
||||
{cols: [{}, grid_totals]}
|
||||
{cols: [{}, grid_totals]},
|
||||
{minHeight: 15, maxHeight: 15},
|
||||
{margin: 20, cols: [{},
|
||||
{view: "button", id: "cmd_timbrar", label: "Timbrar",
|
||||
type: "form", autowidth: true, align:"center"},
|
||||
{view: "button", id: 'cmd_prefactura', label: "PreFactura",
|
||||
type: "form", autowidth: true, align:"center"},
|
||||
{}]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
var toolbar_preinvoices = [
|
||||
{view: 'button', id: 'cmd_facturar_preinvoice', label: 'Facturar',
|
||||
type: 'iconButton', autowidth: true, icon: 'pencil'},
|
||||
{},
|
||||
{view: "button", id: "cmd_delete_preinvoice", label: "Eliminar",
|
||||
type: "iconButton", autowidth: true, icon: "minus"},
|
||||
]
|
||||
|
||||
|
||||
var toolbar_prefilter = [
|
||||
{view: 'richselect', id: 'prefilter_year', label: 'Año', labelAlign: 'right',
|
||||
labelWidth: 50, width: 150, options: []},
|
||||
{view: 'richselect', id: 'prefilter_month', label: 'Mes', labelAlign: 'right',
|
||||
labelWidth: 50, width: 200, options: months},
|
||||
]
|
||||
|
||||
|
||||
var grid_preinvoices_cols = [
|
||||
{id: "id", header:"ID", hidden:true},
|
||||
{id: "folio", header: ["Folio", {content: "numberFilter"}], adjust: "data",
|
||||
sort:"int", css: "cell_right"},
|
||||
{id: "fecha", header: ["Fecha y Hora"],
|
||||
adjust: "data", sort: "date"},
|
||||
{id: "tipo_comprobante", header: ["Tipo", {content: "selectFilter"}],
|
||||
adjust: 'header', sort: 'string'},
|
||||
{id: 'total_mn', header: ['Total M.N.'], width: 150,
|
||||
sort: 'int', format: webix.i18n.priceFormat, css: 'right'},
|
||||
{id: "cliente", header: ["Razón Social", {content: "selectFilter"}],
|
||||
fillspace:true, sort:"string"},
|
||||
{id: 'pdf', header: 'PDF', adjust: 'data', template: get_icon('pdf')},
|
||||
{id: 'email', header: '', adjust: 'data', template: get_icon('email')}
|
||||
]
|
||||
|
||||
|
||||
var grid_preinvoices = {
|
||||
view: 'datatable',
|
||||
id: 'grid_preinvoices',
|
||||
select: 'row',
|
||||
adjust: true,
|
||||
footer: true,
|
||||
resizeColumn: true,
|
||||
headermenu: true,
|
||||
columns: grid_preinvoices_cols,
|
||||
}
|
||||
|
||||
|
||||
var controls_prefactura = [
|
||||
{view: 'toolbar', elements: toolbar_preinvoices},
|
||||
{view: 'toolbar', elements: toolbar_prefilter},
|
||||
grid_preinvoices,
|
||||
]
|
||||
|
||||
|
||||
var controls_invoices = [
|
||||
{
|
||||
view: "tabview",
|
||||
tabbar: {options: ["Generar"]}, animate: true,
|
||||
view: 'tabview',
|
||||
tabbar: {options: ['Generar', 'PreFacturas']}, animate: true,
|
||||
cells: [
|
||||
{id: "Generar", rows: controls_generate},
|
||||
{id: 'Generar', rows: controls_generate},
|
||||
{id: 'PreFacturas', rows: controls_prefactura},
|
||||
]
|
||||
},
|
||||
{rows: [
|
||||
{template:"", type: "section" },
|
||||
{margin: 10, cols: [{},
|
||||
{view: "button", id: "cmd_timbrar", label: "Timbrar",
|
||||
type: "form", autowidth: true, align:"center"},
|
||||
{view: 'button', id: 'cmd_close_invoice', label: 'Cancelar',
|
||||
type: 'danger', autowidth: true, align: 'center'},
|
||||
{}]
|
||||
type: 'danger', autowidth: true, align: 'center'}
|
||||
]
|
||||
},
|
||||
]}
|
||||
]
|
||||
|
@ -316,6 +407,7 @@ var multi_invoices = {
|
|||
{id: 'invoices_home', rows:[
|
||||
{view: 'toolbar', elements: toolbar_invoices},
|
||||
{view: 'toolbar', elements: toolbar_invoices_util},
|
||||
{view: 'toolbar', elements: toolbar_invoices_filter},
|
||||
grid_invoices,
|
||||
]},
|
||||
{id: 'invoices_new', rows:[form_invoice, {}]}
|
||||
|
|
|
@ -62,7 +62,8 @@ var ui_main = {
|
|||
{view: 'label', label: 'Empresa Libre'},
|
||||
{},
|
||||
menu_user,
|
||||
{view: 'button', type: 'icon', width: 45, css: 'app_button', icon: 'bell-o', badge: 1}
|
||||
{view: 'button', type: 'icon', width: 45, css: 'app_button',
|
||||
icon: 'bell-o', badge: 0}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Cancelacion RfcEmisor="{rfc}" Fecha="{fecha}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://cancelacfd.sat.gob.mx">
|
||||
<Folios>
|
||||
<UUID>{uuid}</UUID>
|
||||
</Folios>
|
||||
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
|
||||
<SignedInfo>
|
||||
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
|
||||
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
|
||||
<Reference URI="">
|
||||
<Transforms>
|
||||
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
|
||||
</Transforms>
|
||||
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
|
||||
<DigestValue />
|
||||
</Reference>
|
||||
</SignedInfo>
|
||||
<SignatureValue />
|
||||
<KeyInfo>
|
||||
<X509Data>
|
||||
<X509SubjectName />
|
||||
<X509IssuerSerial />
|
||||
<X509Certificate />
|
||||
</X509Data>
|
||||
<KeyValue />
|
||||
</KeyInfo>
|
||||
</Signature>
|
||||
</Cancelacion>
|
Loading…
Reference in New Issue