Merge branch 'develop'

Agregar tablas para Alumnos,  Tickets y Rangos de precios
This commit is contained in:
Mauricio Baeza 2017-12-18 02:17:00 -06:00
commit 48670592f3
15 changed files with 617 additions and 94 deletions

View File

@ -10,7 +10,7 @@ DEFAULT_PASSWORD = 'blades3.3'
#~ Establece una ruta accesible para el servidor web
LOG_PATH = '/srv/empresa/logs/empresalibre.log'
LOG_PATH = '/srv/empresa/logs/empresa-libre.log'
# ~ Establece los valores para sincronizar los backups de la base de datos
# ~ por ejemplo
@ -21,5 +21,4 @@ LOG_PATH = '/srv/empresa/logs/empresalibre.log'
# ~ 'REPO': 'id_repo',
# ~ }
SEAFILE_SERVER = {}

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
DEBUG = True
DEBUG = False
#~ Ecodex
ID_INTEGRADOR = ''

View File

@ -19,6 +19,7 @@ import uuid
import zipfile
from io import BytesIO
from pathlib import Path
from smtplib import SMTPException, SMTPAuthenticationError
from xml.etree import ElementTree as ET
@ -26,6 +27,7 @@ try:
import uno
from com.sun.star.beans import PropertyValue
from com.sun.star.awt import Size
from com.sun.star.view.PaperFormat import LETTER
APP_LIBO = True
except:
APP_LIBO = False
@ -35,9 +37,9 @@ from dateutil import parser
from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice, \
SeaFileAPI
from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \
from settings import DEBUG, MV, log, template_lookup, COMPANIES, DB_SAT, \
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \
PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES
PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES, DIR_FACTURAS
from settings import SEAFILE_SERVER
@ -843,6 +845,7 @@ class LIBO(object):
if self._template is None:
return b''
self._template.setPrinter(self._set_properties({'PaperFormat': LETTER}))
self._render(data)
path = '{}.ods'.format(tempfile.mkstemp()[1])
@ -1459,16 +1462,66 @@ def backup_dbs():
return
def _validar_directorios(path_bk, target):
path = Path(_join(path_bk, target))
path.mkdir(parents=True, exist_ok=True)
return str(path)
def local_copy(files):
if not MV:
return
path_bk = _join(str(Path.home()), DIR_FACTURAS)
if not os.path.isdir(path_bk):
msg = 'No existe la carpeta: facturas'
log.error(msg)
return
args = 'df -P {} | tail -1 | cut -d" " -f 1'.format(path_bk)
try:
result = _call(args)
log.info(result)
except:
pass
# ~ if result != 'empresalibre\n':
# ~ log.info(result)
# ~ msg = 'Asegurate de que exista la carpeta para sincronizar'
# ~ log.error(msg)
# ~ return
# ~ except subprocess.CalledProcessError:
# ~ msg = 'No se pudo obtener la ruta para sincronizar'
# ~ log.error(msg)
# ~ return
try:
for obj, name, target in files:
path = _validar_directorios(path_bk, target)
path_file = _join(path, name)
m = 'wb'
if name.endswith('xml'):
m = 'w'
save_file(path_file, obj, m)
except Exception as e:
log.error(e)
return
def sync_cfdi(auth, files):
local_copy(files)
if DEBUG:
return
if not auth['REPO']:
return
seafile = SeaFileAPI(SEAFILE_SERVER['URL'], auth['USER'], auth['PASS'])
if seafile.is_connect:
for f in files:
seafile.update_file(
f, auth['REPO'], 'Facturas/{}/'.format(f[2]), auth['PASS'])
return
@ -1671,7 +1724,7 @@ class ImportFacturaLibre(object):
data = []
for row in rows:
new = {t: row[s] for s, t in fields}
new = {t: row[s] for s, t in fields if row[s]}
data.append(new)
return data

View File

