From 4e6c5ffa2c606d292eec17f37c6edd20f3945b91 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Wed, 15 Nov 2017 19:29:51 -0600 Subject: [PATCH 01/12] Generar archivo para productos --- source/app/controllers/util.py | 64 ++++++++- source/app/models/main.py | 179 +++++++++++++++++++++++- source/app/settings.py | 1 + source/static/js/controller/products.js | 12 +- source/static/js/controller/util.js | 26 ++++ source/static/js/ui/admin.js | 3 +- source/static/js/ui/partners.js | 4 +- source/static/js/ui/products.js | 5 +- 8 files changed, 281 insertions(+), 13 deletions(-) diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index f1fb86d..5ff3ddc 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -36,7 +36,7 @@ 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_XMLSEC, TEMPLATE_CANCEL, IMPUESTOS + PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO #~ def _get_hash(password): @@ -1277,10 +1277,10 @@ class ImportFacturaLibre(object): self._rfc = rfc self._con = None self._cursor = None + self._error = '' self._is_connect = self._connect(path) self._clientes = [] self._clientes_rfc = [] - self._error = '' @property def error(self): @@ -1299,7 +1299,7 @@ class ImportFacturaLibre(object): return False if obj['rfc'] != self._rfc: - self._error = 'Los datos no corresponden al emisor: {}'.format(self._rfc) + self._error = 'Los datos no corresponden al RFC: {}'.format(self._rfc) return False return True @@ -1328,6 +1328,7 @@ class ImportFacturaLibre(object): tables = ( ('receptores', 'Socios'), ('cfdfacturas', 'Facturas'), + ('categorias', 'Categorias'), ) for source, target in tables: data[target] = self._get_table(source) @@ -1339,6 +1340,63 @@ class ImportFacturaLibre(object): def _get_table(self, table): return getattr(self, '_{}'.format(table))() + def import_productos(self): + sql = "SELECT * FROM productos" + self._cursor.execute(sql) + rows = self._cursor.fetchall() + + fields = ( + ('id_categoria', 'categoria'), + ('noIdentificacion', 'clave'), + ('descripcion', 'descripcion'), + ('unidad', 'unidad'), + ('valorUnitario', 'valor_unitario'), + ('existencia', 'existencia'), + ('inventario', 'inventario'), + ('codigobarras', 'codigo_barras'), + ('CuentaPredial', 'cuenta_predial'), + ('precio_compra', 'ultimo_precio'), + ('minimo', 'minimo'), + ) + data = [] + + sql = """ + SELECT nombre, tasa, tipo + FROM impuestos, productos, productosimpuestos + WHERE productos.id=productosimpuestos.id_producto + AND productosimpuestos.id_impuesto=impuestos.id + AND productos.id = ? + """ + for row in rows: + new = {t: row[s] for s, t in fields} + new['descripcion'] = ' '.join(new['descripcion'].split()) + new['clave_sat'] = DEFAULT_SAT_PRODUCTO + self._cursor.execute(sql, (row['id'],)) + impuestos = self._cursor.fetchall() + new['impuestos'] = tuple(impuestos) + data.append(new) + + return data + + def _categorias(self): + sql = "SELECT * FROM categorias" + self._cursor.execute(sql) + rows = self._cursor.fetchall() + + fields = ( + ('categoria', 'categoria'), + ('id_padre', 'padre'), + ) + data = [] + + for row in rows: + new = {t: row[s] for s, t in fields} + if new['padre'] == 0: + new['padre'] = None + data.append(new) + + return data + def _get_cliente(self, invoice): sql = "SELECT rfc, nombre FROM receptores WHERE id=?" self._cursor.execute(sql, [invoice['id_cliente']]) diff --git a/source/app/models/main.py b/source/app/models/main.py index b304403..3c85a35 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -15,7 +15,7 @@ if __name__ == '__main__': from controllers import util from settings import log, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, \ - INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS + INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO FORMAT = '{0:.2f}' @@ -529,6 +529,10 @@ class Categorias(BaseModel): (('categoria', 'padre'), True), ) + @classmethod + def exists(cls, filters): + return Categorias.select().where(filters).exists() + @classmethod def get_all(cls): rows = (Categorias.select( @@ -536,6 +540,9 @@ class Categorias(BaseModel): Categorias.categoria.alias('value'), Categorias.padre.alias('parent_id')) ).dicts() + for row in rows: + if row['parent_id'] is None: + row['parent_id'] = 0 return tuple(rows) @@ -3073,6 +3080,112 @@ def _importar_facturas(rows): return +def _importar_categorias(rows): + log.info('\tImportando Categorías...') + for row in rows: + if row['padre'] is None: + filters = ( + (Categorias.categoria==row['categoria']) & + (Categorias.padre.is_null(True)) + ) + else: + filters = ( + (Categorias.categoria==row['categoria']) & + (Categorias.padre==row['padre']) + ) + + if Categorias.exists(filters): + continue + + try: + Categorias.create(**row) + except IntegrityError: + msg = '\tCategoria: ({}) {}'.format(row['padre'], row['categoria']) + log.error(msg) + + log.info('\tCategorías importadas...') + return + + +def _get_id_unidad(unidad): + obj = SATUnidades.select(SATUnidades.id).where(SATUnidades.name==unidad) + + if obj is None: + msg = 'No se encontró: {}'.format(unidad) + log.error('\t', msg) + return unidad + + return str(obj[0].id) + + +def _get_impuestos(impuestos): + lines = '|' + for impuesto in impuestos: + info = ( + IMPUESTOS.get(impuesto['nombre']), + impuesto['nombre'], + impuesto['tipo'][0], + str(round(float(impuesto['tasa']) / 100.0, 6)), + ) + lines += '|'.join(info) + return lines + + +def _generar_archivo_productos(archivo): + rfc = input('Introduce el RFC: ').strip().upper() + if not rfc: + msg = 'El RFC es requerido' + log.error(msg) + return + + args = util.get_con(rfc) + if not args: + return + + conectar(args) + + log.info('Importando datos...') + app = util.ImportFacturaLibre(archivo, rfc) + if not app.is_connect: + log.error('\t{}'.format(app._error)) + return + + rows = app.import_productos() + + p, _, _, _ = util.get_path_info(archivo) + path_txt = util._join(p, 'productos.txt') + log.info('\tGenerando archivo: {}'.format(path_txt)) + + fields = ( + 'clave', + 'clave_sat', + 'unidad', + 'categoria', + 'descripcion', + 'valor_unitario', + 'existencia', + 'inventario', + 'codigo_barras', + 'cuenta_predial', + 'ultimo_precio', + 'minimo', + ) + + data = ['|'.join(fields)] + for row in rows: + impuestos = row.pop('impuestos', ()) + line = [str(row[r]) for r in fields] + line[2] = _get_id_unidad(line[2]) + line = '|'.join(line) + _get_impuestos(impuestos) + data.append(line) + + with open(path_txt, 'w') as fh: + fh.write('\n'.join(data)) + + log.info('\tArchivo generado: {}'.format(path_txt)) + return + + def _importar_factura_libre(archivo): rfc = input('Introduce el RFC: ').strip().upper() if not rfc: @@ -3089,18 +3202,39 @@ def _importar_factura_libre(archivo): log.info('Importando datos...') app = util.ImportFacturaLibre(archivo, rfc) if not app.is_connect: - log.error('\t{}'.format(app.error)) + log.error('\t{}'.format(app._error)) return data = app.import_data() _importar_socios(data['Socios']) _importar_facturas(data['Facturas']) + _importar_categorias(data['Categorias']) + #~ _generar_archivo_productos(data['Productos'], archivo) log.info('Importación terminada...') return +def _test(): + rfc = input('Introduce el RFC: ').strip().upper() + if not rfc: + msg = 'El RFC es requerido' + log.error(msg) + return + + args = util.get_con(rfc) + if not args: + return + + conectar(args) + + rows = Categorias.select().where( + Categorias.categoria=='Productos', Categorias.padre.is_null(True)).exists() + print (rows) + return + + CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) help_create_tables = 'Crea las tablas en la base de datos' help_migrate_db = 'Migra las tablas en la base de datos' @@ -3125,10 +3259,19 @@ help_lr = 'Listar RFCs' @click.option('-i', '--importar-valores', is_flag=True, default=False) @click.option('-a', '--archivo') @click.option('-fl', '--factura-libre', is_flag=True, default=False) +@click.option('-t', '--test', is_flag=True, default=False) +@click.option('-gap', '--generar-archivo-productos', is_flag=True, default=False) +@click.option('-ip', '--importar-productos', is_flag=True, default=False) def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc, - borrar_rfc, listar_rfc, importar_valores, archivo, factura_libre): + borrar_rfc, listar_rfc, importar_valores, archivo, factura_libre, test, + generar_archivo_productos, importar_productos): + opt = locals() + if opt['test']: + _test() + sys.exit(0) + if opt['iniciar_bd']: _iniciar_bd() sys.exit(0) @@ -3183,6 +3326,36 @@ def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc, _importar_factura_libre(opt['archivo']) sys.exit(0) + if opt['generar_archivo_productos']: + if not opt['archivo']: + msg = 'Falta la ruta de la base de datos' + raise click.ClickException(msg) + if not util.is_file(opt['archivo']): + msg = 'No es un archivo' + raise click.ClickException(msg) + _, _, _, ext = util.get_path_info(opt['archivo']) + if ext != '.sqlite': + msg = 'No es una base de datos' + raise click.ClickException(msg) + + _generar_archivo_productos(opt['archivo']) + sys.exit(0) + + if opt['importar_productos']: + if not opt['archivo']: + msg = 'Falta la ruta del archivo' + raise click.ClickException(msg) + if not util.is_file(opt['archivo']): + msg = 'No es un archivo' + raise click.ClickException(msg) + _, _, _, ext = util.get_path_info(opt['archivo']) + if ext != '.txt': + msg = 'No es un archivo de texto' + raise click.ClickException(msg) + + _importar_productos(opt['archivo']) + sys.exit(0) + return diff --git a/source/app/settings.py b/source/app/settings.py index c670fb1..2a4e9df 100644 --- a/source/app/settings.py +++ b/source/app/settings.py @@ -102,3 +102,4 @@ IMPUESTOS = { 'ICIC': '000', 'CEDULAR': '000', } +DEFAULT_SAT_PRODUCTO = '01010101' diff --git a/source/static/js/controller/products.js b/source/static/js/controller/products.js index 87255ff..da87d4f 100644 --- a/source/static/js/controller/products.js +++ b/source/static/js/controller/products.js @@ -1,13 +1,21 @@ +function get_categorias(){ + webix.ajax().sync().get('/values/categorias', function(text, data){ + var values = data.json() + $$('categoria').getList().parse(values, 'plainjs') + }) +} + + function cmd_new_product_click(id, e, node){ $$('form_product').setValues({ - id: 0, es_activo_producto: true}) + id: 0, es_activo_producto: true}) add_config({'key': 'id_product', 'value': ''}) get_new_key() get_taxes() + get_categorias() $$('grid_products').clearSelection() - $$('categoria').getList().load('/values/categorias') $$('unidad').getList().load('/values/unidades') $$("multi_products").setValue("product_new") } diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index dd26684..dd607ee 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -153,3 +153,29 @@ function get_config(value){ return key.value } } + + +webix.DataDriver.plainjs = webix.extend({ + arr2hash:function(data){ + var hash = {}; + for (var i=0; i Date: Wed, 15 Nov 2017 19:33:41 -0600 Subject: [PATCH 02/12] Agregar scroll a formularios --- source/static/js/ui/invoices.js | 3 ++- source/static/js/ui/products.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index d2c3d82..5da23ca 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -556,6 +556,7 @@ var form_invoice = { view: 'form', id: 'form_invoice', complexData: true, + scroll: true, elements: controls_invoices, }] } @@ -572,7 +573,7 @@ var multi_invoices = { {view: 'toolbar', elements: toolbar_invoices_filter}, grid_invoices, ]}, - {id: 'invoices_new', rows:[form_invoice, {}]} + {id: 'invoices_new', rows:[form_invoice]} ] } diff --git a/source/static/js/ui/products.js b/source/static/js/ui/products.js index bb6c575..6650587 100644 --- a/source/static/js/ui/products.js +++ b/source/static/js/ui/products.js @@ -164,7 +164,7 @@ var form_product = { cols: [{ view: "form", id: "form_product", - //~ width: 600, + scroll: true, complexData: true, elements: controls_products, rules: { @@ -183,7 +183,7 @@ var multi_products = { {view:"toolbar", elements: toolbar_products}, grid_products, ]}, - {id: "product_new", rows:[form_product, {}]} + {id: "product_new", rows:[form_product]} ], } From 960ba21467f80cc1f1227cbbe2f09ff4568d8a47 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Thu, 16 Nov 2017 01:17:22 -0600 Subject: [PATCH 03/12] Paginar clientes --- source/app/controllers/main.py | 1 + source/app/controllers/util.py | 5 +- source/app/models/main.py | 103 +++++++++++++++++++++++- source/static/js/controller/invoices.js | 2 + source/static/js/controller/main.js | 14 ++-- source/static/js/ui/partners.js | 25 +++++- source/static/js/ui/products.js | 4 +- 7 files changed, 137 insertions(+), 17 deletions(-) diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index e3c3924..03b46d8 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -140,6 +140,7 @@ class AppPartners(object): def on_get(self, req, resp): values = req.params + #~ print ('VALUES', values) req.context['result'] = self._db.get_partners(values) resp.status = falcon.HTTP_200 diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 5ff3ddc..fce597b 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -767,9 +767,8 @@ class LIBO(object): #~ Si no se encuentra, copia las celdas hacia abajo de #~ {subtotal.titulo} y {subtotal} - print (data['descuento']) + #~ print (data['descuento']) if 'descuento' in data: - self._copy_cell(cell_title) self._copy_cell(cell_value) cell_title = self._set_cell(v='Descuento', cell=cell_title) @@ -1534,6 +1533,8 @@ class ImportFacturaLibre(object): data = [] for row in rows: new = {t: row[s] for s, t in fields} + if not new['uuid']: + new['uuid'] = None if row['estatus'] == 'Pagada': new['pagada'] = True elif row['estatus'] == 'Cancelada': diff --git a/source/app/models/main.py b/source/app/models/main.py index 3c85a35..0fb1e0b 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -747,6 +747,11 @@ class SATImpuestos(BaseModel): (('key', 'factor', 'tipo', 'tasa'), True), ) + @classmethod + def get_o_crea(self, values): + obj, _ = SATImpuestos.get_or_create(**values) + return obj + @classmethod def add(self, values): tasa = float(values['tasa']) @@ -1083,7 +1088,9 @@ class Socios(BaseModel): @classmethod def get_(cls, values): - if values: + print ('values', values) + id = values.get('id', 0) + if id: id = int(values['id']) row = Socios.select().where(Socios.id==id).dicts()[0] row['uso_cfdi_socio'] = row.pop('uso_cfdi') @@ -1092,6 +1099,18 @@ class Socios(BaseModel): str(CondicionesPago.get(id=row['condicion_pago'])) return row + #~ return {'data': data['rows'][:100], 'pos':0, 'total_count': 1300} + #~ start = 0 + #~ count = 0 + #~ end = 100 + #~ if values: + #~ {'start': '100', 'count': '100', 'continue': 'true'} + #~ start = int(values['start']) + #~ cont = int(values['count']) + #~ end = start + count + + total = Socios.select().count() + rows = (Socios .select( Socios.id, @@ -1100,7 +1119,7 @@ class Socios(BaseModel): Socios.saldo_cliente) .dicts() ) - return {'ok': True, 'rows': tuple(rows)} + return {'pos': 0, 'total_count': total, 'data': tuple(rows)} @classmethod def get_by_client(cls, values): @@ -3074,7 +3093,7 @@ def _importar_facturas(rows): } FacturasImpuestos.create(**new) except IntegrityError: - msg = '\tFactura: id: {}'.format(row['serie'] + row['folio']) + msg = '\tFactura: id: {}'.format(row['serie'] + str(row['folio'])) log.error(msg) log.info('\tFacturas importadas...') return @@ -3210,7 +3229,83 @@ def _importar_factura_libre(archivo): _importar_socios(data['Socios']) _importar_facturas(data['Facturas']) _importar_categorias(data['Categorias']) - #~ _generar_archivo_productos(data['Productos'], archivo) + + log.info('Importación terminada...') + return + + +def _importar_productos(archivo): + rfc = input('Introduce el RFC: ').strip().upper() + if not rfc: + msg = 'El RFC es requerido' + log.error(msg) + return + + args = util.get_con(rfc) + if not args: + return + + conectar(args) + log.info('Importando productos...') + + fields = ( + 'clave', + 'clave_sat', + 'unidad', + 'categoria', + 'descripcion', + 'valor_unitario', + 'existencia', + 'inventario', + 'codigo_barras', + 'cuenta_predial', + 'ultimo_precio', + 'minimo', + ) + + rows = util.read_file(archivo, 'r').split('\n') + for i, row in enumerate(rows): + if i == 0: + continue + data = row.split('|') + + new = {} + for i, f in enumerate(fields): + if not len(data[0]): + continue + + if i in (2, 3): + try: + new[f] = int(data[i]) + except ValueError: + continue + elif i in (5, 6, 10, 11): + new[f] = float(data[i]) + elif i == 7: + new[f] = bool(data[i]) + else: + new[f] = data[i] + + impuestos = data[i + 1:] + if not impuestos: + taxes = [SATImpuestos.select().where(SATImpuestos.id==6)] + else: + taxes = [] + for i in range(0, len(impuestos), 4): + w = { + 'key': impuestos[i], + 'name': impuestos[i+1], + 'tipo': impuestos[i+2], + 'tasa': float(impuestos[i+3]), + } + taxes.append(SATImpuestos.get_o_crea(w)) + + with database_proxy.transaction(): + try: + obj = Productos.create(**new) + obj.impuestos = taxes + except IntegrityError: + pass log.info('Importación terminada...') return diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index d23b1c1..593cab9 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -953,6 +953,8 @@ function get_invoices(rango){ } var grid = $$('grid_invoices') + grid.showProgress({type: 'icon'}) + webix.ajax().get('/invoices', rango, { error: function(text, data, xhr) { webix.message({type: 'error', text: 'Error al consultar'}) diff --git a/source/static/js/controller/main.js b/source/static/js/controller/main.js index ab9fcdb..f7c287e 100644 --- a/source/static/js/controller/main.js +++ b/source/static/js/controller/main.js @@ -63,6 +63,8 @@ var controllers = { $$('cmd_delete_preinvoice').attachEvent('onItemClick', cmd_delete_preinvoice_click) $$('cmd_facturar_preinvoice').attachEvent('onItemClick', cmd_facturar_preinvoice_click) $$('grid_preinvoices').attachEvent('onItemClick', grid_preinvoices_click) + + webix.extend($$('grid_invoices'), webix.ProgressBar) } } @@ -77,18 +79,18 @@ function get_uso_cfdi_to_table(){ function get_partners(){ - webix.ajax().get("/partners", {}, { + webix.ajax().get('/partners', {}, { error: function(text, data, xhr) { - webix.message({ type:"error", text: "Error al consultar"}); + webix.message({type: 'error', text: 'Error al consultar'}); }, success: function(text, data, xhr) { var values = data.json(); - $$("grid_partners").clearAll(); - if (values.ok){ - $$("grid_partners").parse(values.rows, 'json'); + $$('grid_partners').clearAll(); + if (values.data){ + $$('grid_partners').parse(values.data, 'json'); }; } - }); + }) } diff --git a/source/static/js/ui/partners.js b/source/static/js/ui/partners.js index 2ae0df8..3c399c3 100644 --- a/source/static/js/ui/partners.js +++ b/source/static/js/ui/partners.js @@ -11,10 +11,10 @@ var toolbar_partners = [ var grid_partners_cols = [ - {id: 'index', header:'#', adjust:'data', css: 'right', + {id: 'index', header:'#', css: 'right', footer: {content: 'rowCount', colspan: 2, css: 'right'}}, - {id: 'id', header: 'Clave', adjust:'data', sort: 'int', css: 'right'}, - {id: 'rfc', header: ['RFC', {content: 'textFilter'}], adjust:'data', + {id: 'id', header: 'Clave', sort: 'int', css: 'right'}, + {id: 'rfc', header: ['RFC', {content: 'textFilter'}], sort: 'string', footer: {text: 'Clientes y Proveedores', colspan: 2}}, {id: 'nombre', header: ['Razón Social', {content: 'textFilter'}], fillspace:true, sort: 'string'}, @@ -32,6 +32,15 @@ var grid_partners = { resizeColumn: true, headermenu: true, columns: grid_partners_cols, + pager: 'pager_clientes', + //~ datafetch: 100, + //~ loadahead: 100, + //~ url: '/partners', + ready:function(){ + this.adjustColumn('index'); + this.adjustColumn('id'); + this.adjustColumn('rfc'); + }, on:{ 'data->onStoreUpdated':function(){ this.data.each(function(obj, i){ @@ -300,6 +309,15 @@ var form_partner = { } +var pager_clientes = { + view: "pager", + id: "pager_clientes", + template: "{common.prev()} {common.pages()} {common.next()}", + size: 100, + group: 10, +} + + var multi_partners = { id: 'multi_partners', animate: true, @@ -307,6 +325,7 @@ var multi_partners = { {id: 'partners_home', rows:[ {view: 'toolbar', elements: toolbar_partners}, grid_partners, + pager_clientes, ]}, {id: 'partners_new', rows:[form_partner]} ] diff --git a/source/static/js/ui/products.js b/source/static/js/ui/products.js index 6650587..e2bbdaf 100644 --- a/source/static/js/ui/products.js +++ b/source/static/js/ui/products.js @@ -13,9 +13,9 @@ var toolbar_products = [ var grid_products_cols = [ { id: "id", header: "ID", width: 75}, { id: "clave", header: ["Clave", {content: "textFilter"}], width: 100, - sort:"string" }, + sort: 'string', footer: {content: 'rowCount', css: 'right'}}, { id: "descripcion", header: ["Descripción", {content: "textFilter"}], - fillspace:true, sort:"string" }, + fillspace:true, sort: 'string', footer: 'Productos y Servicios'}, { id: "unidad", header: ["Unidad", {content: "selectFilter"}], width: 150, sort:"string" }, { id: "valor_unitario", header: ["Precio", {content: "numberFilter"}], width: 150, From c8f7554ff6e758512b4f54abb43da7562cd9011b Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Thu, 16 Nov 2017 22:49:17 -0600 Subject: [PATCH 04/12] Cuentas Banco UI --- source/app/controllers/main.py | 2 +- source/app/controllers/util.py | 2 + source/app/models/db.py | 3 + source/app/models/main.py | 98 ++++++++++++++++++++++++++- source/static/js/controller/admin.js | 4 +- source/static/js/controller/bancos.js | 41 +++++++++++ source/static/js/controller/main.js | 10 +++ source/static/js/controller/util.js | 17 +++++ source/static/js/ui/bancos.js | 78 +++++++++++++++++++++ source/static/js/ui/invoices.js | 26 ++----- source/static/js/ui/main.js | 16 +++-- source/templates/main.html | 2 + 12 files changed, 266 insertions(+), 33 deletions(-) create mode 100644 source/static/js/controller/bancos.js create mode 100644 source/static/js/ui/bancos.js diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 03b46d8..4cce715 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -258,7 +258,7 @@ class AppCuentasBanco(object): def on_get(self, req, resp): values = req.params session = req.env['beaker.session'] - #~ req.context['result'] = self._db.get_emisor(session['rfc']) + req.context['result'] = self._db.get_cuentasbanco(values) resp.status = falcon.HTTP_200 def on_post(self, req, resp): diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index fce597b..1a1e82d 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -1535,6 +1535,8 @@ class ImportFacturaLibre(object): new = {t: row[s] for s, t in fields} if not new['uuid']: new['uuid'] = None + if new['xml'] is None: + new['xml'] = '' if row['estatus'] == 'Pagada': new['pagada'] = True elif row['estatus'] == 'Cancelada': diff --git a/source/app/models/db.py b/source/app/models/db.py index c0b498b..ce8e278 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -217,6 +217,9 @@ class StorageEngine(object): def cuentasbanco(self, values): return main.CuentasBanco.add(values) + def get_cuentasbanco(self, values): + return main.CuentasBanco.get_(values) + def get_folios(self): return main.Folios.get_() diff --git a/source/app/models/main.py b/source/app/models/main.py index 0fb1e0b..dd78974 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -914,6 +914,21 @@ class CuentasBanco(BaseModel): def __str__(self): return '{} ({})'.format(self.banco.name, self.cuenta[-4:]) + @classmethod + def get_(cls, values): + if values['tipo'] == '1': + rows = (CuentasBanco + .select( + CuentasBanco.id, + str(CuentasBanco)) + .where(CuentasBanco.de_emisor==True) + .dicts() + ) + print (tuple(rows)) + return {'ok': True, 'rows': tuple(rows)} + + return + @classmethod def emisor(cls): rows = (CuentasBanco @@ -968,6 +983,52 @@ class CuentasBanco(BaseModel): return data +class BancoMovimientos(BaseModel): + cuenta = ForeignKeyField(CuentasBanco) + fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) + movimiento = IntegerField(default=0) + descripcion = TextField(default='') + forma_pago = ForeignKeyField(SATFormaPago) + retiro = DecimalField(default=0.0, max_digits=20, decimal_places=6, + auto_round=True) + deposito = DecimalField(default=0.0, max_digits=20, decimal_places=6, + auto_round=True) + saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, + auto_round=True) + moneda = TextField(default='MXN') # Complemento de pagos + tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6, + auto_round=True) + numero_operacion = TextField(default='') + origen_rfc = TextField(default='') + origen_nombre = TextField(default='') + origen_cuenta = TextField(default='') + destino_rfc = TextField(default='') + destino_cuenta = TextField(default='') + tipo_cadena_pago = TextField(default='') + certificado_pago = TextField(default='') + cadena_pago = TextField(default='') + sello_pago = TextField(default='') + + class Meta: + order_by = ('fecha',) + indexes = ( + (('cuenta', 'movimiento'), True), + ) + + +class CfdiPagos(BaseModel): + movimiento = ForeignKeyField(BancoMovimientos) + xml = TextField(default='') + uuid = UUIDField(null=True) + estatus = TextField(default='Guardado') + estatus_sat = TextField(default='') + notas = TextField(default='') + cancelado = BooleanField(default=False) + + class Meta: + order_by = ('movimiento',) + + class SATUsoCfdi(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) @@ -2523,6 +2584,17 @@ class FacturasRelacionadas(BaseModel): return [str(r.factura_origen.uuid) for r in query] +class CfdiPagosFacturas(BaseModel): + pago = ForeignKeyField(CfdiPagos) + factura = ForeignKeyField(Facturas) + + class Meta: + order_by = ('pago',) + indexes = ( + (('pago', 'factura'), True), + ) + + class PreFacturasRelacionadas(BaseModel): factura = ForeignKeyField(PreFacturas, related_name='original') factura_origen = ForeignKeyField(PreFacturas, related_name='relacion') @@ -2665,6 +2737,23 @@ class FacturasImpuestos(BaseModel): ) +class FacturasPagos(BaseModel): + 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 = ('factura',) + indexes = ( + (('factura', 'numero'), True), + ) + + class PreFacturasImpuestos(BaseModel): factura = ForeignKeyField(PreFacturas) impuesto = ForeignKeyField(SATImpuestos) @@ -2815,13 +2904,15 @@ def _init_values(rfc): def _crear_tablas(rfc): tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion, - Emisor, Facturas, FacturasDetalle, FacturasImpuestos, Folios, + Folios, + Emisor, Facturas, FacturasDetalle, FacturasImpuestos, FacturasPagos, FacturasRelacionadas, Productos, PreFacturas, PreFacturasDetalle, PreFacturasImpuestos, PreFacturasRelacionadas, SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes, SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos, - Socios, Tags, Usuarios, CuentasBanco, TipoCambio, + Socios, Tags, Usuarios, CuentasBanco, TipoCambio, BancoMovimientos, + CfdiPagos, CfdiPagosFacturas, Emisor.regimenes.get_through_model(), Socios.tags.get_through_model(), Productos.impuestos.get_through_model(), @@ -3092,7 +3183,8 @@ def _importar_facturas(rows): 'importe': impuesto['importe'], } FacturasImpuestos.create(**new) - except IntegrityError: + except IntegrityError as e: + print (e) msg = '\tFactura: id: {}'.format(row['serie'] + str(row['folio'])) log.error(msg) log.info('\tFacturas importadas...') diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index dfabde5..9bb5cbd 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -184,7 +184,7 @@ function get_certificado(){ } -function get_cuentas_banco(){ +function get_admin_cuentas_banco(){ webix.ajax().get('/values/monedasid', function(text, data){ var values = data.json() @@ -310,7 +310,7 @@ function multi_admin_change(prevID, nextID){ $$('tab_emisor').setValue('Datos Fiscales') get_emisor() get_certificado() - get_cuentas_banco() + get_admin_cuentas_banco() return } diff --git a/source/static/js/controller/bancos.js b/source/static/js/controller/bancos.js new file mode 100644 index 0000000..9d5a3f4 --- /dev/null +++ b/source/static/js/controller/bancos.js @@ -0,0 +1,41 @@ +var msg = '' + +var bancos_controllers = { + init: function(){ + $$('lst_cuentas_banco').attachEvent('onChange', lst_cuentas_banco_change) + $$('cmd_agregar_retiro').attachEvent('onItemClick', cmd_agregar_retiro_click) + $$('cmd_agregar_deposito').attachEvent('onItemClick', cmd_agregar_deposito_click) + } +} + + +function get_cuentas_banco(){ + webix.ajax().get('/cuentasbanco', {'tipo': 1}, { + error:function(text, data, XmlHttpRequest){ + msg = 'Ocurrio un error, consulta a soporte técnico' + msg_error(msg) + }, + success:function(text, data, XmlHttpRequest){ + var values = data.json() + if(values.ok){ + $$('lst_cuentas_banco').getList().parse(values.rows) + $$('lst_cuentas_banco').setValue(values.rows[0].id) + } + } + }) +} + + +function lst_cuentas_banco_change(nv, ov){ + show('Cuenta change') +} + + +function cmd_agregar_retiro_click(){ + show('Retiro') +} + + +function cmd_agregar_deposito_click(){ + show('Depósito') +} diff --git a/source/static/js/controller/main.js b/source/static/js/controller/main.js index f7c287e..d44948d 100644 --- a/source/static/js/controller/main.js +++ b/source/static/js/controller/main.js @@ -65,6 +65,8 @@ var controllers = { $$('grid_preinvoices').attachEvent('onItemClick', grid_preinvoices_click) webix.extend($$('grid_invoices'), webix.ProgressBar) + + bancos_controllers.init() } } @@ -166,6 +168,14 @@ function multi_change(prevID, nextID){ return } + if(nextID == 'app_bancos'){ + active = $$('multi_bancos').getActiveId() + if(active == 'bancos_home'){ + get_cuentas_banco() + } + return + } + if(nextID == 'app_invoices'){ active = $$('multi_invoices').getActiveId() if(active == 'invoices_home'){ diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index dd607ee..ac5ca32 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -15,6 +15,23 @@ var table_usocfdi = db.addCollection('usocfdi') var table_relaciones = db.addCollection('relaciones') +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'}, +] + + function show(values){ webix.message(JSON.stringify(values, null, 2)) } diff --git a/source/static/js/ui/bancos.js b/source/static/js/ui/bancos.js new file mode 100644 index 0000000..a17157a --- /dev/null +++ b/source/static/js/ui/bancos.js @@ -0,0 +1,78 @@ + + +var toolbar_banco = [ + {view: 'richselect', id: 'lst_cuentas_banco', label: 'Cuenta', + labelWidth: 100, options: []}, + {view: 'text', id: 'txt_cuenta_moneda', label: 'Moneda', readonly: true}, + {view: 'currency', id: 'txt_cuenta_saldo', label: 'Saldo', readonly: true, + inputAlign: 'right', value: 0} +] + + +var toolbar_filtro_cuenta = [ + {view: 'richselect', id: 'filtro_cuenta_year', label: 'Año', + labelAlign: 'right', labelWidth: 50, width: 150, options: []}, + {view: 'richselect', id: 'filtro_cuenta_mes', label: 'Mes', + labelAlign: 'right', labelWidth: 50, width: 200, options: months}, + {view: 'daterangepicker', id: 'filtro_cuenta_fechas', label: 'Fechas', + labelAlign: 'right', width: 300}, + {}, + {view: 'button', id: 'cmd_agregar_retiro', label: 'Retiro', + type: 'iconButton', autowidth: true, icon: 'minus'}, + {view: 'button', id: 'cmd_agregar_deposito', label: 'Depósito', + type: 'iconButton', autowidth: true, icon: 'plus'}, +] + + +var grid_cuentabanco_cols = [ + {id: 'id', header:'ID', hidden: true}, + {id: 'fecha', header: 'Fecha', width: 150}, + {id: 'numero_operacion', header: 'Referencia'}, + {id: 'descripcion', header: ['Descripción', {content: 'textFilter'}], + fillspace:true}, + {id: 'retiro', header: ['Retiro', {content: 'numberFilter'}], + width: 125, format: webix.i18n.priceFormat, css: 'right'}, + {id: 'deposito', header: ['Depósito', {content: 'numberFilter'}], + width: 125, format: webix.i18n.priceFormat, css: 'right'}, + {id: 'saldo', header: ['Saldo'], + width: 125, format: webix.i18n.priceFormat, css: 'right'}, +] + + +var grid_cuentabanco = { + view: 'datatable', + id: 'grid_cuentabanco', + select: 'row', + adjust: true, + footer: true, + resizeColumn: true, + headermenu: true, + columns: grid_cuentabanco_cols, +} + + +var multi_bancos = { + id: 'multi_bancos', + animate: true, + cells:[ + {id: 'bancos_home', rows:[ + {view: 'toolbar', elements: toolbar_banco}, + {view: 'toolbar', elements: toolbar_filtro_cuenta}, + grid_cuentabanco, + ]} + //~ {id: 'partners_new', rows:[form_partner]} + ], +} + + +var title_partners = 'Administración de Bancos' +var app_bancos = { + id: 'app_bancos', + rows:[ + {view: 'template', id: 'th_bancos', type: 'header', + template: title_partners}, + multi_bancos + ] +} + + diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index 5da23ca..6fc27f3 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -1,20 +1,4 @@ -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 grid_cfdi_cliente_cols = [ {id: 'index', header: '#', adjust: 'data', css: 'right', @@ -253,15 +237,15 @@ var grid_details_cols = [ fillspace: true, editor: 'text'}, {id: "unidad", header:{text: 'Unidad', css: 'center'}, width: 100}, {id: 'cantidad', header: {text: 'Cantidad', css: 'center'}, width: 100, - format: webix.i18n.numberFormat, css:'right', editor: 'text'}, + format: webix.i18n.numberFormat, css: 'right', editor: 'text'}, {id: "valor_unitario", header:{text: 'Valor Unitario', css: 'center'}, - width: 100, format: webix.i18n.priceFormat, css:'right', editor: 'text'}, + width: 100, format: webix.i18n.priceFormat, css: 'right', editor: 'text'}, {id: 'descuento', header:{text: 'Descuento', css: 'center'}, - width: 80, format: webix.i18n.priceFormat, css:'right', editor: 'text'}, + width: 80, format: webix.i18n.priceFormat, css: 'right', editor: 'text'}, {id: 'precio_final', hidden: true, header: 'precio_final', width: 80, - format: webix.i18n.priceFormat, css:'right'}, + format: webix.i18n.priceFormat, css: 'right'}, {id: "importe", header:{text: 'Importe', css: 'center'}, width: 150, - format: webix.i18n.priceFormat, css:'right'}, + format: webix.i18n.priceFormat, css: 'right'}, ] diff --git a/source/static/js/ui/main.js b/source/static/js/ui/main.js index 9ba1099..2e61ebb 100644 --- a/source/static/js/ui/main.js +++ b/source/static/js/ui/main.js @@ -1,10 +1,12 @@ + var menu_data = [ {id: 'app_home', icon: 'dashboard', value: 'Inicio'}, {id: 'app_partners', icon: 'users', value: 'Clientes y Proveedores'}, {id: 'app_products', icon: 'server', value: 'Productos y Servicios'}, + {id: 'app_bancos', icon: 'university', value: 'Bancos'}, {id: 'app_invoices', icon: 'cart-plus', value: 'Facturas'}, -]; +] var sidebar = { @@ -18,8 +20,8 @@ var sidebar = { onAfterSelect: function(id){ $$('multi').setValue(id) } - } -}; + }, +} var multi_main = { @@ -33,9 +35,10 @@ var multi_main = { }, app_partners, app_products, + app_bancos, app_invoices, - ] -}; + ], +} var menu_user = { @@ -49,7 +52,8 @@ var menu_user = { type: { subsign: true, }, -}; +} + var ui_main = { rows: [ diff --git a/source/templates/main.html b/source/templates/main.html index 2312e84..8c95768 100644 --- a/source/templates/main.html +++ b/source/templates/main.html @@ -7,11 +7,13 @@ + + From 3cf46e69ae67e4286696580ae53ca7f8a7e555d8 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 17 Nov 2017 00:38:06 -0600 Subject: [PATCH 05/12] Mostrar estado de cuenta --- source/app/controllers/main.py | 17 ++++++ source/app/main.py | 4 +- source/app/models/db.py | 6 +++ source/app/models/main.py | 78 +++++++++++++++++++++++---- source/static/js/controller/bancos.js | 69 +++++++++++++++++++++++- source/static/js/ui/admin.js | 15 +++--- 6 files changed, 170 insertions(+), 19 deletions(-) diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 4cce715..0143285 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -267,6 +267,23 @@ class AppCuentasBanco(object): resp.status = falcon.HTTP_200 +class AppMovimientosBanco(object): + + def __init__(self, db): + self._db = db + + def on_get(self, req, resp): + values = req.params + session = req.env['beaker.session'] + req.context['result'] = self._db.get_movimientosbanco(values) + resp.status = falcon.HTTP_200 + + def on_post(self, req, resp): + values = req.params + req.context['result'] = self._db.cuentasbanco(values) + resp.status = falcon.HTTP_200 + + class AppFolios(object): def __init__(self, db): diff --git a/source/app/main.py b/source/app/main.py index ed80b25..2db8add 100644 --- a/source/app/main.py +++ b/source/app/main.py @@ -15,7 +15,8 @@ from models.db import StorageEngine from controllers.main import ( AppLogin, AppLogout, AppAdmin, AppEmisor, AppConfig, AppMain, AppValues, AppPartners, AppProducts, AppInvoices, AppFolios, - AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco + AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco, + AppMovimientosBanco ) from settings import DEBUG @@ -46,6 +47,7 @@ api.add_route('/products', AppProducts(db)) api.add_route('/invoices', AppInvoices(db)) api.add_route('/preinvoices', AppPreInvoices(db)) api.add_route('/cuentasbanco', AppCuentasBanco(db)) +api.add_route('/movbanco', AppMovimientosBanco(db)) if DEBUG: diff --git a/source/app/models/db.py b/source/app/models/db.py index ce8e278..ffcdac1 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -58,6 +58,9 @@ class StorageEngine(object): years2 = main.PreFacturas.filter_years() return [years1, years2] + def _get_cuentayears(self, values): + return main.CuentasBanco.get_years() + def _get_cert(self, values): return main.Certificado.get_data() @@ -242,3 +245,6 @@ class StorageEngine(object): return data, file_name, content_type + def get_movimientosbanco(self, values): + return main.MovimientosBanco.get_(values) + diff --git a/source/app/models/main.py b/source/app/models/main.py index dd78974..dbafd61 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -914,18 +914,40 @@ class CuentasBanco(BaseModel): def __str__(self): return '{} ({})'.format(self.banco.name, self.cuenta[-4:]) + @classmethod + def get_years(cls): + data = [{'id': -1, 'value': 'Todos'}] + year1 = (CuentasBanco + .select(fn.Min(CuentasBanco.fecha_apertura.year)) + .where(CuentasBanco.de_emisor==True, CuentasBanco.activa==True) + .group_by(CuentasBanco.fecha_apertura.year) + .order_by(CuentasBanco.fecha_apertura.year) + .scalar() + ) + + if year1: + year2 = util.now().year + 1 + data += [{'id': y, 'value': y} for y in range(int(year1), year2)] + + return data + @classmethod def get_(cls, values): if values['tipo'] == '1': rows = (CuentasBanco - .select( - CuentasBanco.id, - str(CuentasBanco)) - .where(CuentasBanco.de_emisor==True) - .dicts() + .select() + .where(CuentasBanco.de_emisor==True, CuentasBanco.activa==True) ) - print (tuple(rows)) - return {'ok': True, 'rows': tuple(rows)} + first = rows[0] + rows = [{'id': r.id, 'value': '{} ({})'.format( + r.banco.name, r.cuenta[-4:])} for r in rows] + data = { + 'ok': True, + 'rows': tuple(rows), + 'moneda': first.moneda.name, + 'saldo': first.saldo, + } + return data return @@ -983,7 +1005,7 @@ class CuentasBanco(BaseModel): return data -class BancoMovimientos(BaseModel): +class MovimientosBanco(BaseModel): cuenta = ForeignKeyField(CuentasBanco) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) movimiento = IntegerField(default=0) @@ -1015,9 +1037,45 @@ class BancoMovimientos(BaseModel): (('cuenta', 'movimiento'), True), ) + @classmethod + def get_(cls, values): + cuenta = int(values['cuenta']) + if 'fechas' in values: + rango = values['fechas'] + fd = (MovimientosBanco.fecha.between( + util.get_date(rango['start']), + util.get_date(rango['end'], True))) + filtros = (fd & MovimientosBanco.cuenta.id==cuenta) + else: + year = int(values['year']) + mes = int(values['mes']) + if year == -1: + fy = (MovimientosBanco.fecha.year > 0) + else: + fy = (MovimientosBanco.fecha.year == year) + if mes == -1: + fm = (MovimientosBanco.fecha.month > 0) + else: + fm = (MovimientosBanco.fecha.month == mes) + filtros = (fy & fm & MovimientosBanco.cuenta.id==cuenta) + + rows = tuple(MovimientosBanco + .select( + MovimientosBanco.id, + MovimientosBanco.fecha, + MovimientosBanco.numero_operacion, + MovimientosBanco.descripcion, + MovimientosBanco.retiro, + MovimientosBanco.deposito, + MovimientosBanco.saldo) + .where(filtros) + .dicts() + ) + return {'ok': True, 'rows': rows} + class CfdiPagos(BaseModel): - movimiento = ForeignKeyField(BancoMovimientos) + movimiento = ForeignKeyField(MovimientosBanco) xml = TextField(default='') uuid = UUIDField(null=True) estatus = TextField(default='Guardado') @@ -2911,7 +2969,7 @@ def _crear_tablas(rfc): PreFacturasRelacionadas, SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes, SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos, - Socios, Tags, Usuarios, CuentasBanco, TipoCambio, BancoMovimientos, + Socios, Tags, Usuarios, CuentasBanco, TipoCambio, MovimientosBanco, CfdiPagos, CfdiPagosFacturas, Emisor.regimenes.get_through_model(), Socios.tags.get_through_model(), diff --git a/source/static/js/controller/bancos.js b/source/static/js/controller/bancos.js index 9d5a3f4..5647674 100644 --- a/source/static/js/controller/bancos.js +++ b/source/static/js/controller/bancos.js @@ -5,11 +5,39 @@ var bancos_controllers = { $$('lst_cuentas_banco').attachEvent('onChange', lst_cuentas_banco_change) $$('cmd_agregar_retiro').attachEvent('onItemClick', cmd_agregar_retiro_click) $$('cmd_agregar_deposito').attachEvent('onItemClick', cmd_agregar_deposito_click) + set_year_month() } } +function set_year_month(){ + var d = new Date() + var y = $$('filtro_cuenta_year') + var m = $$('filtro_cuenta_mes') + + webix.ajax().get('/values/cuentayears', { + error:function(text, data, XmlHttpRequest){ + msg = 'Ocurrio un error, consulta a soporte técnico' + msg_error(msg) + }, + success:function(text, data, XmlHttpRequest){ + var values = data.json() + y.getList().parse(values) + y.blockEvent() + m.blockEvent() + y.setValue(d.getFullYear()) + m.setValue(d.getMonth() + 1) + y.unblockEvent() + m.unblockEvent() + } + }) + +} + + function get_cuentas_banco(){ + var list = $$('lst_cuentas_banco') + webix.ajax().get('/cuentasbanco', {'tipo': 1}, { error:function(text, data, XmlHttpRequest){ msg = 'Ocurrio un error, consulta a soporte técnico' @@ -18,8 +46,45 @@ function get_cuentas_banco(){ success:function(text, data, XmlHttpRequest){ var values = data.json() if(values.ok){ - $$('lst_cuentas_banco').getList().parse(values.rows) - $$('lst_cuentas_banco').setValue(values.rows[0].id) + list.getList().parse(values.rows) + list.blockEvent() + list.setValue(values.rows[0].id) + list.unblockEvent() + $$('txt_cuenta_moneda').setValue(values.moneda) + $$('txt_cuenta_saldo').setValue(values.saldo) + get_estado_cuenta() + } + } + }) +} + + +function get_estado_cuenta(rango){ + if(rango == undefined){ + var filtro = { + cuenta: $$('lst_cuentas_banco').getValue(), + year: $$('filtro_cuenta_year').getValue(), + mes: $$('filtro_cuenta_mes').getValue(), + } + }else{ + var filtro = { + cuenta: $$('lst_cuentas_banco').getValue(), + fechas: rango, + } + } + + var grid = $$('grid_cuentabanco') + + webix.ajax().get('/movbanco', filtro, { + error:function(text, data, XmlHttpRequest){ + msg = 'Ocurrio un error, consulta a soporte técnico' + msg_error(msg) + }, + success:function(text, data, XmlHttpRequest){ + var values = data.json() + grid.clearAll() + if (values.ok){ + grid.parse(values.rows, 'json') } } }) diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index be52df8..02f78de 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -164,13 +164,9 @@ var emisor_cuentas_banco = [ {view: 'form', id: 'form_emisor_cuenta_banco', rows: [ {cols: [ {view: 'text', id: 'emisor_cuenta_nombre', name: 'emisor_cuenta_nombre', - label: 'Nombre: ', required: true}, {}]}, - {cols: [ + label: 'Nombre: ', required: true}, {view: 'richselect', id: 'lst_emisor_banco', name: 'emisor_banco', label: 'Banco: ', required: true, options: []}, - {view: 'datepicker', id: 'emisor_cuenta_fecha', format: '%d-%M-%Y', - name: 'emisor_cuenta_fecha', label: 'Fecha de apertura: ', - required: true}, ]}, {cols: [ {view: 'text', id: 'emisor_cuenta', name: 'emisor_cuenta', @@ -187,10 +183,17 @@ var emisor_cuentas_banco = [ required: true, invalidMessage: 'Captura un valor númerico', inputAlign: 'right', value: ''}, ]}, + {cols: [ + {view: 'datepicker', id: 'emisor_cuenta_fecha', format: '%d-%M-%Y', + name: 'emisor_cuenta_fecha', label: 'Fecha de apertura: ', + required: true}, + {view: 'datepicker', id: 'emisor_fecha_saldo', format: '%d-%M-%Y', + name: 'emisor_fecha_saldo', label: 'Fecha este depósito: ', + required: true}, + ]}, {minHeight: 10}, {cols: [{}, {view: 'button', id: 'cmd_emisor_agregar_cuenta', label: 'Agregar cuenta'}, {}]}, - ], rules: { From 6954b74677f748d70e56b0a39ec50e2220ab9ba0 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 17 Nov 2017 14:13:39 -0600 Subject: [PATCH 06/12] Crear primer movimiento --- source/app/controllers/main.py | 7 +++ source/app/models/db.py | 5 ++ source/app/models/main.py | 68 ++++++++++++++++++++++++++-- source/static/js/controller/admin.js | 61 ++++++++++++++++++++++++- source/static/js/ui/admin.js | 12 +++-- 5 files changed, 142 insertions(+), 11 deletions(-) diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 0143285..2afb7a0 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -266,6 +266,13 @@ class AppCuentasBanco(object): req.context['result'] = self._db.cuentasbanco(values) resp.status = falcon.HTTP_200 + def on_delete(self, req, resp): + values = req.params + if self._db.delete('cuentasbanco', values['id']): + resp.status = falcon.HTTP_200 + else: + resp.status = falcon.HTTP_204 + class AppMovimientosBanco(object): diff --git a/source/app/models/db.py b/source/app/models/db.py index ffcdac1..c41252e 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -148,6 +148,9 @@ class StorageEngine(object): def _get_usocfdi(self, values): return main.SATUsoCfdi.get_activos() + def _get_ebancomov(self, values): + return main.MovimientosBanco.con(values['id']) + def delete(self, table, id): if table == 'partner': return main.Socios.remove(id) @@ -161,6 +164,8 @@ class StorageEngine(object): return main.PreFacturas.remove(id) if table == 'satimpuesto': return main.SATImpuestos.remove(id) + if table == 'cuentasbanco': + return main.CuentasBanco.remove(id) return False def _get_client(self, values): diff --git a/source/app/models/main.py b/source/app/models/main.py index dbafd61..c415334 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -642,6 +642,10 @@ class SATFormaPago(BaseModel): def __str__(self): return 'Forma de pago: ({}) {}'.format(self.key, self.name) + @classmethod + def get_by_key(cls, key): + return SATFormaPago.get(SATFormaPago.key==key) + @classmethod def get_activos(cls, values): field = SATFormaPago.id @@ -914,6 +918,18 @@ class CuentasBanco(BaseModel): def __str__(self): return '{} ({})'.format(self.banco.name, self.cuenta[-4:]) + @classmethod + def remove(cls, id): + try: + with database_proxy.atomic() as txn: + q = MovimientosBanco.delete().where(MovimientosBanco.cuenta==id) + q.execute() + q = CuentasBanco.delete().where(CuentasBanco.id==id) + q.execute() + return True + except: + return False + @classmethod def get_years(cls): data = [{'id': -1, 'value': 'Todos'}] @@ -975,15 +991,32 @@ class CuentasBanco(BaseModel): def add(cls, values): w = '37137137137137137' dv = str( - 10 - + (10 - sum([(int(v) * int(values['clabe'][i])) % 10 for i, v in enumerate(w)]) - % 10) + % 10) % 10) if dv != values['clabe'][-1]: msg = 'Digito de control de la CLABE es incorrecto' return {'ok': False, 'msg': msg} + fecha_deposito = values.pop('fecha_deposito', None) + with database_proxy.transaction(): - obj = CuentasBanco.create(**values) + try: + obj = CuentasBanco.create(**values) + except IntegrityError: + msg = 'Esta cuenta ya existe' + return {'ok': False, 'msg': msg} + + nuevo_mov= { + 'cuenta': obj.id, + 'fecha': fecha_deposito, + 'movimiento': 1, + 'descripcion': 'Saldo inicial', + 'forma_pago': SATFormaPago.get_by_key('99'), + 'deposito': values['saldo'], + 'saldo': values['saldo'], + } + MovimientosBanco.add(nuevo_mov) rows = (CuentasBanco .select( @@ -1037,6 +1070,30 @@ class MovimientosBanco(BaseModel): (('cuenta', 'movimiento'), True), ) + @classmethod + def add(cls, values): + with database_proxy.transaction(): + try: + obj = MovimientosBanco.create(**values) + except IntegrityError: + msg = 'Este movimiento ya existe' + return {'ok': False, 'msg': msg} + + return {'ok': True} + + @classmethod + def con(cls, id): + cant = (MovimientosBanco + .select(MovimientosBanco.id) + .where(MovimientosBanco.cuenta==id) + .count() + ) + if cant > 2: + return {'ok': True} + + return {'ok': False} + + @classmethod def get_(cls, values): cuenta = int(values['cuenta']) @@ -1045,7 +1102,7 @@ class MovimientosBanco(BaseModel): fd = (MovimientosBanco.fecha.between( util.get_date(rango['start']), util.get_date(rango['end'], True))) - filtros = (fd & MovimientosBanco.cuenta.id==cuenta) + filtros = (fd & (MovimientosBanco.cuenta==cuenta)) else: year = int(values['year']) mes = int(values['mes']) @@ -1057,7 +1114,7 @@ class MovimientosBanco(BaseModel): fm = (MovimientosBanco.fecha.month > 0) else: fm = (MovimientosBanco.fecha.month == mes) - filtros = (fy & fm & MovimientosBanco.cuenta.id==cuenta) + filtros = (fy & fm & (MovimientosBanco.cuenta==cuenta)) rows = tuple(MovimientosBanco .select( @@ -1071,6 +1128,7 @@ class MovimientosBanco(BaseModel): .where(filtros) .dicts() ) + return {'ok': True, 'rows': rows} diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 9bb5cbd..0b1852b 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -22,6 +22,7 @@ var controllers = { $$('cmd_guardar_correo').attachEvent('onItemClick', cmd_guardar_correo_click) $$('emisor_logo').attachEvent('onItemClick', emisor_logo_click) $$('cmd_emisor_agregar_cuenta').attachEvent('onItemClick', cmd_emisor_agregar_cuenta_click) + $$('cmd_emisor_eliminar_cuenta').attachEvent('onItemClick', cmd_emisor_eliminar_cuenta_click) $$('emisor_cuenta_saldo_inicial').attachEvent('onChange', emisor_cuenta_saldo_inicial_change) //~ SAT tb_sat = $$('tab_sat').getTabbar() @@ -906,17 +907,18 @@ function cmd_emisor_agregar_cuenta_click(){ var values = form.getValues() - var si = parseFloat(values.emisor_cuenta_saldo_inicial.replace('$', '').replace(',', '')) + var saldo_inicial = parseFloat(values.emisor_cuenta_saldo_inicial.replace('$', '').replace(',', '')) var cuenta = { de_emisor: true, activa: true, nombre: values.emisor_cuenta_nombre.trim(), banco: values.emisor_banco, fecha_apertura: values.emisor_cuenta_fecha, + fecha_deposito: values.emisor_fecha_saldo, cuenta: values.emisor_cuenta.trim(), clabe: values.emisor_clabe.trim(), moneda: values.emisor_cuenta_moneda, - saldo_inicial: si + saldo_inicial: saldo_inicial } if(!cuenta.nombre){ @@ -979,6 +981,7 @@ function cmd_emisor_agregar_cuenta_click(){ var values = data.json() if(values.ok){ $$('grid_emisor_cuentas_banco').add(values.row) + form.setValues({}) }else{ msg_error(values.msg) } @@ -1125,3 +1128,57 @@ function grid_admin_taxes_click(id, e, node){ }) } + + +function eliminar_cuenta_banco(id){ + var grid = $$('grid_emisor_cuentas_banco') + + webix.ajax().del('/cuentasbanco', {id: id}, function(text, xml, xhr){ + msg = 'Cuenta eliminada correctamente' + if(xhr.status == 200){ + grid.remove(id) + msg_sucess(msg) + }else{ + msg = 'No se pudo eliminar' + msg_error(msg) + } + }) +} + + +function cmd_emisor_eliminar_cuenta_click(){ + var respuesta = undefined + var row = $$('grid_emisor_cuentas_banco').getSelectedItem() + + if (row == undefined){ + msg = 'Selecciona una cuenta de banco' + msg_error(msg) + return + } + + webix.ajax().sync().get('/values/ebancomov', {id: row['id']}, function(text, data){ + respuesta = data.json() + }) + + if(respuesta.ok){ + msg = 'La cuenta tiene movimientos, no se puede eliminar' + msg_error(msg) + return + } + + var msg = '¿Estás seguro de eliminar la cuenta de banco?

