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 #~ 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 # ~ Establece los valores para sincronizar los backups de la base de datos
# ~ por ejemplo # ~ por ejemplo
@ -21,5 +21,4 @@ LOG_PATH = '/srv/empresa/logs/empresalibre.log'
# ~ 'REPO': 'id_repo', # ~ 'REPO': 'id_repo',
# ~ } # ~ }
SEAFILE_SERVER = {} SEAFILE_SERVER = {}

View File

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

View File

@ -19,6 +19,7 @@ import uuid
import zipfile import zipfile
from io import BytesIO from io import BytesIO
from pathlib import Path
from smtplib import SMTPException, SMTPAuthenticationError from smtplib import SMTPException, SMTPAuthenticationError
from xml.etree import ElementTree as ET from xml.etree import ElementTree as ET
@ -26,6 +27,7 @@ try:
import uno import uno
from com.sun.star.beans import PropertyValue from com.sun.star.beans import PropertyValue
from com.sun.star.awt import Size from com.sun.star.awt import Size
from com.sun.star.view.PaperFormat import LETTER
APP_LIBO = True APP_LIBO = True
except: except:
APP_LIBO = False APP_LIBO = False
@ -35,9 +37,9 @@ from dateutil import parser
from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice, \ from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice, \
SeaFileAPI 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_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 from settings import SEAFILE_SERVER
@ -843,6 +845,7 @@ class LIBO(object):
if self._template is None: if self._template is None:
return b'' return b''
self._template.setPrinter(self._set_properties({'PaperFormat': LETTER}))
self._render(data) self._render(data)
path = '{}.ods'.format(tempfile.mkstemp()[1]) path = '{}.ods'.format(tempfile.mkstemp()[1])
@ -1459,16 +1462,66 @@ def backup_dbs():
return 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): def sync_cfdi(auth, files):
local_copy(files)
if DEBUG: if DEBUG:
return return
if not auth['REPO']:
return
seafile = SeaFileAPI(SEAFILE_SERVER['URL'], auth['USER'], auth['PASS']) seafile = SeaFileAPI(SEAFILE_SERVER['URL'], auth['USER'], auth['PASS'])
if seafile.is_connect: if seafile.is_connect:
for f in files: for f in files:
seafile.update_file( seafile.update_file(
f, auth['REPO'], 'Facturas/{}/'.format(f[2]), auth['PASS']) f, auth['REPO'], 'Facturas/{}/'.format(f[2]), auth['PASS'])
return return
@ -1671,7 +1724,7 @@ class ImportFacturaLibre(object):
data = [] data = []
for row in rows: 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) data.append(new)
return data return data

View File

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

View File

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

View File