@ -18,7 +18,6 @@ from controllers.main import (AppEmpresas,
AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco,
AppMovimientosBanco
)
from settings import DEBUG
db = StorageEngine()
@ -51,13 +50,13 @@ api.add_route('/cuentasbanco', AppCuentasBanco(db))
api.add_route('/movbanco', AppMovimientosBanco(db))
if DEBUG:
api.add_sink(static, '/static')
# ~ Activa si usas waitress
# ~ api.add_sink(static, '/static')
session_options = {
'session.type': 'file',
'session.cookie_expires': True,
'session.cookie_expires': 3600,
'session.data_dir': '/tmp/cache/data',
'session.lock_dir': '/tmp/cache/lock',
}

View File

@ -6,3 +6,5 @@ master = true
processes = 4
threads = 4
py-autoreload = 1
thunder-lock = true
static-map = /static=../static

View File

@ -1,8 +1,13 @@
[uwsgi]
http = 127.0.0.1:8000
http = :8000
uid = user
gid = user
chdir = /home/USER/.opt/empresa-libre/source/app
wsgi-file = main.py
callable = app
master = true
processes = 4
threads = 4
thunder-lock = true
static-map = /static=../static
logger = file:../../../empresa-libre-uwsgi.log

View File

@ -226,6 +226,86 @@ class Tags(BaseModel):
order_by = ('tag',)
class TipoDireccion(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoTitulo(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoTelefono(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoCorreo(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoPariente(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoResponsable(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoMovimientoAlumno(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoMovimientoAlmacen(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class Usuarios(BaseModel):
usuario = TextField(unique=True)
nombre = TextField(default='')
@ -1074,6 +1154,45 @@ class SATBancos(BaseModel):
return {'ok': result}
class SATNivelesEducativos(BaseModel):
name = TextField(index=True)
class Meta:
order_by = ('name',)
def __str__(self):
return self.name
class NivelesEducativos(BaseModel):
nombre = TextField()
autorizacion = TextField(default='')
class Meta:
order_by = ('nombre',)
indexes = (
(('nombre', 'autorizacion'), True),
)
def __str__(self):
return '{} ({})'.format(self.nombre, self.autorizacion)
class Grupos(BaseModel):
nivel = ForeignKeyField(NivelesEducativos)
grado = TextField(default='')
nombre = TextField(default='')
class Meta:
order_by = ('nivel', 'grado', 'nombre')
indexes = (
(('nivel', 'grado', 'nombre'), True),
)
def __str__(self):
return '{} {} {}'.format(self.nivel.nombre, self.grado, self.nombre)
class CuentasBanco(BaseModel):
de_emisor = BooleanField(default=False)
activa = BooleanField(default=True)
@ -1671,6 +1790,132 @@ class Socios(BaseModel):
return bool(q.execute())
class Contactos(BaseModel):
socio = ForeignKeyField(Socios)
titulo = ForeignKeyField(TipoTitulo)
foto = TextField(default='')
nombre = TextField(index=True)
paterno = TextField(index=True)
materno = TextField(default='')
fecha_nacimiento = DateField(null=True)
notas = TextField(default='')
class Meta:
order_by = ('socio', 'nombre')
indexes = (
(('socio', 'nombre', 'paterno', 'materno'), True),
)
class ContactoDirecciones(BaseModel):
contacto = ForeignKeyField(Contactos)
tipo = ForeignKeyField(TipoDireccion)
direccion = TextField()
class Meta:
order_by = ('contacto',)
indexes = (
(('contacto', 'tipo', 'direccion'), True),
)
class ContactoTelefonos(BaseModel):
contacto = ForeignKeyField(Contactos)
tipo = ForeignKeyField(TipoTelefono)
telefono = TextField()
class Meta:
order_by = ('contacto',)
indexes = (
(('contacto', 'tipo', 'telefono'), True),
)
class ContactoCorreos(BaseModel):
contacto = ForeignKeyField(Contactos)
tipo = ForeignKeyField(TipoCorreo)
correo = TextField()
class Meta:
order_by = ('contacto',)
indexes = (
(('contacto', 'tipo', 'correo'), True),
)
class Alumnos(BaseModel):
rfc = TextField(null=True)
curp = TextField(index=True, unique=True)
foto = TextField(default='')
nombre = TextField(index=True)
paterno = TextField(index=True)
materno = TextField(default='')
calle = TextField(default='')
no_exterior = TextField(default='')
no_interior = TextField(default='')
colonia = TextField(default='')
municipio = TextField(default='')
estado = TextField(default='')
pais = TextField(default='')
codigo_postal = TextField(default='')
notas = TextField(default='')
telefonos = TextField(default='')
correos = TextField(default='')
es_activo = BooleanField(default=True)
fecha_alta = DateField(default=util.now)
fecha_nacimiento = DateField(null=True)
factura = ForeignKeyField(Socios, null=True)
grupo = ForeignKeyField(Grupos, null=True)
def __str__(self):
t = '{} {} {}'
return t.format(self.nombre, self.paterno, self.materno)
class Meta:
order_by = ('nombre', 'paterno')
class AlumnosParientes(BaseModel):
alumno = ForeignKeyField(Alumnos)
tipo_pariente = ForeignKeyField(TipoPariente)
foto = TextField(default='')
nombre = TextField(index=True)
paterno = TextField(index=True)
materno = TextField(default='')
fecha_nacimiento = DateField(null=True)
puede_recoger = BooleanField(default=False)
class Meta:
order_by = ('alumno',)
class ParienteDirecciones(BaseModel):
pariente = ForeignKeyField(AlumnosParientes)
tipo = ForeignKeyField(TipoDireccion)
direccion = TextField()
class Meta:
order_by = ('pariente',)
class ParienteTelefonos(BaseModel):
pariente = ForeignKeyField(AlumnosParientes)
tipo = ForeignKeyField(TipoTelefono)
telefono = TextField()
class Meta:
order_by = ('pariente',)
class ParienteCorreos(BaseModel):
pariente = ForeignKeyField(AlumnosParientes)
tipo = ForeignKeyField(TipoCorreo)
correo = TextField()
class Meta:
order_by = ('pariente',)
class Almacenes(BaseModel):
nombre = TextField(default='')
ubicacion = TextField(default='')
@ -1882,6 +2127,21 @@ class Productos(BaseModel):
return bool(q.execute())
class RangosPrecios(BaseModel):
producto = ForeignKeyField(Productos)
descripcion = TextField(default='')
desde = DecimalField(default=0.0, max_digits=18, decimal_places=6,
auto_round=True)
hasta = 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 = IntegerField(default=0)
class Meta:
order_by = ('producto',)
class Facturas(BaseModel):
cliente = ForeignKeyField(Socios)
version = TextField(default=CURRENT_CFDI)
@ -1967,6 +2227,7 @@ class Facturas(BaseModel):
def get_xml(cls, id):
obj = Facturas.get(Facturas.id==id)
name = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc)
cls._sync_xml(cls, obj)
return obj.xml, name
#~ Revisar
@ -2056,8 +2317,12 @@ class Facturas(BaseModel):
return values
@classmethod
def get_pdf(cls, id, rfc):
emisor = Emisor.select()[0]
def get_pdf(cls, id, rfc, sync=True):
try:
emisor = Emisor.select()[0]
except IndexError:
return b'', 'sin_datos_de_emisor.pdf'
obj = Facturas.get(Facturas.id==id)
name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc)
if obj.uuid is None:
@ -2066,6 +2331,11 @@ class Facturas(BaseModel):
values = cls._get_not_in_xml(cls, obj, emisor)
data = util.get_data_from_xml(obj, values)
doc = util.to_pdf(data, emisor.rfc)
if sync:
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
cls._sync_pdf(cls, doc, name, target)
return doc, name
@classmethod
@ -2095,6 +2365,27 @@ class Facturas(BaseModel):
def _sync(self, id, auth):
return Facturas.sync(id, auth)
@util.run_in_thread
def _sync_pdf(self, pdf, name_pdf, target):
auth = Emisor.get_auth()
files = (
(pdf, name_pdf, target),
)
util.sync_cfdi(auth, files)
return
@util.run_in_thread
def _sync_xml(self, obj):
emisor = Emisor.select()[0]
auth = Emisor.get_auth()
name_xml = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc)
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
files = (
(obj.xml, name_xml, target),
)
util.sync_cfdi(auth, files)
return
@util.run_in_thread
def _actualizar_saldo_cliente(self, invoice):
if invoice.tipo_comprobante == 'T':
@ -2159,17 +2450,15 @@ class Facturas(BaseModel):
@classmethod
def sync(cls, id, auth):
if not auth['REPO']:
return
obj = Facturas.get(Facturas.id==id)
if obj.uuid is None:
msg = 'La factura no esta timbrada'
return
pdf, name_pdf = cls.get_pdf(id, auth['RFC'])
emisor = Emisor.select()[0]
pdf, name_pdf = cls.get_pdf(id, auth['RFC'], False)
name_xml = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc)
target = str(obj.fecha)[:7].replace('-', '/')
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
files = (
(obj.xml, name_xml, target),
(pdf, name_pdf, target),
@ -2343,7 +2632,7 @@ class Facturas(BaseModel):
totals_tax = {}
total_trasladados = None
total_retenciones = None
total_iva = 0
# ~ total_iva = 0
locales_traslados = 0
locales_retenciones = 0
@ -2376,52 +2665,66 @@ class Facturas(BaseModel):
FacturasDetalle.create(**product)
base = product['importe'] - product['descuento']
for tax in p.impuestos:
impuesto_producto = round(float(tax.tasa) * base, DECIMALES)
if tax.tipo == 'T' and tax.key != '000':
total_trasladados = (total_trasladados or 0) + impuesto_producto
elif tax.tipo == 'R' and tax.key != '000':
total_retenciones = (total_retenciones or 0) + impuesto_producto
elif tax.tipo == 'T' and tax.key == '000':
locales_traslados += impuesto_producto
elif tax.tipo == 'R' and tax.key == '000':
locales_retenciones += impuesto_producto
if tax.id in totals_tax:
totals_tax[tax.id].importe += importe
totals_tax[tax.id].base += base
totals_tax[tax.id].suma_impuestos += impuesto_producto
else:
tax.importe = importe
tax.base = base
tax.suma_impuestos = impuesto_producto
totals_tax[tax.id] = tax
for tax in totals_tax.values():
if tax.tipo == 'E' or tax.tipo == 'R':
# ~ if tax.tipo == 'E' or tax.tipo == 'R':
if tax.tipo == 'E':
continue
import_tax = round(float(tax.tasa) * tax.importe, DECIMALES)
if tax.key == '000':
locales_traslados += import_tax
else:
total_trasladados = (total_trasladados or 0) + import_tax
if tax.name == 'IVA':
total_iva += import_tax
# ~ import_tax = round(float(tax.tasa) * tax.importe, DECIMALES)
# ~ if tax.key == '000':
# ~ locales_traslados += import_tax
# ~ else:
# ~ total_trasladados = (total_trasladados or 0) + import_tax
# ~ if tax.name == 'IVA':
# ~ total_iva += import_tax
invoice_tax = {
'factura': invoice.id,
'impuesto': tax.id,
'base': tax.importe,
'importe': import_tax,
'base': tax.base,
'importe': tax.suma_impuestos,
}
FacturasImpuestos.create(**invoice_tax)
for tax in totals_tax.values():
if tax.tipo == 'E' or tax.tipo == 'T':
continue
if tax.tasa == round(Decimal(2/3), 6):
import_tax = round(float(tax.tasa) * total_iva, DECIMALES)
else:
import_tax = round(float(tax.tasa) * tax.importe, DECIMALES)
if tax.key == '000':
locales_retenciones += import_tax
else:
total_retenciones = (total_retenciones or 0) + import_tax
# ~ for tax in totals_tax.values():
# ~ if tax.tipo == 'E' or tax.tipo == 'T':
# ~ continue
# ~ if tax.tasa == round(Decimal(2/3), 6):
# ~ import_tax = round(float(tax.tasa) * total_iva, DECIMALES)
# ~ else:
# ~ import_tax = round(float(tax.tasa) * tax.importe, DECIMALES)
# ~ if tax.key == '000':
# ~ locales_retenciones += import_tax
# ~ else:
# ~ total_retenciones = (total_retenciones or 0) + import_tax
invoice_tax = {
'factura': invoice.id,
'impuesto': tax.id,
'base': tax.importe,
'importe': import_tax,
}
FacturasImpuestos.create(**invoice_tax)
# ~ invoice_tax = {
# ~ 'factura': invoice.id,
# ~ 'impuesto': tax.id,
# ~ 'base': tax.base,
# ~ 'importe': tax.suma_impuestos,
# ~ }
# ~ FacturasImpuestos.create(**invoice_tax)
total = subtotal - descuento_cfdi + \
(total_trasladados or 0) - (total_retenciones or 0) \
@ -3169,24 +3472,6 @@ class FacturasComplementos(BaseModel):
return {r.nombre: util.loads(r.valores) for r in query}
# ~ class CfdiPagosFacturas(BaseModel):
# ~ pago = ForeignKeyField(CfdiPagos)
# ~ factura = ForeignKeyField(Facturas)
# ~ numero = IntegerField(default=1)
# ~ saldo_anterior = 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)
# ~ saldo = DecimalField(default=0.0, max_digits=18, decimal_places=6,
# ~ auto_round=True)
# ~ class Meta:
# ~ order_by = ('pago',)
# ~ indexes = (
# ~ (('pago', 'factura', 'numero'), True),
# ~ )
class PreFacturasRelacionadas(BaseModel):
factura = ForeignKeyField(PreFacturas, related_name='original')
factura_origen = ForeignKeyField(PreFacturas, related_name='relacion')
@ -3468,6 +3753,94 @@ class PreFacturasImpuestos(BaseModel):
return data
class CamposPersonalizados(BaseModel):
nombre = TextField()
slug = TextField(unique=True)
class Meta:
order_by = ('nombre',)
class FacturasPersonalizados(BaseModel):
factura = ForeignKeyField(Facturas)
campo = TextField()
valor = TextField()
class Meta:
order_by = ('factura',)
class Tickets(BaseModel):
cliente = ForeignKeyField(Socios, null=True)
serie = TextField(default='')
folio = IntegerField(default=0)
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
forma_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)
total = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
total_trasladados = DecimalField(
max_digits=20, decimal_places=6, auto_round=True, null=True)
estatus = TextField(default='Generado')
notas = TextField(default='')
factura = ForeignKeyField(Facturas, null=True)
cancelado = BooleanField(default=False)
vendedor = TextField(default='')
comision = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
cambio = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
class Meta:
order_by = ('fecha',)
class TicketsDetalle(BaseModel):
ticket = ForeignKeyField(Tickets)
producto = ForeignKeyField(Productos, null=True)
descripcion = TextField(default='')
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)
class Meta:
order_by = ('ticket',)
class TicketsImpuestos(BaseModel):
ticket = ForeignKeyField(Tickets)
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 = ('ticket',)
indexes = (
(('ticket', 'impuesto'), True),
)
class SeriesProductos(BaseModel):
factura = ForeignKeyField(FacturasDetalle, null=True)
ticket = ForeignKeyField(TicketsDetalle, null=True)
serie = TextField(default='')
class Meta:
order_by = ('serie',)
def authenticate(args):
respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''}
values = util.get_con(args['rfc'])
@ -3583,15 +3956,22 @@ def _init_values(rfc):
def _crear_tablas(rfc):
tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion,
Folios, Registro,
Folios, Registro, CamposPersonalizados,
Emisor, Facturas, FacturasDetalle, FacturasImpuestos, FacturasPagos,
FacturasRelacionadas, FacturasComplementos, Almacenes, Productos,
FacturasRelacionadas, FacturasComplementos, FacturasPersonalizados,
SeriesProductos, Almacenes, Productos, RangosPrecios,
PreFacturas, PreFacturasDetalle, PreFacturasImpuestos,
PreFacturasRelacionadas,
PreFacturasRelacionadas, Tickets, TicketsDetalle, TicketsImpuestos,
SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes,
SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos,
Socios, Tags, Usuarios, CuentasBanco, TipoCambio, MovimientosBanco,
CfdiPagos,
SATNivelesEducativos,
Socios, Contactos, ContactoCorreos, ContactoDirecciones,
ContactoTelefonos,
Tags, Usuarios, CuentasBanco, TipoCambio, MovimientosBanco,
TipoCorreo, TipoDireccion, TipoPariente, TipoResponsable, TipoTelefono,
TipoTitulo, TipoMovimientoAlumno, TipoMovimientoAlmacen,
CfdiPagos, NivelesEducativos, Alumnos, AlumnosParientes, Grupos,
ParienteDirecciones, ParienteTelefonos, ParienteCorreos,
Emisor.regimenes.get_through_model(),
Socios.tags.get_through_model(),
Productos.impuestos.get_through_model(),

View File

@ -21,7 +21,7 @@ except ImportError:
DEBUG = DEBUG
VERSION = '0.2.1'
VERSION = '1.2.0'
EMAIL_SUPPORT = ('soporte@empresalibre.net',)
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
@ -115,3 +115,4 @@ IMPUESTOS = {
'CEDULAR': '000',
}
DEFAULT_SAT_PRODUCTO = '01010101'
DIR_FACTURAS = 'facturas'

BIN
source/bin/libeay32.dll Normal file

Binary file not shown.

BIN
source/bin/openssl.exe Normal file

Binary file not shown.

BIN
source/bin/ssleay32.dll Normal file

Binary file not shown.

View File

@ -27,6 +27,16 @@
{"key": "ACT", "name": "Actividad", "activo": false}
]
},
{
"tabla": "SATNivelesEducativos",
"datos": [
{"name": "Preescolar"},
{"name": "Primaria"},
{"name": "Secundaria"},
{"name": "Profesional técnico"},
{"name": "Bachillerato o su equivalente"}
]
},
{
"tabla": "SATTipoRelacion",
"datos": [

View File

@ -82,6 +82,22 @@ function get_regimen_fiscal(){
}
function validar_timbrar(){
webix.ajax().sync().get('/values/validartimbrar', function(text, data){
var values = data.json()
if(!values.ok){
msg_error(values.msg)
$$('cmd_timbrar').disable()
}else{
if(values.msg){
msg_error(values.msg)
}
$$('cmd_timbrar').enable()
}
})
}
function default_config(){
webix.ajax().sync().get('/values/taxes', function(text, data){
var values = data.json()
@ -96,18 +112,7 @@ function default_config(){
table_pt.clear()
table_totals.clear()
webix.ajax().sync().get('/values/validartimbrar', function(text, data){
var values = data.json()
if(!values.ok){
msg_error(values.msg)
$$('cmd_timbrar').disable()
}else{
if(values.msg){
msg_error(values.msg)
}
$$('cmd_timbrar').enable()
}
})
validar_timbrar()
webix.ajax().sync().get('/values/configtimbrar', function(text, data){
var values = data.json()
@ -702,6 +707,69 @@ function calculate_taxes(){
}
function calcular_impuestos(){
var tmp = null
var subtotal = 0
var id = 2
var grid_totals = $$('grid_totals')
var impuesto_producto = 0
var impuesto = null
table_totals.clear()
grid_totals.clearAll()
grid_totals.add({id: 1, concepto: 'SubTotal', importe: 0})
grid.eachRow(function(row){
var product = grid.getItem(row)
var importe = parseFloat(product.importe)
subtotal += importe
query = table_pt.chain().find({'product': product.id}).data()
for(var tax of query){
impuesto = table_taxes.findOne({'id': tax.tax})
if(impuesto.tipo == 'E'){
continue
}
var base = importe
if(impuesto.tipo == 'R'){
base = (importe * -1).round(DECIMALES)
}
impuesto_producto = (impuesto.tasa * base).round(DECIMALES)
tmp = table_totals.findOne({'tax': tax.tax})
if(tmp === null){
table_totals.insert({'tax': tax.tax, 'importe': impuesto_producto})
}else{
tmp.importe += impuesto_producto
table_totals.update(tmp)
}
}
})
var tipo = ''
var concepto = ''
query = table_totals.chain().data()
for(var t of query){
tax = table_taxes.findOne({'id': t.tax})
if(tax.tipo == 'E'){
continue
}
tipo = 'Traslado '
if(tax.tipo == 'R'){
tipo = 'Retención '
}
concepto = tipo + tax.name + ' (' + tax.tasa + ')'
grid_totals.add({id: id, concepto: concepto, importe: t.importe})
id += 1
}
var row = {importe: subtotal}
grid_totals.updateItem(1, row)
}
function set_product(values){
var taxes = values.taxes
var values = values.row
@ -729,7 +797,8 @@ function set_product(values){
table_pt.insert(v)
}
}
calculate_taxes()
//~ calculate_taxes()
calcular_impuestos()
}
@ -846,7 +915,8 @@ function grid_details_before_edit_stop(state, editor){
row['importe'] = (cantidad * precio_final).round(DECIMALES)
grid.refresh()
calculate_taxes()
//~ calculate_taxes()
calcular_impuestos()
}
@ -855,7 +925,8 @@ function grid_details_click(id, e, node){
return
}
grid.remove(id.row)
calculate_taxes()
//~ calculate_taxes()
calcular_impuestos()
}
@ -874,7 +945,8 @@ function grid_details_header_click(id){
callback:function(result){
if (result){
grid.clearAll()
calculate_taxes()
//~ calculate_taxes()
calcular_impuestos()
}
}
})
@ -1299,7 +1371,8 @@ function refacturar_preinvoice(id){
for(var p of values.rows){
agregar_preproducto(p)
}
calculate_taxes()
//~ calculate_taxes()
calcular_impuestos()
$$('tv_invoice').getTabbar().setValue('Generar')
}
})

View File

@ -200,6 +200,7 @@ function multi_change(prevID, nextID){
if(active == 'invoices_home'){
current_dates()
get_invoices()
validar_timbrar()
}
gi = $$('grid_invoices')
return

View File

@ -10,7 +10,7 @@ var grid_cfdi_cliente_cols = [
css: 'right'},
{id: 'uuid', header: ['UUID', {content: 'textFilter'}], width: 250,
sort: 'string'},
{id: "fecha", header: ["Fecha y Hora"], width: 150, sort: 'date'},
{id: "fecha", header: ["Fecha y Hora"], width: 150, sort: 'string'},
{id: "tipo_comprobante", header: ["Tipo", {content: "selectFilter"}],
adjust: 'header', sort: 'string'},
{id: "estatus", header: ["Estatus", {content: "selectFilter"}],
@ -202,7 +202,7 @@ var grid_invoices_cols = [
{id: "uuid", header: ["UUID", {content: "textFilter"}], adjust: "data",
sort:"string", hidden:true},
{id: "fecha", header: ["Fecha y Hora"],
adjust: "data", sort: "date"},
adjust: "data", sort: "string"},
{id: "tipo_comprobante", header: ["Tipo", {content: "selectFilter"}],
adjust: 'header', sort: 'string'},
{id: "estatus", header: ["Estatus", {content: "selectFilter"}],