' + msg += row['banco'] + ' (' + row['cuenta'] + ')' + msg += '

ESTA ACCIÓN NO SE PUEDE DESHACER' + webix.confirm({ + title: 'Eliminar Cuenta de Banco', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if (result){ + eliminar_cuenta_banco(row['id']) + } + } + }) +} diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index 02f78de..6a5862d 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -179,7 +179,7 @@ var emisor_cuentas_banco = [ name: 'emisor_cuenta_moneda', label: 'Moneda: ', required: true, options: []}, {view: 'currency', type: 'text', id: 'emisor_cuenta_saldo_inicial', - name: 'emisor_cuenta_saldo_inicial', label: 'Saldo inicial', + name: 'emisor_cuenta_saldo_inicial', label: 'Saldo inicial: ', required: true, invalidMessage: 'Captura un valor númerico', inputAlign: 'right', value: ''}, ]}, @@ -188,12 +188,16 @@ var emisor_cuentas_banco = [ name: 'emisor_cuenta_fecha', label: 'Fecha de apertura: ', required: true}, {view: 'datepicker', id: 'emisor_fecha_saldo', format: '%d-%M-%Y', - name: 'emisor_fecha_saldo', label: 'Fecha este depósito: ', + name: 'emisor_fecha_saldo', label: 'Fecha saldo inicial: ', required: true}, ]}, {minHeight: 10}, - {cols: [{}, {view: 'button', id: 'cmd_emisor_agregar_cuenta', - label: 'Agregar cuenta'}, {}]}, + {cols: [{}, + {view: 'button', id: 'cmd_emisor_agregar_cuenta', + label: 'Agregar cuenta'}, {}, + {view: 'button', id: 'cmd_emisor_eliminar_cuenta', + label: 'Eliminar cuenta'}, + {}]}, ], rules: { From b8ab26174dcef1dcae7bbe405da31072ddd4858f Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 17 Nov 2017 14:56:39 -0600 Subject: [PATCH 07/12] Validar unidad al importar productos --- source/app/models/main.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/source/app/models/main.py b/source/app/models/main.py index c415334..32911d9 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -581,6 +581,9 @@ class SATUnidades(BaseModel): (('key', 'name'), True), ) + def __str__(self): + return '{} ({})'.format(self.name, self.key) + @classmethod def get_(self): rows = SATUnidades.select().dicts() @@ -3335,24 +3338,31 @@ def _importar_categorias(rows): def _get_id_unidad(unidad): - obj = SATUnidades.select(SATUnidades.id).where(SATUnidades.name==unidad) - - if obj is None: - msg = 'No se encontró: {}'.format(unidad) - log.error('\t', msg) + try: + if 'pieza' in unidad.lower(): + unidad = 'pieza' + obj = SATUnidades.get(SATUnidades.name.contains(unidad)) + except SATUnidades.DoesNotExist: + msg = '\tNo se encontró la unidad: {}'.format(unidad) + log.error(msg) return unidad - return str(obj[0].id) + return str(obj.id) def _get_impuestos(impuestos): lines = '|' for impuesto in impuestos: + if impuesto['tasa'] == '-2/3': + tasa = str(round(2/3, 6)) + else: + tasa = str(round(float(impuesto['tasa']) / 100.0, 6)) + info = ( IMPUESTOS.get(impuesto['nombre']), impuesto['nombre'], impuesto['tipo'][0], - str(round(float(impuesto['tasa']) / 100.0, 6)), + tasa, ) lines += '|'.join(info) return lines @@ -3402,6 +3412,8 @@ def _generar_archivo_productos(archivo): for row in rows: impuestos = row.pop('impuestos', ()) line = [str(row[r]) for r in fields] + if line[10] == 'None': + line[10] = '0.0' line[2] = _get_id_unidad(line[2]) line = '|'.join(line) + _get_impuestos(impuestos) data.append(line) From 76300593610cfeecb059cf9c12c5fa82085623bb Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 17 Nov 2017 16:28:11 -0600 Subject: [PATCH 08/12] Validar si existe la factura al importar --- source/app/models/main.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/source/app/models/main.py b/source/app/models/main.py index 32911d9..ffc54d1 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -957,6 +957,9 @@ class CuentasBanco(BaseModel): .select() .where(CuentasBanco.de_emisor==True, CuentasBanco.activa==True) ) + if not (len(rows)): + return {'ok': False} + first = rows[0] rows = [{'id': r.id, 'value': '{} ({})'.format( r.banco.name, r.cuenta[-4:])} for r in rows] @@ -3275,12 +3278,22 @@ def _importar_socios(rows): with database_proxy.atomic() as txn: Socios.create(**row) except IntegrityError: - msg = '\tSocio: id: {}'.format(row['nombre']) - log.error(msg) + msg = '\tSocio existente: {}'.format(row['nombre']) + log.info(msg) log.info('\tClientes importados...') return +def _existe_factura(row): + filtro = (Facturas.uuid==row['uuid']) + if row['uuid'] is None: + filtro = ( + (Facturas.serie==row['serie']) & + (Facturas.folio==row['folio']) + ) + return Facturas.select().where(filtro).exists() + + def _importar_facturas(rows): log.info('\tImportando Facturas...') for row in rows: @@ -3290,6 +3303,11 @@ def _importar_facturas(rows): cliente = row.pop('cliente') row['cliente'] = Socios.get(**cliente) with database_proxy.atomic() as txn: + if _existe_factura(row): + msg = '\tFactura existente: {}{}'.format( + row['serie'], row['folio']) + log.info(msg) + continue obj = Facturas.create(**row) for detalle in detalles: detalle['factura'] = obj @@ -3303,7 +3321,7 @@ def _importar_facturas(rows): } FacturasImpuestos.create(**new) except IntegrityError as e: - print (e) + #~ print (e) msg = '\tFactura: id: {}'.format(row['serie'] + str(row['folio'])) log.error(msg) log.info('\tFacturas importadas...') From 7cc248da1718ef1da1c34536c7f4b4e63b113a10 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 17 Nov 2017 21:12:23 -0600 Subject: [PATCH 09/12] Fix - Agregar retencion de impuesto --- source/app/models/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/app/models/main.py b/source/app/models/main.py index ffc54d1..4280cf1 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -764,7 +764,7 @@ class SATImpuestos(BaseModel): tasa = float(values['tasa']) tipo = 'T' if tasa < 0: - tipo: 'R' + tipo = 'R' row = { 'key': IMPUESTOS.get(values['impuesto']), @@ -772,6 +772,7 @@ class SATImpuestos(BaseModel): 'tipo': tipo, 'tasa': abs(tasa), } + try: obj = SATImpuestos.create(**row) row['id'] = obj.id From bf84b9a49bef0ba4e886483aa122dd7cba3116db Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 17 Nov 2017 21:16:01 -0600 Subject: [PATCH 10/12] Fix - Mostrar activo en impuestos --- source/app/models/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/app/models/main.py b/source/app/models/main.py index 4280cf1..302be69 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -794,7 +794,9 @@ class SATImpuestos(BaseModel): SQL(" '-' AS delete"), SATImpuestos.name, SATImpuestos.tipo, - SATImpuestos.tasa) + SATImpuestos.tasa, + SATImpuestos.activo, + SATImpuestos.default) .dicts() ) return tuple(rows) From eb89b8e8fa3c0958ee8c05e907f3d8db817b75a1 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 17 Nov 2017 21:23:49 -0600 Subject: [PATCH 11/12] Fix - Factura con retenciones --- source/app/models/main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/app/models/main.py b/source/app/models/main.py index 302be69..6b6e160 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +from decimal import Decimal import sqlite3 import click from peewee import * @@ -2046,8 +2047,8 @@ class Facturas(BaseModel): invoice_tax = { 'factura': invoice.id, - 'impuesto': tax['id'], - 'base': tax['importe'], + 'impuesto': tax.id, + 'base': tax.importe, 'importe': import_tax, } FacturasImpuestos.create(**invoice_tax) From ff21b3c8bc2b19b21f8c3882880354d6f67b0e22 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 17 Nov 2017 21:45:47 -0600 Subject: [PATCH 12/12] Fix - Al borrar impuestos --- source/app/models/main.py | 7 +++++-- source/static/js/controller/admin.js | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/source/app/models/main.py b/source/app/models/main.py index 6b6e160..4c3c0f2 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -785,8 +785,11 @@ class SATImpuestos(BaseModel): @classmethod def remove(cls, id): with database_proxy.transaction(): - q = SATImpuestos.delete().where(SATImpuestos.id==id) - return bool(q.execute()) + try: + q = SATImpuestos.delete().where(SATImpuestos.id==id) + return bool(q.execute()) + except IntegrityError: + return False @classmethod def get_(self): diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 0b1852b..17377c0 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -1099,10 +1099,13 @@ function borrar_impuesto(row){ var grid = $$('grid_admin_taxes') webix.ajax().del('/values/satimpuesto', {id: row}, function(text, xml, xhr){ - var msg = 'Impuesto eliminado correctamente' + msg = 'Impuesto eliminado correctamente' if(xhr.status == 200){ grid.remove(row) msg_sucess(msg) + }else{ + msg = 'Impuesto en uso, no se pudo eliminar.' + msg_sucess(msg) } }) }