@ -1,8 +1,13 @@
[uwsgi] [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 wsgi-file = main.py
callable = app callable = app
master = true master = true
processes = 4 processes = 4
threads = 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',) 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): class Usuarios(BaseModel):
usuario = TextField(unique=True) usuario = TextField(unique=True)
nombre = TextField(default='') nombre = TextField(default='')
@ -1074,6 +1154,45 @@ class SATBancos(BaseModel):
return {'ok': result} 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): class CuentasBanco(BaseModel):
de_emisor = BooleanField(default=False) de_emisor = BooleanField(default=False)
activa = BooleanField(default=True) activa = BooleanField(default=True)
@ -1671,6 +1790,132 @@ class Socios(BaseModel):
return bool(q.execute()) 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): class Almacenes(BaseModel):
nombre = TextField(default='') nombre = TextField(default='')
ubicacion = TextField(default='') ubicacion = TextField(default='')
@ -1882,6 +2127,21 @@ class Productos(BaseModel):
return bool(q.execute()) 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): class Facturas(BaseModel):
cliente = ForeignKeyField(Socios) cliente = ForeignKeyField(Socios)
version = TextField(default=CURRENT_CFDI) version = TextField(default=CURRENT_CFDI)
@ -1967,6 +2227,7 @@ class Facturas(BaseModel):
def get_xml(cls, id): def get_xml(cls, id):
obj = Facturas.get(Facturas.id==id) obj = Facturas.get(Facturas.id==id)
name = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc) name = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc)
cls._sync_xml(cls, obj)
return obj.xml, name return obj.xml, name
#~ Revisar #~ Revisar
@ -2056,8 +2317,12 @@ class Facturas(BaseModel):
return values return values
@classmethod @classmethod
def get_pdf(cls, id, rfc): def get_pdf(cls, id, rfc, sync=True):
try:
emisor = Emisor.select()[0] emisor = Emisor.select()[0]
except IndexError:
return b'', 'sin_datos_de_emisor.pdf'
obj = Facturas.get(Facturas.id==id) obj = Facturas.get(Facturas.id==id)
name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc) name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc)
if obj.uuid is None: if obj.uuid is None:
@ -2066,6 +2331,11 @@ class Facturas(BaseModel):
values = cls._get_not_in_xml(cls, obj, emisor) values = cls._get_not_in_xml(cls, obj, emisor)
data = util.get_data_from_xml(obj, values) data = util.get_data_from_xml(obj, values)
doc = util.to_pdf(data, emisor.rfc) 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 return doc, name
@classmethod @classmethod
@ -2095,6 +2365,27 @@ class Facturas(BaseModel):
def _sync(self, id, auth): def _sync(self, id, auth):
return Facturas.sync(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 @util.run_in_thread
def _actualizar_saldo_cliente(self, invoice): def _actualizar_saldo_cliente(self, invoice):
if invoice.tipo_comprobante == 'T': if invoice.tipo_comprobante == 'T':
@ -2159,17 +2450,15 @@ class Facturas(BaseModel):
@classmethod @classmethod
def sync(cls, id, auth): def sync(cls, id, auth):
if not auth['REPO']:
return
obj = Facturas.get(Facturas.id==id) obj = Facturas.get(Facturas.id==id)
if obj.uuid is None: if obj.uuid is None:
msg = 'La factura no esta timbrada' msg = 'La factura no esta timbrada'
return 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) 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 = ( files = (
(obj.xml, name_xml, target), (obj.xml, name_xml, target),
(pdf, name_pdf, target), (pdf, name_pdf, target),
@ -2343,7 +2632,7 @@ class Facturas(BaseModel):
totals_tax = {} totals_tax = {}
total_trasladados = None total_trasladados = None
total_retenciones = None total_retenciones = None
total_iva = 0 # ~ total_iva = 0
locales_traslados = 0 locales_traslados = 0
locales_retenciones = 0 locales_retenciones = 0
@ -2376,52 +2665,66 @@ class Facturas(BaseModel):
FacturasDetalle.create(**product) FacturasDetalle.create(**product)
base = product['importe'] - product['descuento']
for tax in p.impuestos: 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: 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: else:
tax.importe = importe tax.base = base
tax.suma_impuestos = impuesto_producto
totals_tax[tax.id] = tax totals_tax[tax.id] = tax
for tax in totals_tax.values(): 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 continue
import_tax = round(float(tax.tasa) * tax.importe, DECIMALES) # ~ import_tax = round(float(tax.tasa) * tax.importe, DECIMALES)
if tax.key == '000': # ~ if tax.key == '000':
locales_traslados += import_tax # ~ locales_traslados += import_tax
else: # ~ else:
total_trasladados = (total_trasladados or 0) + import_tax # ~ total_trasladados = (total_trasladados or 0) + import_tax
if tax.name == 'IVA': # ~ if tax.name == 'IVA':
total_iva += import_tax # ~ total_iva += import_tax
invoice_tax = { invoice_tax = {
'factura': invoice.id, 'factura': invoice.id,
'impuesto': tax.id, 'impuesto': tax.id,
'base': tax.importe, 'base': tax.base,
'importe': import_tax, 'importe': tax.suma_impuestos,
} }
FacturasImpuestos.create(**invoice_tax) FacturasImpuestos.create(**invoice_tax)
for tax in totals_tax.values(): # ~ for tax in totals_tax.values():
if tax.tipo == 'E' or tax.tipo == 'T': # ~ if tax.tipo == 'E' or tax.tipo == 'T':
continue # ~ continue
if tax.tasa == round(Decimal(2/3), 6): # ~ if tax.tasa == round(Decimal(2/3), 6):
import_tax = round(float(tax.tasa) * total_iva, DECIMALES) # ~ import_tax = round(float(tax.tasa) * total_iva, DECIMALES)
else: # ~ else:
import_tax = round(float(tax.tasa) * tax.importe, DECIMALES) # ~ import_tax = round(float(tax.tasa) * tax.importe, DECIMALES)
if tax.key == '000': # ~ if tax.key == '000':
locales_retenciones += import_tax # ~ locales_retenciones += import_tax
else: # ~ else:
total_retenciones = (total_retenciones or 0) + import_tax # ~ total_retenciones = (total_retenciones or 0) + import_tax
invoice_tax = { # ~ invoice_tax = {
'factura': invoice.id, # ~ 'factura': invoice.id,
'impuesto': tax.id, # ~ 'impuesto': tax.id,
'base': tax.importe, # ~ 'base': tax.base,
'importe': import_tax, # ~ 'importe': tax.suma_impuestos,
} # ~ }
FacturasImpuestos.create(**invoice_tax) # ~ FacturasImpuestos.create(**invoice_tax)
total = subtotal - descuento_cfdi + \ total = subtotal - descuento_cfdi + \
(total_trasladados or 0) - (total_retenciones or 0) \ (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} 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): class PreFacturasRelacionadas(BaseModel):
factura = ForeignKeyField(PreFacturas, related_name='original') factura = ForeignKeyField(PreFacturas, related_name='original')
factura_origen = ForeignKeyField(PreFacturas, related_name='relacion') factura_origen = ForeignKeyField(PreFacturas, related_name='relacion')
@ -3468,6 +3753,94 @@ class PreFacturasImpuestos(BaseModel):
return data 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): def authenticate(args):
respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''} respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''}
values = util.get_con(args['rfc']) values = util.get_con(args['rfc'])
@ -3583,15 +3956,22 @@ def _init_values(rfc):
def _crear_tablas(rfc): def _crear_tablas(rfc):
tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion, tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion,
Folios, Registro, Folios, Registro, CamposPersonalizados,
Emisor, Facturas, FacturasDetalle, FacturasImpuestos, FacturasPagos, Emisor, Facturas, FacturasDetalle, FacturasImpuestos, FacturasPagos,
FacturasRelacionadas, FacturasComplementos, Almacenes, Productos, FacturasRelacionadas, FacturasComplementos, FacturasPersonalizados,
SeriesProductos, Almacenes, Productos, RangosPrecios,
PreFacturas, PreFacturasDetalle, PreFacturasImpuestos, PreFacturas, PreFacturasDetalle, PreFacturasImpuestos,
PreFacturasRelacionadas, PreFacturasRelacionadas, Tickets, TicketsDetalle, TicketsImpuestos,
SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes, SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes,
SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos, SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos,
Socios, Tags, Usuarios, CuentasBanco, TipoCambio, MovimientosBanco, SATNivelesEducativos,
CfdiPagos, 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(), Emisor.regimenes.get_through_model(),
Socios.tags.get_through_model(), Socios.tags.get_through_model(),
Productos.impuestos.get_through_model(), Productos.impuestos.get_through_model(),

View File

@ -21,7 +21,7 @@ except ImportError:
DEBUG = DEBUG DEBUG = DEBUG
VERSION = '0.2.1' VERSION = '1.2.0'
EMAIL_SUPPORT = ('soporte@empresalibre.net',) EMAIL_SUPPORT = ('soporte@empresalibre.net',)
BASE_DIR = os.path.abspath(os.path.dirname(__file__)) BASE_DIR = os.path.abspath(os.path.dirname(__file__))
@ -115,3 +115,4 @@ IMPUESTOS = {
'CEDULAR': '000', 'CEDULAR': '000',
} }
DEFAULT_SAT_PRODUCTO = '01010101' 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} {"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", "tabla": "SATTipoRelacion",
"datos": [ "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(){ function default_config(){
webix.ajax().sync().get('/values/taxes', function(text, data){ webix.ajax().sync().get('/values/taxes', function(text, data){
var values = data.json() var values = data.json()
@ -96,18 +112,7 @@ function default_config(){
table_pt.clear() table_pt.clear()
table_totals.clear() table_totals.clear()
webix.ajax().sync().get('/values/validartimbrar', function(text, data){ validar_timbrar()
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()
}
})
webix.ajax().sync().get('/values/configtimbrar', function(text, data){ webix.ajax().sync().get('/values/configtimbrar', function(text, data){
var values = data.json() 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){ function set_product(values){
var taxes = values.taxes var taxes = values.taxes
var values = values.row var values = values.row
@ -729,7 +797,8 @@ function set_product(values){
table_pt.insert(v) 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) row['importe'] = (cantidad * precio_final).round(DECIMALES)
grid.refresh() grid.refresh()
calculate_taxes() //~ calculate_taxes()
calcular_impuestos()
} }
@ -855,7 +925,8 @@ function grid_details_click(id, e, node){
return return
} }
grid.remove(id.row) grid.remove(id.row)
calculate_taxes() //~ calculate_taxes()
calcular_impuestos()
} }
@ -874,7 +945,8 @@ function grid_details_header_click(id){
callback:function(result){ callback:function(result){
if (result){ if (result){
grid.clearAll() grid.clearAll()
calculate_taxes() //~ calculate_taxes()
calcular_impuestos()
} }
} }
}) })
@ -1299,7 +1371,8 @@ function refacturar_preinvoice(id){
for(var p of values.rows){ for(var p of values.rows){
agregar_preproducto(p) agregar_preproducto(p)
} }
calculate_taxes() //~ calculate_taxes()
calcular_impuestos()
$$('tv_invoice').getTabbar().setValue('Generar') $$('tv_invoice').getTabbar().setValue('Generar')
} }
}) })

View File

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

View File

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