From 9b430b882b30ad8fd3fae98b326d2a9ebef0e395 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 19 Nov 2017 00:42:16 -0600 Subject: [PATCH 01/13] Impuestos locales --- source/app/controllers/cfdi_xml.py | 48 +++++++++++++++++- source/app/models/main.py | 65 +++++++++++++++++++++---- source/static/js/controller/invoices.js | 10 +++- source/static/js/controller/util.js | 1 + source/static/js/ui/invoices.js | 1 + source/xslt/cadena.xslt | 3 +- source/xslt/implocal.xslt | 39 +++++++++++++++ 7 files changed, 152 insertions(+), 15 deletions(-) create mode 100644 source/xslt/implocal.xslt diff --git a/source/app/controllers/cfdi_xml.py b/source/app/controllers/cfdi_xml.py index dee3bfa..6520c5a 100644 --- a/source/app/controllers/cfdi_xml.py +++ b/source/app/controllers/cfdi_xml.py @@ -39,6 +39,12 @@ SAT = { 'xmlns': 'http://www.sat.gob.mx/nomina12', 'schema': 'http://www.sat.gob.mx/nomina12 http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina12.xsd', }, + 'locales': { + 'version': '1.0', + 'prefix': 'implocal', + 'xmlns': 'http://www.sat.gob.mx/implocal', + 'schema': ' http://www.sat.gob.mx/implocal http://www.sat.gob.mx/sitio_internet/cfd/implocal/implocal.xsd', + }, } @@ -49,6 +55,8 @@ class CFDI(object): self._xsi = SAT['xsi'] self._pre = self._sat_cfdi['prefix'] self._cfdi = None + self._complemento = None + self._impuestos_locales = False self.error = '' def _now(self): @@ -68,6 +76,7 @@ class CFDI(object): self._nomina(datos['nomina']) if 'complementos' in datos: self._complementos(datos['complementos']) + self._locales(datos['impuestos']) return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8')) def add_sello(self, sello): @@ -80,6 +89,10 @@ class CFDI(object): return xml def _validate(self, datos): + if datos['impuestos']['total_locales_trasladados'] or \ + datos['impuestos']['total_locales_retenciones']: + self._impuestos_locales = True + if 'nomina' in datos: return self._validate_nomina(datos) return True @@ -105,7 +118,15 @@ class CFDI(object): attributes = {} attributes['xmlns:{}'.format(self._pre)] = self._sat_cfdi['xmlns'] attributes['xmlns:xsi'] = self._xsi - attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + + shema_locales = '' + if self._impuestos_locales: + name = 'xmlns:{}'.format(SAT['locales']['prefix']) + attributes[name] = SAT['locales']['xmlns'] + shema_locales = SAT['locales']['schema'] + + attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \ + shema_locales attributes.update(datos) if not 'Version' in attributes: @@ -257,8 +278,31 @@ class CFDI(object): ET.SubElement(deducciones, '{}:Deduccion'.format(pre), row) return + def _locales(self, datos): + if not self._impuestos_locales: + return + + if self._complemento is None: + self._complemento = ET.SubElement( + self._cfdi, '{}:Complemento'.format(self._pre)) + + attributes = {} + attributes['version'] = SAT['locales']['version'] + attributes['TotaldeTraslados'] = datos['total_locales_trasladados'] + attributes['TotaldeRetenciones'] = datos['total_locales_retenciones'] + + node = ET.SubElement( + self._complemento, 'implocal:ImpuestosLocales', attributes) + + for retencion in datos['locales_retenciones']: + ET.SubElement(node, 'implocal:RetencionesLocales', retencion) + for traslado in datos['locales_trasladados']: + ET.SubElement(node, 'implocal:TrasladosLocales', traslado) + return + def _complementos(self, datos): - complemento = ET.SubElement(self._cfdi, '{}:Complemento'.format(self._pre)) + self._complemento = ET.SubElement( + self._cfdi, '{}:Complemento'.format(self._pre)) if 'ce' in datos: pre = 'cce11' datos = datos.pop('ce') diff --git a/source/app/models/main.py b/source/app/models/main.py index 4c3c0f2..ef1ac74 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1443,6 +1443,7 @@ class Productos(BaseModel): .select( Productos.id, Productos.clave, + Productos.clave_sat, Productos.descripcion, SATUnidades.name.alias('unidad'), Productos.valor_unitario, @@ -1463,8 +1464,12 @@ class Productos(BaseModel): if name: rows = (Productos .select( - Productos.id, Productos.clave, Productos.descripcion, - SATUnidades.name.alias('unidad'), Productos.valor_unitario) + Productos.id, + Productos.clave, + Productos.clave_sat, + Productos.descripcion, + SATUnidades.name.alias('unidad'), + Productos.valor_unitario) .join(SATUnidades) .switch(Productos) .where(Productos.descripcion.contains(name)) @@ -1984,6 +1989,8 @@ class Facturas(BaseModel): total_trasladados = None total_retenciones = None total_iva = 0 + locales_traslados = 0 + locales_retenciones = 0 for product in products: id_product = product.pop('id') @@ -2015,10 +2022,8 @@ class Facturas(BaseModel): for tax in p.impuestos: if tax.id in totals_tax: - #~ totals_tax[tax.id].importe += product['importe'] totals_tax[tax.id].importe += importe else: - #~ tax.importe = product['importe'] tax.importe = importe totals_tax[tax.id] = tax @@ -2027,7 +2032,10 @@ class Facturas(BaseModel): continue import_tax = round(float(tax.tasa) * tax.importe, DECIMALES) - total_trasladados = (total_trasladados or 0) + import_tax + if tax.key == '000': + locales_traslados += import_tax + else: + total_trasladados = (total_trasladados or 0) + import_tax if tax.name == 'IVA': total_iva += import_tax @@ -2046,7 +2054,10 @@ class Facturas(BaseModel): import_tax = round(float(tax.tasa) * total_iva, DECIMALES) else: import_tax = round(float(tax.tasa) * tax.importe, DECIMALES) - total_retenciones = (total_retenciones or 0) + import_tax + if tax.key == '000': + locales_retenciones += import_tax + else: + total_retenciones = (total_retenciones or 0) + import_tax invoice_tax = { 'factura': invoice.id, @@ -2056,7 +2067,9 @@ class Facturas(BaseModel): } FacturasImpuestos.create(**invoice_tax) - total = subtotal + (total_trasladados or 0) - (total_retenciones or 0) + total = subtotal + \ + (total_trasladados or 0) - (total_retenciones or 0) \ + + locales_traslados - locales_retenciones total_mn = round(total * invoice.tipo_cambio, DECIMALES) data = { 'subtotal': subtotal + descuento, @@ -2163,6 +2176,7 @@ class Facturas(BaseModel): } #~ descuento = 0 + #~ tax_locales = False conceptos = [] rows = FacturasDetalle.select().where(FacturasDetalle.factura==invoice) for row in rows: @@ -2187,6 +2201,11 @@ class Facturas(BaseModel): for impuesto in row.producto.impuestos: if impuesto.tipo == 'E': continue + + if impuesto.key == '000': + #~ tax_locales = True + continue + base = row.importe - row.descuento import_tax = round(impuesto.tasa * base, DECIMALES) tipo_factor = 'Tasa' @@ -2211,12 +2230,13 @@ class Facturas(BaseModel): concepto['impuestos'] = taxes conceptos.append(concepto) - #~ if descuento: - #~ comprobante['Descuento'] = FORMAT.format(descuento) - impuestos = {} traslados = [] retenciones = [] + total_locales_trasladados = 0 + total_locales_retenciones = 0 + locales_trasladados = [] + locales_retenciones = [] if not invoice.total_trasladados is None: impuestos['TotalImpuestosTrasladados'] = \ FORMAT.format(invoice.total_trasladados) @@ -2228,6 +2248,25 @@ class Facturas(BaseModel): .select() .where(FacturasImpuestos.factura==invoice)) for tax in taxes: + if tax.impuesto.key == '000': + if tax.impuesto.tipo == 'T': + traslado = { + 'ImpLocTrasladado': tax.impuesto.name, + 'TasadeTraslado': str(round(tax.impuesto.tasa, 2)), + 'Importe': FORMAT.format(tax.importe), + } + locales_trasladados.append(traslado) + total_locales_trasladados += tax.importe + else: + retencion = { + 'ImpLocRetenido': tax.impuesto.name, + 'TasadeRetencion': str(round(tax.impuesto.tasa, 2)), + 'Importe': FORMAT.format(tax.importe), + } + locales_retenciones.append(retencion) + total_locales_retenciones += tax.importe + continue + tipo_factor = 'Tasa' if tax.impuesto.factor != 'T': tipo_factor = 'Cuota' @@ -2248,6 +2287,12 @@ class Facturas(BaseModel): impuestos['traslados'] = traslados impuestos['retenciones'] = retenciones + impuestos['total_locales_trasladados'] = \ + FORMAT.format(total_locales_trasladados) + impuestos['total_locales_retenciones'] = \ + FORMAT.format(total_locales_retenciones) + impuestos['locales_trasladados'] = locales_trasladados + impuestos['locales_retenciones'] = locales_retenciones data = { 'comprobante': comprobante, diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 593cab9..0bce3da 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -260,6 +260,13 @@ function validate_invoice(values){ return false } + var r = grid.data.getRange() + if(r[0].clave_sat != CLAVE_ANTICIPOS){ + msg = 'La clave del SAT para anticipos debe ser: ' + CLAVE_ANTICIPOS + msg_error(msg) + return false + } + query = table_relaciones.chain().data() if(query.length > 0){ msg = 'Los anticipos no deben llevar CFDI relacionados' @@ -419,6 +426,7 @@ function guardar_y_timbrar(values){ for (i = 0; i < rows.length; i++) { delete rows[i]['delete'] delete rows[i]['clave'] + delete rows[i]['clave_sat'] delete rows[i]['unidad'] delete rows[i]['importe'] rows[i]['valor_unitario'] = parseFloat(rows[i]['valor_unitario']) @@ -639,7 +647,7 @@ function set_product(values){ values['importe'] = (precio_final * values['cantidad']).round(DECIMALES) grid.updateItem(row.id, values) } - form.setValues({search_product_id:'', search_product_name:''}, true) + form.setValues({search_product_id: '', search_product_name: ''}, true) for(var v of taxes){ var pt = table_pt.findOne(v) diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index ac5ca32..3a81c1a 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -3,6 +3,7 @@ var RFC_PUBLICO = "XAXX010101000"; var RFC_EXTRANJERO = "XEXX010101000"; var PAIS = "México"; var DECIMALES = 2; +var CLAVE_ANTICIPOS = '84111506'; var db = new loki('data.db'); diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index 6fc27f3..a688939 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -233,6 +233,7 @@ var grid_details_cols = [ {id: "id", header:"ID", hidden: true}, {id: 'delete', header: '', width: 30, css: 'delete'}, {id: "clave", header:{text: 'Clave', css: 'center'}, width: 100}, + {id: "clave_sat", hidden: true}, {id: "descripcion", header:{text: 'Descripción', css: 'center'}, fillspace: true, editor: 'text'}, {id: "unidad", header:{text: 'Unidad', css: 'center'}, width: 100}, diff --git a/source/xslt/cadena.xslt b/source/xslt/cadena.xslt index 2b81c70..bde9ab9 100644 --- a/source/xslt/cadena.xslt +++ b/source/xslt/cadena.xslt @@ -8,12 +8,11 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 364ac55b8b5e3c4a88c922e7b447dba96008140f Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 19 Nov 2017 14:34:54 -0600 Subject: [PATCH 02/13] Configurar anticipo y donativo --- source/app/models/db.py | 3 +++ source/app/models/main.py | 22 ++++++++++++++++++++++ source/static/js/controller/admin.js | 23 +++++++++++++++++++++++ source/static/js/controller/invoices.js | 13 +++++++++---- source/static/js/controller/util.js | 14 +++++++++++++- source/static/js/ui/admin.js | 12 +++++++++++- source/static/js/ui/invoices.js | 7 +++++-- 7 files changed, 86 insertions(+), 8 deletions(-) diff --git a/source/app/models/db.py b/source/app/models/db.py index c41252e..4d5becd 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -14,6 +14,9 @@ class StorageEngine(object): def get_values(self, table, values=None): return getattr(self, '_get_{}'.format(table))(values) + def _get_configtimbrar(self, values): + return main.config_timbrar() + def _get_validartimbrar(self, values): return main.validar_timbrar() diff --git a/source/app/models/main.py b/source/app/models/main.py index ef1ac74..2c7fb0a 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -102,6 +102,20 @@ def validar_timbrar(): return {'ok': True, 'msg': msg} +def config_timbrar(): + try: + obj = Emisor.select()[0] + except IndexError: + return {'cfdi_donativo': False} + + conf = { + 'cfdi_anticipo': Configuracion.get_('chk_config_anticipo'), + 'cfdi_donativo': obj.es_ong, + } + + return conf + + class Configuracion(BaseModel): clave = TextField(unique=True) valor = TextField(default='') @@ -143,6 +157,13 @@ class Configuracion(BaseModel): .select() .where(Configuracion.clave.in_(fields)) ) + elif keys['fields'] == 'configotros': + fields = ( + 'chk_config_anticipo',) + data = (Configuracion + .select() + .where(Configuracion.clave.in_(fields)) + ) values = {r.clave: r.valor for r in data} return values @@ -151,6 +172,7 @@ class Configuracion(BaseModel): def add(cls, values): try: for k, v in values.items(): + print (k, v) obj, created = Configuracion.get_or_create(clave=k) obj.valor = v obj.save() diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 17377c0..121eaa4 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -39,6 +39,7 @@ var controllers = { tb_options.attachEvent('onChange', tab_options_change) $$('txt_plantilla_factura_32').attachEvent('onItemClick', txt_plantilla_factura_32_click) $$('txt_plantilla_factura_33').attachEvent('onItemClick', txt_plantilla_factura_33_click) + $$('chk_config_anticipo').attachEvent('onItemClick', chk_config_item_click) } } @@ -297,6 +298,7 @@ function get_config_values(opt){ }, success: function(text, data, xhr) { var values = data.json() + showvar(values) Object.keys(values).forEach(function(key){ $$(key).setValue(values[key]) }) @@ -808,6 +810,7 @@ function txt_plantilla_factura_32_click(e){ function tab_options_change(nv, ov){ var cv = { Plantillas: 'templates', + Otros: 'configotros', } get_config_values(cv[nv]) } @@ -1185,3 +1188,23 @@ function cmd_emisor_eliminar_cuenta_click(){ } }) } + + +function chk_config_item_click(id, e){ + var values = {} + values[id] = $$(id).getValue() + + webix.ajax().sync().post('/config', values, { + error: function(text, data, xhr) { + msg = 'Error al guardar la configuración' + msg_error(msg) + }, + success: function(text, data, xhr) { + var values = data.json(); + if (!values.ok){ + msg_error(values.msg) + } + } + }) + +} diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 0bce3da..10ede99 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -56,7 +56,6 @@ function get_monedas(){ $$('lst_moneda').setValue(pre.id) if(values.length == 1){ $$('fs_moneda').hide() - //~ $$('fs_moneda').refresh() } }) } @@ -77,7 +76,6 @@ function get_regimen_fiscal(){ $$('lst_regimen_fiscal').setValue(pre.id) if(values.length == 1){ $$('fs_regimen_fiscal').hide() - //~ $$('fs_regimen_fiscal').refresh() } }) } @@ -109,6 +107,13 @@ function default_config(){ $$('cmd_timbrar').enable() } }) + + webix.ajax().sync().get('/values/configtimbrar', function(text, data){ + var values = data.json() + show('chk_cfdi_anticipo', values.cfdi_anticipo) + show('chk_cfdi_donativo', values.cfdi_donativo) + + }) } @@ -808,7 +813,7 @@ function grid_details_header_click(id){ function cmd_refacturar_click(){ - show('Refacturar') + showvar('Refacturar') } @@ -1014,7 +1019,7 @@ function cmd_invoice_sat_click(){ webix.ajax().get('/values/statussat', {id: row.id}, function(text, data){ var values = data.json() - show(values) + showvar(values) }) } diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index 3a81c1a..c89af4d 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -33,11 +33,23 @@ var months = [ ] -function show(values){ +function showvar(values){ webix.message(JSON.stringify(values, null, 2)) } +function show(nombre, value){ + if(value == '0'){ + value = false + } + if(value){ + $$(nombre).show() + }else{ + $$(nombre).hide() + } +} + + function msg_error(msg){ webix.message({type: 'error', text: msg}) } diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index 6a5862d..d418e5f 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -430,6 +430,16 @@ var options_templates = [ {}] +var options_admin_otros = [ + {maxHeight: 15}, + {cols: [{maxWidth: 15}, + {view: 'checkbox', id: 'chk_config_anticipo', labelWidth: 0, + labelRight: 'Ayuda para generar anticipos'}, + {}]}, + {maxHeight: 20}, +{}] + + var tab_options = { view: 'tabview', id: 'tab_options', @@ -440,7 +450,7 @@ var tab_options = { animate: true, cells: [ {id: 'Plantillas', rows: options_templates}, - {id: 'Otros', rows: [{}]}, + {id: 'Otros', rows: options_admin_otros}, {}, ], } diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index a688939..32fa67e 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -163,8 +163,11 @@ var toolbar_invoices_util = [ var toolbar_invoices_generate = {view: 'toolbar', elements: [{}, {view: 'button', id: 'cmd_cfdi_relacionados', label: 'CFDI Relacionados', type: 'iconButton', autowidth: true, icon: 'file-o'}, - {view: 'checkbox', id: 'chk_cfdi_anticipo', label: 'Es Anticipo', width: 100}, -]} + {view: 'checkbox', id: 'chk_cfdi_anticipo', labelRight: 'Es Anticipo', + labelWidth: 0, width: 100, hidden: true}, + {view: 'checkbox', id: 'chk_cfdi_donativo', labelRight: 'Es Donativo', + labelWidth: 0, width: 100, hidden: true}, +{}]} var toolbar_invoices_filter = [ From 964d53dbae65dafa861d0f78c59354e2f4b6aa63 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 19 Nov 2017 14:57:49 -0600 Subject: [PATCH 03/13] Agregar xslt para donatarias --- source/xslt/cadena.xslt | 2 +- source/xslt/donat11.xslt | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 source/xslt/donat11.xslt diff --git a/source/xslt/cadena.xslt b/source/xslt/cadena.xslt index bde9ab9..42502d3 100644 --- a/source/xslt/cadena.xslt +++ b/source/xslt/cadena.xslt @@ -9,9 +9,9 @@ + + + + + + + + + + From b70fac7872dd6a5bb04e8a41f14a76e65ff0cafb Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 19 Nov 2017 21:26:54 -0600 Subject: [PATCH 04/13] Fix - impuestos locales --- source/app/controllers/cfdi_xml.py | 2 ++ source/app/models/main.py | 26 +++++++++++++------------ source/static/js/controller/admin.js | 2 +- source/static/js/controller/invoices.js | 13 +++++++++++++ 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/source/app/controllers/cfdi_xml.py b/source/app/controllers/cfdi_xml.py index 6520c5a..03c2270 100644 --- a/source/app/controllers/cfdi_xml.py +++ b/source/app/controllers/cfdi_xml.py @@ -89,6 +89,8 @@ class CFDI(object): return xml def _validate(self, datos): + print (datos['impuestos']['total_locales_trasladados']) + print (datos['impuestos']['total_locales_retenciones']) if datos['impuestos']['total_locales_trasladados'] or \ datos['impuestos']['total_locales_retenciones']: self._impuestos_locales = True diff --git a/source/app/models/main.py b/source/app/models/main.py index 2c7fb0a..9aee95e 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -2197,8 +2197,6 @@ class Facturas(BaseModel): 'UsoCFDI': invoice.uso_cfdi, } - #~ descuento = 0 - #~ tax_locales = False conceptos = [] rows = FacturasDetalle.select().where(FacturasDetalle.factura==invoice) for row in rows: @@ -2214,7 +2212,6 @@ class Facturas(BaseModel): } if row.descuento: concepto['Descuento'] = FORMAT.format(row.descuento) - #~ descuento += row.descuento taxes = {} traslados = [] @@ -2225,7 +2222,6 @@ class Facturas(BaseModel): continue if impuesto.key == '000': - #~ tax_locales = True continue base = row.importe - row.descuento @@ -2271,19 +2267,21 @@ class Facturas(BaseModel): .where(FacturasImpuestos.factura==invoice)) for tax in taxes: if tax.impuesto.key == '000': + tasa = str(round(tax.impuesto.tasa * 100, 2)) + simporte = FORMAT.format(tax.importe) if tax.impuesto.tipo == 'T': traslado = { 'ImpLocTrasladado': tax.impuesto.name, - 'TasadeTraslado': str(round(tax.impuesto.tasa, 2)), - 'Importe': FORMAT.format(tax.importe), + 'TasadeTraslado': tasa, + 'Importe': simporte, } locales_trasladados.append(traslado) total_locales_trasladados += tax.importe else: retencion = { 'ImpLocRetenido': tax.impuesto.name, - 'TasadeRetencion': str(round(tax.impuesto.tasa, 2)), - 'Importe': FORMAT.format(tax.importe), + 'TasadeRetencion': tasa, + 'Importe': simporte, } locales_retenciones.append(retencion) total_locales_retenciones += tax.importe @@ -2309,10 +2307,14 @@ class Facturas(BaseModel): impuestos['traslados'] = traslados impuestos['retenciones'] = retenciones - impuestos['total_locales_trasladados'] = \ - FORMAT.format(total_locales_trasladados) - impuestos['total_locales_retenciones'] = \ - FORMAT.format(total_locales_retenciones) + impuestos['total_locales_trasladados'] = '' + if total_locales_trasladados: + impuestos['total_locales_trasladados'] = \ + FORMAT.format(total_locales_trasladados) + impuestos['total_locales_retenciones'] = '' + if total_locales_retenciones: + impuestos['total_locales_retenciones'] = \ + FORMAT.format(total_locales_retenciones) impuestos['locales_trasladados'] = locales_trasladados impuestos['locales_retenciones'] = locales_retenciones diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 121eaa4..cfbdc49 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -298,7 +298,7 @@ function get_config_values(opt){ }, success: function(text, data, xhr) { var values = data.json() - showvar(values) + //~ showvar(values) Object.keys(values).forEach(function(key){ $$(key).setValue(values[key]) }) diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 10ede99..eb742dd 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -280,6 +280,19 @@ function validate_invoice(values){ } } + donativo = $$('chk_cfdi_donativo').getValue() + if(donativo){ + query = table_totals.chain().data() + for(var t of query){ + tax = table_taxes.findOne({'id': t.tax}) + if(tax.tipo != 'E'){ + msg = 'Los donativos deben de ser exentos' + msg_error(msg) + return false + } + } + } + return true } From b8cb214b796051ebc7fa2452829fc6ea7ab27bca Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 19 Nov 2017 21:59:14 -0600 Subject: [PATCH 05/13] Soporte complemento donatarias --- source/app/controllers/cfdi_xml.py | 47 +++++++++++++++++++++---- source/app/models/main.py | 9 +++++ source/static/js/controller/invoices.js | 1 + 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/source/app/controllers/cfdi_xml.py b/source/app/controllers/cfdi_xml.py index 03c2270..d9589b2 100644 --- a/source/app/controllers/cfdi_xml.py +++ b/source/app/controllers/cfdi_xml.py @@ -45,6 +45,13 @@ SAT = { 'xmlns': 'http://www.sat.gob.mx/implocal', 'schema': ' http://www.sat.gob.mx/implocal http://www.sat.gob.mx/sitio_internet/cfd/implocal/implocal.xsd', }, + 'donativo': { + 'version': '1.1', + 'prefix': 'donat', + 'xmlns': 'http://www.sat.gob.mx/donat', + 'schema': ' http://www.sat.gob.mx/donat http://www.sat.gob.mx/sitio_internet/cfd/donat/donat11.xsd', + 'leyenda': 'Este comprobante ampara un donativo, el cual será destinado por la donataria a los fines propios de su objeto social. En el caso de que los bienes donados hayan sido deducidos previamente para los efectos del impuesto sobre la renta, este donativo no es deducible. La reproducción no autorizada de este comprobante constituye un delito en los términos de las disposiciones fiscales.', + }, } @@ -57,6 +64,7 @@ class CFDI(object): self._cfdi = None self._complemento = None self._impuestos_locales = False + self._donativo = False self.error = '' def _now(self): @@ -72,11 +80,14 @@ class CFDI(object): self._receptor(datos['receptor']) self._conceptos(datos['conceptos']) self._impuestos(datos['impuestos']) + self._locales(datos['impuestos']) + self._donatarias(datos['donativo']) + if 'nomina' in datos: self._nomina(datos['nomina']) if 'complementos' in datos: self._complementos(datos['complementos']) - self._locales(datos['impuestos']) + return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8')) def add_sello(self, sello): @@ -89,12 +100,13 @@ class CFDI(object): return xml def _validate(self, datos): - print (datos['impuestos']['total_locales_trasladados']) - print (datos['impuestos']['total_locales_retenciones']) if datos['impuestos']['total_locales_trasladados'] or \ datos['impuestos']['total_locales_retenciones']: self._impuestos_locales = True + if datos['donativo']: + self._donativo = True + if 'nomina' in datos: return self._validate_nomina(datos) return True @@ -121,14 +133,20 @@ class CFDI(object): attributes['xmlns:{}'.format(self._pre)] = self._sat_cfdi['xmlns'] attributes['xmlns:xsi'] = self._xsi - shema_locales = '' + schema_locales = '' if self._impuestos_locales: name = 'xmlns:{}'.format(SAT['locales']['prefix']) attributes[name] = SAT['locales']['xmlns'] - shema_locales = SAT['locales']['schema'] + schema_locales = SAT['locales']['schema'] + + schema_donativo = '' + if self._donativo: + name = 'xmlns:{}'.format(SAT['donativo']['prefix']) + attributes[name] = SAT['donativo']['xmlns'] + schema_donativo = SAT['donativo']['schema'] attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \ - shema_locales + schema_locales + schema_donativo attributes.update(datos) if not 'Version' in attributes: @@ -302,6 +320,23 @@ class CFDI(object): ET.SubElement(node, 'implocal:TrasladosLocales', traslado) return + def _donatarias(self, datos): + if not datos: + return + + if self._complemento is None: + self._complemento = ET.SubElement( + self._cfdi, '{}:Complemento'.format(self._pre)) + + attributes = {} + attributes['version'] = SAT['donativo']['version'] + attributes['leyenda'] = SAT['donativo']['leyenda'] + attributes.update(datos) + + node = ET.SubElement(self._complemento, 'donat:Donatarias', attributes) + + return + def _complementos(self, datos): self._complemento = ET.SubElement( self._cfdi, '{}:Complemento'.format(self._pre)) diff --git a/source/app/models/main.py b/source/app/models/main.py index 9aee95e..560d7f2 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -2122,6 +2122,7 @@ class Facturas(BaseModel): values['tipo_cambio'] = float(values['tipo_cambio']) values['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal values['anticipo'] = util.get_bool(values['anticipo']) + values['donativo'] = util.get_bool(values['donativo']) with database_proxy.atomic() as txn: obj = Facturas.create(**values) @@ -2153,8 +2154,15 @@ class Facturas(BaseModel): def _make_xml(self, invoice): emisor = Emisor.select()[0] certificado = Certificado.select()[0] + comprobante = {} relacionados = {} + donativo = {} + + if invoice.donativo: + donativo['noAutorizacion'] = emisor.autorizacion + donativo['fechaAutorizacion'] = str(emisor.fecha_autorizacion) + if invoice.serie: comprobante['Serie'] = invoice.serie if invoice.condiciones_pago: @@ -2325,6 +2333,7 @@ class Facturas(BaseModel): 'receptor': receptor, 'conceptos': conceptos, 'impuestos': impuestos, + 'donativo': donativo, } return util.make_xml(data, certificado) diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index eb742dd..49e6402 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -467,6 +467,7 @@ function guardar_y_timbrar(values){ data['relacionados'] = ids data['tipo_relacion'] = tipo_relacion data['anticipo'] = anticipo + data['donativo'] = $$('chk_cfdi_donativo').getValue() if(!save_invoice(data)){ return From 74ac8b92956e320ed0ee497ef6fdeba77d437435 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Mon, 20 Nov 2017 00:47:23 -0600 Subject: [PATCH 06/13] Soporte complemento ine --- source/app/controllers/cfdi_xml.py | 37 +++++++++++++++--- source/app/models/main.py | 47 ++++++++++++++++++++--- source/static/js/controller/admin.js | 1 + source/static/js/controller/invoices.js | 44 ++++++++++++++++++++- source/static/js/ui/admin.js | 4 ++ source/static/js/ui/invoices.js | 44 ++++++++++++++++++++- source/xslt/cadena.xslt | 2 +- source/xslt/ine11.xslt | 51 +++++++++++++++++++++++++ 8 files changed, 217 insertions(+), 13 deletions(-) create mode 100644 source/xslt/ine11.xslt diff --git a/source/app/controllers/cfdi_xml.py b/source/app/controllers/cfdi_xml.py index d9589b2..f680f58 100644 --- a/source/app/controllers/cfdi_xml.py +++ b/source/app/controllers/cfdi_xml.py @@ -52,6 +52,12 @@ SAT = { 'schema': ' http://www.sat.gob.mx/donat http://www.sat.gob.mx/sitio_internet/cfd/donat/donat11.xsd', 'leyenda': 'Este comprobante ampara un donativo, el cual será destinado por la donataria a los fines propios de su objeto social. En el caso de que los bienes donados hayan sido deducidos previamente para los efectos del impuesto sobre la renta, este donativo no es deducible. La reproducción no autorizada de este comprobante constituye un delito en los términos de las disposiciones fiscales.', }, + 'ine': { + 'version': '1.1', + 'prefix': 'ine', + 'xmlns': 'http://www.sat.gob.mx/ine', + 'schema': ' http://www.sat.gob.mx/ine http://www.sat.gob.mx/sitio_internet/cfd/ine/ine11.xsd', + }, } @@ -65,6 +71,7 @@ class CFDI(object): self._complemento = None self._impuestos_locales = False self._donativo = False + self._ine = False self.error = '' def _now(self): @@ -82,11 +89,12 @@ class CFDI(object): self._impuestos(datos['impuestos']) self._locales(datos['impuestos']) self._donatarias(datos['donativo']) + self._complementos(datos['complementos']) if 'nomina' in datos: self._nomina(datos['nomina']) - if 'complementos' in datos: - self._complementos(datos['complementos']) + #~ if 'complementos' in datos: + #~ self._complementos(datos['complementos']) return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8')) @@ -107,6 +115,9 @@ class CFDI(object): if datos['donativo']: self._donativo = True + if 'ine' in datos['complementos']: + self._ine = True + if 'nomina' in datos: return self._validate_nomina(datos) return True @@ -145,8 +156,14 @@ class CFDI(object): attributes[name] = SAT['donativo']['xmlns'] schema_donativo = SAT['donativo']['schema'] + schema_ine = '' + if self._ine: + name = 'xmlns:{}'.format(SAT['ine']['prefix']) + attributes[name] = SAT['ine']['xmlns'] + schema_donativo = SAT['ine']['schema'] + attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \ - schema_locales + schema_donativo + schema_locales + schema_donativo +schema_ine attributes.update(datos) if not 'Version' in attributes: @@ -338,8 +355,18 @@ class CFDI(object): return def _complementos(self, datos): - self._complemento = ET.SubElement( - self._cfdi, '{}:Complemento'.format(self._pre)) + if not datos: + return + + if self._complemento is None: + self._complemento = ET.SubElement( + self._cfdi, '{}:Complemento'.format(self._pre)) + + if 'ine' in datos: + atributos = {'Version': SAT['ine']['version']} + atributos.update(datos['ine']) + ET.SubElement(self._complemento, 'ine:INE', atributos) + if 'ce' in datos: pre = 'cce11' datos = datos.pop('ce') diff --git a/source/app/models/main.py b/source/app/models/main.py index 560d7f2..fff5fb9 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -111,6 +111,7 @@ def config_timbrar(): conf = { 'cfdi_anticipo': Configuracion.get_('chk_config_anticipo'), 'cfdi_donativo': obj.es_ong, + 'cfdi_ine': Configuracion.get_('chk_config_ine'), } return conf @@ -150,16 +151,19 @@ class Configuracion(BaseModel): ) elif keys['fields'] == 'templates': fields = ( - 'txt_plantilla_factura_32', - 'txt_plantilla_factura_33', - 'txt_plantilla_factura_33j') + 'txt_plantilla_factura_32', + 'txt_plantilla_factura_33', + 'txt_plantilla_factura_33j' + ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) elif keys['fields'] == 'configotros': fields = ( - 'chk_config_anticipo',) + 'chk_config_anticipo', + 'chk_config_ine', + ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) @@ -2112,10 +2116,23 @@ class Facturas(BaseModel): FacturasRelacionadas.create(**data) return + def _guardar_ine(self, invoice, valores): + if not valores: + return + + data = { + 'factura': invoice, + 'nombre': 'ine', + 'valores': valores, + } + FacturasComplementos.create(**data) + return + @classmethod def add(cls, values): productos = util.loads(values.pop('productos')) relacionados = util.loads(values.pop('relacionados')) + ine = values.pop('ine', {}) emisor = Emisor.select()[0] values['folio'] = cls._get_folio(cls, values['serie']) @@ -2128,6 +2145,7 @@ class Facturas(BaseModel): obj = Facturas.create(**values) totals = cls._calculate_totals(cls, obj, productos) cls._guardar_relacionados(cls, obj, relacionados) + cls._guardar_ine(cls, obj, ine) obj.subtotal = totals['subtotal'] obj.descuento = totals['descuento'] obj.total_trasladados = totals['total_trasladados'] @@ -2158,6 +2176,7 @@ class Facturas(BaseModel): comprobante = {} relacionados = {} donativo = {} + complementos = FacturasComplementos.get_(invoice) if invoice.donativo: donativo['noAutorizacion'] = emisor.autorizacion @@ -2334,6 +2353,7 @@ class Facturas(BaseModel): 'conceptos': conceptos, 'impuestos': impuestos, 'donativo': donativo, + 'complementos': complementos, } return util.make_xml(data, certificado) @@ -2791,6 +2811,23 @@ class FacturasRelacionadas(BaseModel): return [str(r.factura_origen.uuid) for r in query] +class FacturasComplementos(BaseModel): + factura = ForeignKeyField(Facturas) + nombre = TextField(default='') + valores = TextField(default='') + + class Meta: + order_by = ('factura',) + + @classmethod + def get_(cls, factura): + query = (FacturasComplementos + .select() + .where(FacturasComplementos.factura==factura) + ) + return {r.nombre: util.loads(r.valores) for r in query} + + class CfdiPagosFacturas(BaseModel): pago = ForeignKeyField(CfdiPagos) factura = ForeignKeyField(Facturas) @@ -3113,7 +3150,7 @@ def _crear_tablas(rfc): tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion, Folios, Emisor, Facturas, FacturasDetalle, FacturasImpuestos, FacturasPagos, - FacturasRelacionadas, Productos, + FacturasRelacionadas, FacturasComplementos, Productos, PreFacturas, PreFacturasDetalle, PreFacturasImpuestos, PreFacturasRelacionadas, SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes, diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index cfbdc49..3a75a1a 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -40,6 +40,7 @@ var controllers = { $$('txt_plantilla_factura_32').attachEvent('onItemClick', txt_plantilla_factura_32_click) $$('txt_plantilla_factura_33').attachEvent('onItemClick', txt_plantilla_factura_33_click) $$('chk_config_anticipo').attachEvent('onItemClick', chk_config_item_click) + $$('chk_config_ine').attachEvent('onItemClick', chk_config_item_click) } } diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 49e6402..d6e9c3b 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -112,7 +112,11 @@ function default_config(){ var values = data.json() show('chk_cfdi_anticipo', values.cfdi_anticipo) show('chk_cfdi_donativo', values.cfdi_donativo) - + if(values.cfdi_ine == '0'){ + $$('tv_invoice').getTabbar().hideOption('INE') + }else{ + $$('tv_invoice').getTabbar().showOption('INE') + } }) } @@ -293,6 +297,32 @@ function validate_invoice(values){ } } + usar_ine = $$('chk_cfdi_usar_ine').getValue() + if(usar_ine){ + var id_contabilidad = $$('txt_ine_idcontabilidad').getValue().trim() + if(!id_contabilidad){ + $$('tv_invoice').getTabbar().setValue('INE') + msg = 'El ID de contabilidad es requerido si se usa el complemento INE' + msg_error(msg) + return false + } + + if(!id_contabilidad.is_number()){ + $$('tv_invoice').getTabbar().setValue('INE') + msg = 'El ID de contabilidad deben ser solo digitos' + msg_error(msg) + return False + } + + if(id_contabilidad.length != 6){ + $$('tv_invoice').getTabbar().setValue('INE') + msg = 'El ID de contabilidad deben ser 6 digitos' + msg_error(msg) + return False + } + + } + return true } @@ -379,6 +409,7 @@ function save_invoice(data){ if(values.ok){ msg_sucess('Factura guardada correctamente. Enviando a timbrar') update_grid_invoices(values) + gi.showItem(values.row['id']) send_timbrar(values.row['id']) result = true }else{ @@ -469,6 +500,16 @@ function guardar_y_timbrar(values){ data['anticipo'] = anticipo data['donativo'] = $$('chk_cfdi_donativo').getValue() + var usar_ine = $$('chk_cfdi_usar_ine').getValue() + if(usar_ine){ + var valores = { + TipoProceso: $$('lst_ine_tipo_proceso').getValue(), + TipoComite: $$('lst_ine_tipo_comite').getValue(), + IdContabilidad: $$('txt_ine_idcontabilidad').getValue(), + } + data['ine'] = valores + } + if(!save_invoice(data)){ return } @@ -477,6 +518,7 @@ function guardar_y_timbrar(values){ tipo_relacion = '' anticipo = false $$('chk_cfdi_anticipo').setValue(0) + $$('chk_cfdi_usar_ine').getValue(0) $$('form_invoice').setValues({id_partner: 0, lbl_partner: 'Ninguno'}) $$('multi_invoices').setValue('invoices_home') diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index d418e5f..3ec846f 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -436,6 +436,10 @@ var options_admin_otros = [ {view: 'checkbox', id: 'chk_config_anticipo', labelWidth: 0, labelRight: 'Ayuda para generar anticipos'}, {}]}, + {cols: [{maxWidth: 15}, + {view: 'checkbox', id: 'chk_config_ine', labelWidth: 0, + labelRight: 'Mostrar el complemento INE al facturar'}, + {}]}, {maxHeight: 20}, {}] diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index 32fa67e..dd70624 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -524,14 +524,56 @@ var controls_prefactura = [ ] +var opt_tipo_proceso = [ + {id: 'Ordinario', value: 'Ordinario'}, + {id: 'Precampaña', value: 'Precampaña'}, + {id: 'Campaña', value: 'Campaña'}, +] + + +var opt_tipo_comite = [ + {id: 'Ejecutivo Nacional', value: 'Ejecutivo Nacional'}, + {id: 'Ejecutivo Estatal', value: 'Ejecutivo Estatal'}, + {id: 'Directivo Estatal', value: 'Directivo Estatal'}, +] + + +var controles_ine = [ + {maxHeight: 15}, + {cols: [{maxWidth: 15}, + {view: 'checkbox', id: 'chk_cfdi_usar_ine', labelWidth: 0, + labelRight: 'Usar el complemento INE'}, + {}]}, + {maxHeight: 10}, + {cols: [{maxWidth: 15}, + {view: 'richselect', id: 'lst_ine_tipo_proceso', labelWidth: 150, + label: 'Tipo de Proceso', options: opt_tipo_proceso, + value: 'Ordinario'}, + {}]}, + {maxHeight: 10}, + {cols: [{maxWidth: 15}, + {view: 'richselect', id: 'lst_ine_tipo_comite', labelWidth: 150, + label: 'Tipo de Comite', options: opt_tipo_comite, + value: 'Ejecutivo Nacional'}, + {}]}, + {maxHeight: 10}, + {cols: [{maxWidth: 15}, + {view: 'text', id: 'txt_ine_idcontabilidad', name: 'ine_idcontabilidad', + label: 'ID de Contabilidad: ', labelWidth: 150}, + {}]}, +] + + var controls_invoices = [ { view: 'tabview', id: 'tv_invoice', - tabbar: {options: ['Generar', 'PreFacturas']}, animate: true, + //~ tabbar: {options: ['Generar', 'PreFacturas', 'INE']}, + animate: true, cells: [ {id: 'Generar', rows: controls_generate}, {id: 'PreFacturas', rows: controls_prefactura}, + {id: 'INE', rows: controles_ine}, ] }, ] diff --git a/source/xslt/cadena.xslt b/source/xslt/cadena.xslt index 42502d3..be01a88 100644 --- a/source/xslt/cadena.xslt +++ b/source/xslt/cadena.xslt @@ -10,6 +10,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 2083a19db2160c1469348d62c043b646610acafe Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Tue, 21 Nov 2017 00:48:51 -0600 Subject: [PATCH 07/13] Agregar retiros de banco --- source/app/controllers/main.py | 2 +- source/app/controllers/util.py | 6 +- source/app/models/db.py | 3 + source/app/models/main.py | 34 ++++++- source/static/js/controller/bancos.js | 136 +++++++++++++++++++++++++- source/static/js/controller/main.js | 2 +- source/static/js/controller/util.js | 10 ++ source/static/js/ui/bancos.js | 66 ++++++++++++- 8 files changed, 245 insertions(+), 14 deletions(-) diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 2afb7a0..0b2cceb 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -287,7 +287,7 @@ class AppMovimientosBanco(object): def on_post(self, req, resp): values = req.params - req.context['result'] = self._db.cuentasbanco(values) + req.context['result'] = self._db.add_movbanco(values) resp.status = falcon.HTTP_200 diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 1a1e82d..db46559 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, DEFAULT_SAT_PRODUCTO + PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES #~ def _get_hash(password): @@ -1270,6 +1270,10 @@ def get_bool(value): return False +def get_float(value): + return round(float(value), DECIMALES) + + class ImportFacturaLibre(object): def __init__(self, path, rfc): diff --git a/source/app/models/db.py b/source/app/models/db.py index 4d5becd..a74fc7e 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -228,6 +228,9 @@ class StorageEngine(object): def cuentasbanco(self, values): return main.CuentasBanco.add(values) + def add_movbanco(self, values): + return main.MovimientosBanco.add(values) + def get_cuentasbanco(self, values): return main.CuentasBanco.get_(values) diff --git a/source/app/models/main.py b/source/app/models/main.py index fff5fb9..6199bd5 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -954,6 +954,12 @@ class CuentasBanco(BaseModel): def __str__(self): return '{} ({})'.format(self.banco.name, self.cuenta[-4:]) + @classmethod + def actualizar_saldo(cls, id, saldo): + fields = {'saldo': saldo} + q = CuentasBanco.update(**fields).where(CuentasBanco.id==id) + return bool(q.execute()) + @classmethod def remove(cls, id): try: @@ -1049,7 +1055,6 @@ class CuentasBanco(BaseModel): nuevo_mov= { 'cuenta': obj.id, 'fecha': fecha_deposito, - 'movimiento': 1, 'descripcion': 'Saldo inicial', 'forma_pago': SATFormaPago.get_by_key('99'), 'deposito': values['saldo'], @@ -1080,7 +1085,6 @@ class CuentasBanco(BaseModel): class MovimientosBanco(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, @@ -1105,12 +1109,28 @@ class MovimientosBanco(BaseModel): class Meta: order_by = ('fecha',) - indexes = ( - (('cuenta', 'movimiento'), True), + + def _ultimo_saldo(self, cuenta): + query = (MovimientosBanco + .select() + .where(MovimientosBanco.cuenta==cuenta)[-1] ) + return round(float(query.saldo), DECIMALES) @classmethod def add(cls, values): + #~ print(values) + if not 'saldo' in values: + hora = values.pop('hora') + values['fecha'] = '{}T{}'.format(values['fecha'][:10], hora) + values['cuenta'] = int(values['cuenta']) + values['retiro'] = util.get_float(values['retiro']) + values['deposito'] = util.get_float(values['deposito']) + values['forma_pago'] = int(values['forma_pago']) + + ultimo_saldo = cls._ultimo_saldo(cls, values['cuenta']) + values['saldo'] = \ + ultimo_saldo - values['retiro'] + values['deposito'] with database_proxy.transaction(): try: obj = MovimientosBanco.create(**values) @@ -1118,7 +1138,9 @@ class MovimientosBanco(BaseModel): msg = 'Este movimiento ya existe' return {'ok': False, 'msg': msg} - return {'ok': True} + CuentasBanco.actualizar_saldo(values['cuenta'], obj.saldo) + + return {'ok': True, 'saldo': obj.saldo} @classmethod def con(cls, id): @@ -1173,6 +1195,8 @@ class MovimientosBanco(BaseModel): class CfdiPagos(BaseModel): movimiento = ForeignKeyField(MovimientosBanco) + fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) + fecha_timbrado = DateTimeField(null=True) xml = TextField(default='') uuid = UUIDField(null=True) estatus = TextField(default='Guardado') diff --git a/source/static/js/controller/bancos.js b/source/static/js/controller/bancos.js index 5647674..fd6558a 100644 --- a/source/static/js/controller/bancos.js +++ b/source/static/js/controller/bancos.js @@ -5,6 +5,8 @@ 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) + $$('cmd_guardar_retiro').attachEvent('onItemClick', cmd_guardar_retiro_click) + $$('txt_retiro_importe').attachEvent('onChange', txt_retiro_importe_change) set_year_month() } } @@ -31,7 +33,6 @@ function set_year_month(){ m.unblockEvent() } }) - } @@ -96,11 +97,140 @@ function lst_cuentas_banco_change(nv, ov){ } +function get_retiro_forma_pago(){ + webix.ajax().get('/values/formapago', {}, function(text, data){ + var values = data.json() + $$('lst_retiro_forma_pago').getList().parse(values) + }) +} + + function cmd_agregar_retiro_click(){ - show('Retiro') + get_retiro_forma_pago() + $$('multi_bancos').setValue('banco_retiro') } function cmd_agregar_deposito_click(){ - show('Depósito') + showvar('Depósito') +} + + +function validate_retiro(values){ + var importe = values.retiro_importe.replace('$', '').replace(',', '').trim() + + if(!importe){ + msg = 'El importe es requerido' + msg_error(msg) + return false + } + importe = parseFloat(importe).round(2) + if(importe <= 0){ + msg = 'El importe debe ser mayor a cero' + msg_error(msg) + return false + } + + if(!values.retiro_descripcion.trim()){ + msg = 'La descripción es requerida' + msg_error(msg) + return false + } + + var horas = $$('time_retiro').getText().split(':') + var seg = parseInt(horas[2]) + var min = parseInt(horas[1]) + var horas = parseInt(horas[0]) + + if(horas > 23){ + focus('time_retiro') + msg = 'Hora inválida' + msg_error(msg) + return false + } + if(min > 59){ + focus('time_retiro') + msg = 'Hora inválida' + msg_error(msg) + return false + } + if(seg > 59){ + focus('time_retiro') + msg = 'Hora inválida' + msg_error(msg) + return false + } + + return true +} + + +function guardar_retiro(values){ + var form = $$('form_banco_retiro') + + var importe = get_float(values.retiro_importe) + var data = new Object() + data['cuenta'] = $$('lst_cuentas_banco').getValue() + data['fecha'] = values.retiro_fecha + data['hora'] = $$('time_retiro').getText() + data['numero_operacion'] = values.retiro_referencia.trim() + data['forma_pago'] = $$('lst_retiro_forma_pago').getValue() + data['retiro'] = importe + data['deposito'] = 0.0 + data['descripcion'] = values.retiro_descripcion + + webix.ajax().post('/movbanco', data, { + 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){ + $$('txt_cuenta_saldo').setValue(values.saldo) + get_estado_cuenta() + $$('multi_bancos').setValue('banco_home') + form.setValues({}) + }else{ + msg_error(values.msg) + } + } + }) +} + + +function cmd_guardar_retiro_click(){ + var form = $$('form_banco_retiro') + + if(!form.validate()) { + msg_error('Valores inválidos') + return + } + + var values = form.getValues() + if(!validate_retiro(values)){ + return + } + + msg = 'Todos los datos son correctos.

¿Deseas agregar este retiro?' + webix.confirm({ + title: 'Guardar Retiro', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if(result){ + guardar_retiro(values) + } + } + }) +} + + +function txt_retiro_importe_change(new_value, old_value){ + if(!isFinite(new_value)){ + this.config.value = old_value + this.refresh() + } } diff --git a/source/static/js/controller/main.js b/source/static/js/controller/main.js index d44948d..2453a0e 100644 --- a/source/static/js/controller/main.js +++ b/source/static/js/controller/main.js @@ -170,7 +170,7 @@ function multi_change(prevID, nextID){ if(nextID == 'app_bancos'){ active = $$('multi_bancos').getActiveId() - if(active == 'bancos_home'){ + if(active == 'banco_home'){ get_cuentas_banco() } return diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index c89af4d..142b4dd 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -33,6 +33,11 @@ var months = [ ] +function focus(name){ + webix.UIManager.setFocus(name) +} + + function showvar(values){ webix.message(JSON.stringify(values, null, 2)) } @@ -70,6 +75,11 @@ String.prototype.is_number = function(){ } +function get_float(value){ + return parseFloat(value.replace('$', '').replace(',', '').trim()).round(2) +} + + webix.protoUI({ $cssName: "text", name: "currency", diff --git a/source/static/js/ui/bancos.js b/source/static/js/ui/bancos.js index a17157a..60eea37 100644 --- a/source/static/js/ui/bancos.js +++ b/source/static/js/ui/bancos.js @@ -51,16 +51,76 @@ var grid_cuentabanco = { } +var toolbar_banco_retiro = [ + {view: 'label', label: 'Agregar retiro de banco'}, + {}, + {view: 'icon', click: '$$("multi_bancos").setValue("banco_home")', + icon: 'times-circle'} +] + + +var body_banco_retiro = [ + +] + +var controls_banco_retiro = [ + {view: 'toolbar', elements: toolbar_banco_retiro}, + {cols: [ + {view: 'datepicker', id: 'date_retiro', name: 'retiro_fecha', + label: 'Fecha', format: '%d-%M-%Y', labelAlign: 'right', + required: true, invalidMessage: 'Selecciona una fecha', + labelWidth: 125}, + {view: 'search', id: 'time_retiro', name: 'retiro_hora', + label: 'Hora', icon: 'clock-o', labelAlign: 'right', + pattern:{mask: '##:##:##', allow:/[0-9]/g}, required: true, + invalidMessage: 'Captura una hora'}, + {view: 'text', id: 'retiro_referencia', name: 'retiro_referencia', + label: 'Referencia', labelAlign: 'right'}, + ]}, + {cols: [ + {view: 'richselect', id: 'lst_retiro_forma_pago', + name: 'retiro_forma_pago', label: 'Forma de Pago', required: true, + options: [], labelWidth: 125, labelAlign: 'right'}, + {view: 'currency', type: 'text', id: 'txt_retiro_importe', + name: 'retiro_importe', label: 'Importe', labelAlign: 'right', + required: true, invalidMessage: 'Captura un valor númerico', + inputAlign: 'right', value: ''} + ]}, + {cols: [ + {view: 'text', id: 'retiro_descripcion', name: 'retiro_descripcion', + label: 'Descripción', labelAlign: 'right', required: true, + labelWidth: 125}, + ]}, + {cols: [{}, + {view: 'button', id: 'cmd_guardar_retiro', label: 'Guardar Retiro', + type: 'iconButton', autowidth: true, icon: 'minus'}, + {}]}, +] + + +var form_banco_retiro = { + type: 'space', + responsive: true, + cols: [{ + view: 'form', + id: 'form_banco_retiro', + complexData: true, + scroll: true, + elements: controls_banco_retiro, + }] +} + + var multi_bancos = { id: 'multi_bancos', animate: true, cells:[ - {id: 'bancos_home', rows:[ + {id: 'banco_home', rows:[ {view: 'toolbar', elements: toolbar_banco}, {view: 'toolbar', elements: toolbar_filtro_cuenta}, grid_cuentabanco, - ]} - //~ {id: 'partners_new', rows:[form_partner]} + ]}, + {id: 'banco_retiro', rows: [form_banco_retiro]} ], } From 61ff2ce29aa10b55a01746f9ef442bb693803cc7 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Wed, 22 Nov 2017 00:46:23 -0600 Subject: [PATCH 08/13] UI para depositos --- source/app/controllers/main.py | 10 +- source/app/controllers/util.py | 2 +- source/app/models/main.py | 131 +++++++++++++++++---- source/static/js/controller/bancos.js | 61 +++++++++- source/static/js/controller/invoices.js | 4 +- source/static/js/controller/main.js | 15 +++ source/static/js/controller/util.js | 5 + source/static/js/ui/bancos.js | 146 ++++++++++++++++++++++-- source/static/js/ui/invoices.js | 2 +- source/static/js/ui/main.js | 5 +- 10 files changed, 336 insertions(+), 45 deletions(-) diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 0b2cceb..183a479 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -18,9 +18,10 @@ class AppLogin(object): session = req.env['beaker.session'] values = req.params values['rfc'] = values['rfc'].upper() - result = self._db.authenticate(values) + result, user = self._db.authenticate(values) if result['login']: session.save() + session['userobj'] = user session['user'] = result['user'] session['rfc'] = values['rfc'] req.context['result'] = result @@ -68,7 +69,11 @@ class AppValues(object): def on_get(self, req, resp, table): values = req.params - req.context['result'] = self._db.get_values(table, values) + if table == 'admin': + session = req.env['beaker.session'] + req.context['result'] = session['userobj'].es_admin + else: + req.context['result'] = self._db.get_values(table, values) resp.status = falcon.HTTP_200 def on_delete(self, req, resp, table): @@ -140,7 +145,6 @@ 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 db46559..bc4d446 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -816,7 +816,7 @@ class LIBO(object): image.GraphicURL = data['path_cbb'] pd.add(image) s = Size() - s.Width = 4250 + s.Width = 4150 s.Height = 4500 image.setSize(s) image.Anchor = self._set_cell('{timbre.cbb}') diff --git a/source/app/models/main.py b/source/app/models/main.py index 6199bd5..939223f 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -176,7 +176,7 @@ class Configuracion(BaseModel): def add(cls, values): try: for k, v in values.items(): - print (k, v) + #~ print (k, v) obj, created = Configuracion.get_or_create(clave=k) obj.valor = v obj.save() @@ -1110,17 +1110,42 @@ class MovimientosBanco(BaseModel): class Meta: order_by = ('fecha',) - def _ultimo_saldo(self, cuenta): + def _ultimo_saldo(self, cuenta, fecha): query = (MovimientosBanco .select() - .where(MovimientosBanco.cuenta==cuenta)[-1] + .where( + (MovimientosBanco.cuenta==cuenta) & + (MovimientosBanco.fecharow.fecha)) + ) + + saldo = row.saldo + if row.retiro: + importe = row.retiro * -1 + else: + importe = row.deposito + + for mov in query: + mov.saldo = saldo + importe + mov.save() + saldo = mov.saldo + CuentasBanco.actualizar_saldo(None, saldo) + return saldo + @classmethod def add(cls, values): #~ print(values) + actualizar = False if not 'saldo' in values: + actualizar = True hora = values.pop('hora') values['fecha'] = '{}T{}'.format(values['fecha'][:10], hora) values['cuenta'] = int(values['cuenta']) @@ -1128,9 +1153,11 @@ class MovimientosBanco(BaseModel): values['deposito'] = util.get_float(values['deposito']) values['forma_pago'] = int(values['forma_pago']) - ultimo_saldo = cls._ultimo_saldo(cls, values['cuenta']) + ultimo_saldo = cls._ultimo_saldo( + cls, values['cuenta'], values['fecha']) values['saldo'] = \ ultimo_saldo - values['retiro'] + values['deposito'] + with database_proxy.transaction(): try: obj = MovimientosBanco.create(**values) @@ -1138,9 +1165,10 @@ class MovimientosBanco(BaseModel): msg = 'Este movimiento ya existe' return {'ok': False, 'msg': msg} - CuentasBanco.actualizar_saldo(values['cuenta'], obj.saldo) + if actualizar: + saldo = cls._actualizar_saldos(cls, obj) - return {'ok': True, 'saldo': obj.saldo} + return {'ok': True, 'saldo': saldo} @classmethod def con(cls, id): @@ -1328,7 +1356,7 @@ class Socios(BaseModel): @classmethod def get_(cls, values): - print ('values', values) + #~ print ('values', values) id = values.get('id', 0) if id: id = int(values['id']) @@ -1447,7 +1475,16 @@ class Socios(BaseModel): return bool(q.execute()) +class Almacenes(BaseModel): + nombre = TextField(default='') + ubicacion = TextField(default='') + + class Meta: + order_by = ('nombre',) + + class Productos(BaseModel): + almacen = ForeignKeyField(Almacenes, null=True) categoria = ForeignKeyField(Categorias, null=True) clave = TextField(unique=True, index=True) clave_sat = TextField(default='') @@ -1678,6 +1715,8 @@ class Facturas(BaseModel): estatus_sat = TextField(default='Vigente') regimen_fiscal = TextField(default='') notas = TextField(default='') + saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, + auto_round=True) pagada = BooleanField(default=False) cancelada = BooleanField(default=False) fecha_cancelacion = DateTimeField(null=True) @@ -1928,7 +1967,39 @@ class Facturas(BaseModel): return (Facturas.folio.between(folio1, folio2)) + def _get_por_pagar(self): + filtros = ( + (Facturas.cancelada==False) & + (Facturas.uuid.is_null(False)) & + (Facturas.tipo_comprobante=='I') & + (Facturas.saldo>0) + ) + rows = tuple(Facturas + .select( + Facturas.id, + Facturas.serie, + Facturas.folio, + Facturas.uuid, + Facturas.fecha, + Facturas.tipo_comprobante, + Facturas.estatus, + Socios.nombre.alias('cliente'), + Facturas.total, + Facturas.saldo, + ) + .where(filtros) + .join(Socios) + .switch(Facturas) + .dicts() + ) + return {'ok': True, 'rows': rows} + + return + def _get_opt(self, values): + if values['opt'] == 'porpagar': + return self._get_por_pagar(self) + cfdis = util.loads(values['cfdis']) if values['year'] == '-1': @@ -2175,6 +2246,7 @@ class Facturas(BaseModel): obj.total_trasladados = totals['total_trasladados'] obj.total_retenciones = totals['total_retenciones'] obj.total = totals['total'] + obj.saldo = totals['total'] obj.total_mn = totals['total_mn'] obj.save() @@ -2855,11 +2927,18 @@ class FacturasComplementos(BaseModel): 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'), True), + (('pago', 'factura', 'numero'), True), ) @@ -3005,21 +3084,21 @@ 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 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 Meta: + #~ order_by = ('factura',) + #~ indexes = ( + #~ (('factura', 'numero'), True), + #~ ) class PreFacturasImpuestos(BaseModel): @@ -3080,9 +3159,11 @@ def authenticate(args): respuesta['msg'] = '' respuesta['login'] = True respuesta['user'] = str(obj) + #~ respuesta['user'] = obj respuesta['super'] = obj.es_superusuario + #~ respuesta['admin'] = obj.es_superusuario or obj.es_admin #~ desconectar() - return respuesta + return respuesta, obj def get_cp(cp): @@ -3173,8 +3254,8 @@ def _init_values(rfc): def _crear_tablas(rfc): tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion, Folios, - Emisor, Facturas, FacturasDetalle, FacturasImpuestos, FacturasPagos, - FacturasRelacionadas, FacturasComplementos, Productos, + Emisor, Facturas, FacturasDetalle, FacturasImpuestos, + FacturasRelacionadas, FacturasComplementos, Almacenes, Productos, PreFacturas, PreFacturasDetalle, PreFacturasImpuestos, PreFacturasRelacionadas, SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes, diff --git a/source/static/js/controller/bancos.js b/source/static/js/controller/bancos.js index fd6558a..d476871 100644 --- a/source/static/js/controller/bancos.js +++ b/source/static/js/controller/bancos.js @@ -7,6 +7,8 @@ var bancos_controllers = { $$('cmd_agregar_deposito').attachEvent('onItemClick', cmd_agregar_deposito_click) $$('cmd_guardar_retiro').attachEvent('onItemClick', cmd_guardar_retiro_click) $$('txt_retiro_importe').attachEvent('onChange', txt_retiro_importe_change) + $$('grid_cfdi_este_deposito').attachEvent('onAfterDrop', grid_cfdi_este_deposito_after_drop) + $$('grid_cfdi_por_pagar').attachEvent('onAfterDrop', grid_cfdi_por_pagar_after_drop) set_year_month() } } @@ -97,22 +99,47 @@ function lst_cuentas_banco_change(nv, ov){ } -function get_retiro_forma_pago(){ +function get_bancos_forma_pago(retiro){ webix.ajax().get('/values/formapago', {}, function(text, data){ var values = data.json() - $$('lst_retiro_forma_pago').getList().parse(values) + if(retiro){ + $$('lst_retiro_forma_pago').getList().parse(values) + }else{ + $$('lst_deposito_forma_pago').getList().parse(values) + } + }) +} + + +function get_facturas_por_pagar(){ + var grid = $$('grid_cfdi_por_pagar') + + webix.ajax().get('/invoices', {'opt': 'porpagar'}, { + 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') + } + } }) } function cmd_agregar_retiro_click(){ - get_retiro_forma_pago() + get_bancos_forma_pago(true) $$('multi_bancos').setValue('banco_retiro') } function cmd_agregar_deposito_click(){ - showvar('Depósito') + get_bancos_forma_pago(false) + get_facturas_por_pagar() + $$('multi_bancos').setValue('banco_deposito') } @@ -234,3 +261,29 @@ function txt_retiro_importe_change(new_value, old_value){ this.refresh() } } + + +function actualizar_deposito(grid){ + var suma = 0 + var descripcion = '' + grid.data.each(function(obj){ + suma += obj.saldo.to_float() + descripcion += 'Pago de la factura: ' + obj.serie + obj.folio + ' del ' + descripcion += 'cliente: ' + obj.cliente + '\n' + }) + $$('txt_deposito_importe').setValue(suma) + $$('deposito_descripcion').setValue(descripcion) +} + + +function grid_cfdi_por_pagar_after_drop(context, native_event){ + var grid = $$('grid_cfdi_este_deposito') + actualizar_deposito(grid) +} + + +function grid_cfdi_este_deposito_after_drop(context, native_event){ + var grid = $$('grid_cfdi_este_deposito') + grid.sort("#fecha#", "asc", "string") + actualizar_deposito(grid) +} diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index d6e9c3b..7520e67 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -112,7 +112,7 @@ function default_config(){ var values = data.json() show('chk_cfdi_anticipo', values.cfdi_anticipo) show('chk_cfdi_donativo', values.cfdi_donativo) - if(values.cfdi_ine == '0'){ + if(!values.cfdi_ine || values.cfdi_ine == '0'){ $$('tv_invoice').getTabbar().hideOption('INE') }else{ $$('tv_invoice').getTabbar().showOption('INE') @@ -409,7 +409,7 @@ function save_invoice(data){ if(values.ok){ msg_sucess('Factura guardada correctamente. Enviando a timbrar') update_grid_invoices(values) - gi.showItem(values.row['id']) + gi.select(values.row['id'], false) send_timbrar(values.row['id']) result = true }else{ diff --git a/source/static/js/controller/main.js b/source/static/js/controller/main.js index 2453a0e..68e0815 100644 --- a/source/static/js/controller/main.js +++ b/source/static/js/controller/main.js @@ -1,10 +1,25 @@ var gi = null +function configuracion_inicial(){ + webix.ajax().get('/values/admin', function(text, data){ + var values = data.json() + $$('cmd_ir_al_admin').show(values) + }) +} + + +function cmd_ir_al_admin_click(){ + window.location = '/admin' +} + + var controllers = { init: function(){ //~ Main $$('menu_user').attachEvent('onMenuItemClick', menu_user_click); + configuracion_inicial() + //~ Partner $$('cmd_new_partner').attachEvent('onItemClick', cmd_new_partner_click); $$('cmd_new_contact').attachEvent('onItemClick', cmd_new_contact_click); diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index 142b4dd..7d53233 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -75,6 +75,11 @@ String.prototype.is_number = function(){ } +String.prototype.to_float = function(){ + return get_float(this) +} + + function get_float(value){ return parseFloat(value.replace('$', '').replace(',', '').trim()).round(2) } diff --git a/source/static/js/ui/bancos.js b/source/static/js/ui/bancos.js index 60eea37..b77ad3d 100644 --- a/source/static/js/ui/bancos.js +++ b/source/static/js/ui/bancos.js @@ -51,18 +51,103 @@ var grid_cuentabanco = { } +var grid_cfdi_por_pagar_cols = [ + {id: 'index', header: '#', adjust: 'data', css: 'right'}, + {id: 'id', header: 'ID', hidden: true}, + {id: 'serie', header: 'Serie', adjust: 'data'}, + {id: 'folio', header: 'Folio', adjust: 'data', css: 'right'}, + {id: 'uuid', header: 'UUID', width: 250, hidden: true}, + {id: 'fecha', header: 'Fecha y Hora', width: 150, sort: 'date'}, + {id: 'tipo_comprobante', header: 'Tipo', adjust: 'data'}, + {id: 'estatus', header: 'Estatus', adjust: 'header'}, + {id: 'cliente', header: ['Razón Social', {content: 'selectFilter'}], + fillspace:true, sort: 'string'}, + {id: 'total', header: ['Total'], width: 125, sort: 'int', + format: webix.i18n.priceFormat, css: 'right'}, + {id: 'saldo', header: ['Saldo'], width: 125, sort: 'int', + format: webix.i18n.priceFormat, css: 'right'}, +] + + +var grid_cfdi_este_deposito_cols = [ + {id: 'index', header: '#', adjust: 'data', css: 'right'}, + {id: 'id', header: 'ID', hidden: true}, + {id: 'serie', header: 'Serie', adjust: 'data'}, + {id: 'folio', header: 'Folio', adjust: 'data', css: 'right'}, + {id: 'uuid', header: 'UUID', width: 250, hidden: true}, + {id: 'fecha', header: 'Fecha y Hora', width: 150, sort: 'date'}, + {id: 'tipo_comprobante', header: 'Tipo', adjust: 'data'}, + {id: 'estatus', header: 'Estatus', adjust: 'header'}, + {id: 'cliente', header: ['Razón Social'], fillspace: true}, + {id: 'total', header: ['Total'], width: 125, sort: 'int', + format: webix.i18n.priceFormat, css: 'right'}, + {id: 'saldo', header: ['Saldo'], width: 125, sort: 'int', + format: webix.i18n.priceFormat, css: 'right'}, +] + + +var grid_cfdi_por_pagar = { + view: 'datatable', + id: 'grid_cfdi_por_pagar', + select: 'row', + autoConfig: false, + adjust: true, + height: 250, + resizeColumn: true, + headermenu: true, + drag: true, + columns: grid_cfdi_por_pagar_cols, + on:{ + 'data->onStoreUpdated':function(){ + this.data.each(function(obj, i){ + obj.index = i + 1 + }) + } + }, +} + + +var grid_cfdi_este_deposito = { + view: 'datatable', + id: 'grid_cfdi_este_deposito', + select: 'row', + autoConfig: false, + adjust: true, + height: 200, + resizeColumn: true, + headermenu: true, + drag: true, + columns: grid_cfdi_este_deposito_cols, + on:{ + 'data->onStoreUpdated':function(){ + this.data.each(function(obj, i){ + obj.index = i + 1 + }) + } + }, +} + + var toolbar_banco_retiro = [ {view: 'label', label: 'Agregar retiro de banco'}, {}, + {view: 'button', id: 'cmd_guardar_retiro', label: 'Guardar Retiro', + type: 'iconButton', autowidth: true, icon: 'minus'}, {view: 'icon', click: '$$("multi_bancos").setValue("banco_home")', icon: 'times-circle'} ] -var body_banco_retiro = [ - +var toolbar_banco_deposito = [ + {view: 'label', label: 'Agregar depósito de banco'}, + {}, + {view: 'button', id: 'cmd_guardar_deposito', label: 'Guardar Depósito', + type: 'iconButton', autowidth: true, icon: 'plus'}, + {view: 'icon', click: '$$("multi_bancos").setValue("banco_home")', + icon: 'times-circle'} ] + var controls_banco_retiro = [ {view: 'toolbar', elements: toolbar_banco_retiro}, {cols: [ @@ -91,10 +176,41 @@ var controls_banco_retiro = [ label: 'Descripción', labelAlign: 'right', required: true, labelWidth: 125}, ]}, - {cols: [{}, - {view: 'button', id: 'cmd_guardar_retiro', label: 'Guardar Retiro', - type: 'iconButton', autowidth: true, icon: 'minus'}, - {}]}, +] + + +var controls_banco_deposito = [ + {view: 'toolbar', elements: toolbar_banco_deposito}, + {cols: [ + {view: 'datepicker', id: 'date_deposito', name: 'deposito_fecha', + label: 'Fecha', format: '%d-%M-%Y', labelAlign: 'right', + required: true, invalidMessage: 'Selecciona una fecha', + labelWidth: 125}, + {view: 'search', id: 'time_deposito', name: 'deposito_hora', + label: 'Hora', icon: 'clock-o', labelAlign: 'right', + pattern:{mask: '##:##:##', allow:/[0-9]/g}, required: true, + invalidMessage: 'Captura una hora'}, + {view: 'text', id: 'deposito_referencia', name: 'deposito_referencia', + label: 'Referencia', labelAlign: 'right'}, + ]}, + {cols: [ + {view: 'richselect', id: 'lst_deposito_forma_pago', + name: 'deposito_forma_pago', label: 'Forma de Pago', required: true, + options: [], labelWidth: 125, labelAlign: 'right'}, + {view: 'currency', type: 'text', id: 'txt_deposito_importe', + name: 'deposito_importe', label: 'Importe', labelAlign: 'right', + required: true, invalidMessage: 'Captura un valor númerico', + inputAlign: 'right', value: ''} + ]}, + {cols: [ + {view: 'textarea', id: 'deposito_descripcion', label: 'Descripción', + name: 'deposito_descripcion', labelAlign: 'right', required: true, + labelWidth: 125, height: 70}, + ]}, + {view: 'label', label: 'Facturas por pagar: '}, + grid_cfdi_por_pagar, + {view: 'label', label: 'Facturas a pagar en este depósito: '}, + grid_cfdi_este_deposito, ] @@ -107,7 +223,20 @@ var form_banco_retiro = { complexData: true, scroll: true, elements: controls_banco_retiro, - }] + }], +} + + +var form_banco_deposito = { + type: 'space', + responsive: true, + cols: [{ + view: 'form', + id: 'form_banco_deposito', + complexData: true, + scroll: true, + elements: controls_banco_deposito, + }], } @@ -120,7 +249,8 @@ var multi_bancos = { {view: 'toolbar', elements: toolbar_filtro_cuenta}, grid_cuentabanco, ]}, - {id: 'banco_retiro', rows: [form_banco_retiro]} + {id: 'banco_retiro', rows: [form_banco_retiro]}, + {id: 'banco_deposito', rows: [form_banco_deposito]} ], } diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index dd70624..6df8320 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -228,7 +228,7 @@ var grid_invoices = { obj.index = i + 1 }) } - } + }, } diff --git a/source/static/js/ui/main.js b/source/static/js/ui/main.js index 2e61ebb..5034328 100644 --- a/source/static/js/ui/main.js +++ b/source/static/js/ui/main.js @@ -67,7 +67,10 @@ var ui_main = { {}, menu_user, {view: 'button', type: 'icon', width: 45, css: 'app_button', - icon: 'bell-o', badge: 0} + icon: 'bell-o', badge: 0}, + {view: 'button', type: 'icon', width: 45, css: 'app_button', + icon: 'cogs', id: 'cmd_ir_al_admin', hidden: true, + click: 'cmd_ir_al_admin_click'} ] }, { From 2d749d15641e0b5c6fe7c30c590a424f75e588e0 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Wed, 22 Nov 2017 01:03:42 -0600 Subject: [PATCH 09/13] Fix - UI para depositos --- source/static/js/controller/bancos.js | 4 +++- source/static/js/ui/bancos.js | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/source/static/js/controller/bancos.js b/source/static/js/controller/bancos.js index d476871..2351d00 100644 --- a/source/static/js/controller/bancos.js +++ b/source/static/js/controller/bancos.js @@ -264,12 +264,15 @@ function txt_retiro_importe_change(new_value, old_value){ function actualizar_deposito(grid){ + grid.sort("#fecha#", "desc", "date") + var suma = 0 var descripcion = '' grid.data.each(function(obj){ suma += obj.saldo.to_float() descripcion += 'Pago de la factura: ' + obj.serie + obj.folio + ' del ' descripcion += 'cliente: ' + obj.cliente + '\n' + obj.importe = obj.saldo }) $$('txt_deposito_importe').setValue(suma) $$('deposito_descripcion').setValue(descripcion) @@ -284,6 +287,5 @@ function grid_cfdi_por_pagar_after_drop(context, native_event){ function grid_cfdi_este_deposito_after_drop(context, native_event){ var grid = $$('grid_cfdi_este_deposito') - grid.sort("#fecha#", "asc", "string") actualizar_deposito(grid) } diff --git a/source/static/js/ui/bancos.js b/source/static/js/ui/bancos.js index b77ad3d..06b8137 100644 --- a/source/static/js/ui/bancos.js +++ b/source/static/js/ui/bancos.js @@ -83,6 +83,8 @@ var grid_cfdi_este_deposito_cols = [ format: webix.i18n.priceFormat, css: 'right'}, {id: 'saldo', header: ['Saldo'], width: 125, sort: 'int', format: webix.i18n.priceFormat, css: 'right'}, + {id: 'importe', header: ['Este pago'], width: 125, sort: 'int', + format: webix.i18n.priceFormat, css: 'right'}, ] From b59f62e4f1a8b9edb7bbb0d632da009bc283c1b6 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Wed, 22 Nov 2017 15:28:31 -0600 Subject: [PATCH 10/13] Fix - Uso del CFDI en clientes --- source/app/models/main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/source/app/models/main.py b/source/app/models/main.py index 939223f..b0784dc 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1259,6 +1259,12 @@ class SATUsoCfdi(BaseModel): return return SATUsoCfdi.get(SATUsoCfdi.key==key).id + @classmethod + def get_key(self, id): + if id is None: + return + return SATUsoCfdi.get(SATUsoCfdi.id==id).key + @classmethod def get_activos(cls): rows = (SATUsoCfdi @@ -1361,7 +1367,7 @@ class Socios(BaseModel): if id: id = int(values['id']) row = Socios.select().where(Socios.id==id).dicts()[0] - row['uso_cfdi_socio'] = row.pop('uso_cfdi') + row['uso_cfdi_socio'] = SATUsoCfdi.get_key(row.pop('uso_cfdi')) if not row['condicion_pago'] is None: row['condicion_pago'] = \ str(CondicionesPago.get(id=row['condicion_pago'])) From 0a575afaa9e369a7642133febe7311f0b24034da Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Wed, 22 Nov 2017 15:42:59 -0600 Subject: [PATCH 11/13] Obtener solo por pagar --- source/app/models/main.py | 7 +++++-- source/static/js/controller/bancos.js | 15 +++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/source/app/models/main.py b/source/app/models/main.py index b0784dc..21838bb 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1973,13 +1973,16 @@ class Facturas(BaseModel): return (Facturas.folio.between(folio1, folio2)) - def _get_por_pagar(self): + def _get_por_pagar(self, ids): filtros = ( (Facturas.cancelada==False) & (Facturas.uuid.is_null(False)) & (Facturas.tipo_comprobante=='I') & (Facturas.saldo>0) ) + if ids: + filtros &= (Facturas.id.not_in(ids)) + rows = tuple(Facturas .select( Facturas.id, @@ -2004,7 +2007,7 @@ class Facturas(BaseModel): def _get_opt(self, values): if values['opt'] == 'porpagar': - return self._get_por_pagar(self) + return self._get_por_pagar(self, util.loads(values['ids'])) cfdis = util.loads(values['cfdis']) diff --git a/source/static/js/controller/bancos.js b/source/static/js/controller/bancos.js index 2351d00..ebb32e2 100644 --- a/source/static/js/controller/bancos.js +++ b/source/static/js/controller/bancos.js @@ -112,18 +112,24 @@ function get_bancos_forma_pago(retiro){ function get_facturas_por_pagar(){ - var grid = $$('grid_cfdi_por_pagar') + var grid1 = $$('grid_cfdi_este_deposito') + var grid2 = $$('grid_cfdi_por_pagar') - webix.ajax().get('/invoices', {'opt': 'porpagar'}, { + var ids = [] + grid1.data.each(function(obj){ + ids.push(obj.id) + }) + + webix.ajax().get('/invoices', {'opt': 'porpagar', 'ids': ids}, { 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() + grid2.clearAll() if (values.ok){ - grid.parse(values.rows, 'json') + grid2.parse(values.rows, 'json') } } }) @@ -276,6 +282,7 @@ function actualizar_deposito(grid){ }) $$('txt_deposito_importe').setValue(suma) $$('deposito_descripcion').setValue(descripcion) + grid.refresh() } From 2e9dadf301bc1f754d623e0cc69068f8d3739c86 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Wed, 22 Nov 2017 19:44:26 -0600 Subject: [PATCH 12/13] Validar agregar deposito --- source/static/js/controller/bancos.js | 219 +++++++++++++++++++++++++- source/static/js/ui/bancos.js | 3 +- source/static/js/ui/invoices.js | 4 +- 3 files changed, 220 insertions(+), 6 deletions(-) diff --git a/source/static/js/controller/bancos.js b/source/static/js/controller/bancos.js index ebb32e2..3a89579 100644 --- a/source/static/js/controller/bancos.js +++ b/source/static/js/controller/bancos.js @@ -6,9 +6,13 @@ var bancos_controllers = { $$('cmd_agregar_retiro').attachEvent('onItemClick', cmd_agregar_retiro_click) $$('cmd_agregar_deposito').attachEvent('onItemClick', cmd_agregar_deposito_click) $$('cmd_guardar_retiro').attachEvent('onItemClick', cmd_guardar_retiro_click) + $$('cmd_guardar_deposito').attachEvent('onItemClick', cmd_guardar_deposito_click) $$('txt_retiro_importe').attachEvent('onChange', txt_retiro_importe_change) + $$('txt_deposito_importe').attachEvent('onChange', txt_deposito_importe_change) $$('grid_cfdi_este_deposito').attachEvent('onAfterDrop', grid_cfdi_este_deposito_after_drop) $$('grid_cfdi_por_pagar').attachEvent('onAfterDrop', grid_cfdi_por_pagar_after_drop) + $$('grid_cfdi_este_deposito').attachEvent('onBeforeEditStop', grid_cfdi_este_deposito_before_edit_stop) + $$('grid_cfdi_este_deposito').attachEvent('onAfterEditStop', grid_cfdi_este_deposito_after_edit_stop) set_year_month() } } @@ -262,6 +266,14 @@ function cmd_guardar_retiro_click(){ function txt_retiro_importe_change(new_value, old_value){ + showvar(new_value) + if(!isFinite(new_value)){ + this.config.value = old_value + this.refresh() + } +} + +function txt_deposito_importe_change(new_value, old_value){ if(!isFinite(new_value)){ this.config.value = old_value this.refresh() @@ -275,13 +287,15 @@ function actualizar_deposito(grid){ var suma = 0 var descripcion = '' grid.data.each(function(obj){ - suma += obj.saldo.to_float() descripcion += 'Pago de la factura: ' + obj.serie + obj.folio + ' del ' descripcion += 'cliente: ' + obj.cliente + '\n' - obj.importe = obj.saldo + if(obj.importe == undefined){ + obj.importe = obj.saldo + } + suma += obj.importe.to_float() }) $$('txt_deposito_importe').setValue(suma) - $$('deposito_descripcion').setValue(descripcion) + $$('deposito_descripcion').setValue(descripcion.slice(0, -1)) grid.refresh() } @@ -296,3 +310,202 @@ function grid_cfdi_este_deposito_after_drop(context, native_event){ var grid = $$('grid_cfdi_este_deposito') actualizar_deposito(grid) } + + +function grid_cfdi_este_deposito_after_edit_stop(state, editor, ignoreUpdate){ + var grid = $$('grid_cfdi_este_deposito') + + var suma = 0 + grid.data.each(function(obj){ + suma += obj.importe.to_float() + }) + $$('txt_deposito_importe').setValue(suma) +} + + +function grid_cfdi_este_deposito_before_edit_stop(state, editor){ + var grid = $$('grid_cfdi_este_deposito') + var row = grid.getItem(editor.row) + + if(editor.column == 'importe'){ + var importe = parseFloat(state.value) + if(isNaN(importe)){ + msg = 'El importe a pagar debe ser un número' + msg_error(msg) + grid.blockEvent() + state.value = state.old + grid.editCancel() + grid.unblockEvent() + return true + } + if(importe <= 0){ + msg = 'El importe a pagar debe ser mayor a cero' + msg_error(msg) + grid.blockEvent() + state.value = state.old + grid.editCancel() + grid.unblockEvent() + return true + } + var saldo = row['saldo'].to_float() + if(importe > saldo){ + msg = 'El importe a pagar no puede ser mayor al saldo de la factura' + msg_error(msg) + grid.blockEvent() + state.value = state.old + grid.editCancel() + grid.unblockEvent() + return true + } + } +} + + +function validate_deposito(values){ + var grid = $$('grid_cfdi_este_deposito') + var importe = values.deposito_importe.to_float() + + if(!importe){ + msg = 'El importe es requerido' + msg_error(msg) + return false + } + + if(importe <= 0){ + msg = 'El importe debe ser mayor a cero' + msg_error(msg) + return false + } + + if(!values.deposito_descripcion.trim()){ + msg = 'La descripción es requerida' + msg_error(msg) + return false + } + + var horas = $$('time_deposito').getText().split(':') + var seg = parseInt(horas[2]) + var min = parseInt(horas[1]) + var horas = parseInt(horas[0]) + + if(horas > 23){ + focus('time_deposito') + msg = 'Hora inválida' + msg_error(msg) + return false + } + if(min > 59){ + focus('time_deposito') + msg = 'Hora inválida' + msg_error(msg) + return false + } + if(seg > 59){ + focus('time_deposito') + msg = 'Hora inválida' + msg_error(msg) + return false + } + + if(grid.count()){ + var suma = 0 + grid.data.each(function(obj){ + var tmp = obj.importe.to_float() + if(tmp <= 0){ + msg = 'El importe de la factura: ' + obj.serie + obj.folio + ' no puede ser menor a cero' + msg_error(msg) + return false + } + suma += tmp + }) + if(suma > importe){ + msg = 'La suma del pago de facturas, no puede ser mayor al deposito' + msg_error(msg) + return false + } + } + + return true +} + + +function guardar_deposito(values){ + var form = $$('form_banco_deposito') + var grid = $$('grid_cfdi_este_deposito') + + var data = new Object() + data['cuenta'] = $$('lst_cuentas_banco').getValue() + data['fecha'] = values.retiro_fecha + data['hora'] = $$('time_deposito').getText() + data['numero_operacion'] = values.deposito_referencia.trim() + data['forma_pago'] = $$('lst_deposito_forma_pago').getValue() + data['deposito'] = values.deposito_importe.to_float() + data['retiro'] = 0.0 + data['descripcion'] = values.deposito_descripcion + + webix.ajax().post('/movbanco', data, { + 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){ + $$('txt_cuenta_saldo').setValue(values.saldo) + get_estado_cuenta() + $$('multi_bancos').setValue('banco_home') + form.setValues({}) + grid.clearAll() + }else{ + msg_error(values.msg) + } + } + }) +} + + +function cmd_guardar_deposito_click(){ + var form = $$('form_banco_deposito') + + if(!form.validate()) { + msg_error('Valores inválidos') + return + } + + var values = form.getValues() + if(!validate_deposito(values)){ + return + } + + if(!grid.count()){ + msg = 'Todos los datos son correctos
br>' + msg = 'El depósito no tiene facturas relacionadas

¿Estás ' + msg += ' seguro de guardar del depósito sin facturas relacionadas?' + webix.confirm({ + title: 'Guardar depósito', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if(result){ + guardar_deposito(values) + } + } + }) + }else{ + msg = 'Todos los datos son correctos.

¿Deseas agregar este depósito?' + webix.confirm({ + title: 'Guardar depósito', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if(result){ + guardar_deposito(values) + } + } + }) + } +} diff --git a/source/static/js/ui/bancos.js b/source/static/js/ui/bancos.js index 06b8137..3d05ff1 100644 --- a/source/static/js/ui/bancos.js +++ b/source/static/js/ui/bancos.js @@ -84,7 +84,7 @@ var grid_cfdi_este_deposito_cols = [ {id: 'saldo', header: ['Saldo'], width: 125, sort: 'int', format: webix.i18n.priceFormat, css: 'right'}, {id: 'importe', header: ['Este pago'], width: 125, sort: 'int', - format: webix.i18n.priceFormat, css: 'right'}, + format: webix.i18n.priceFormat, css: 'right', editor: 'text'}, ] @@ -119,6 +119,7 @@ var grid_cfdi_este_deposito = { resizeColumn: true, headermenu: true, drag: true, + editable: true, columns: grid_cfdi_este_deposito_cols, on:{ 'data->onStoreUpdated':function(){ diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index 6df8320..5d94855 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -261,7 +261,7 @@ var grid_details = { autoheight: true, editable: true, columns: grid_details_cols, - data: [] + data: [], } @@ -284,7 +284,7 @@ var grid_totals = { footer: true, autoheight: true, columns: grid_totals_cols, - data: [{id: 1, concepto: 'SubTotal', importe: 0}] + data: [{id: 1, concepto: 'SubTotal', importe: 0}], } From 9cb6dbf0b2570b7b44f3f191baceeb72529b71c9 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Thu, 23 Nov 2017 23:56:03 -0600 Subject: [PATCH 13/13] =?UTF-8?q?Agregar=20dep=C3=B3sitos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/app/controllers/main.py | 7 ++ source/app/models/db.py | 5 + source/app/models/main.py | 165 ++++++++++++++++++-------- source/static/js/controller/bancos.js | 99 +++++++++++++++- source/static/js/ui/bancos.js | 8 ++ 5 files changed, 234 insertions(+), 50 deletions(-) diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 183a479..bc3ac45 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -294,6 +294,13 @@ class AppMovimientosBanco(object): req.context['result'] = self._db.add_movbanco(values) resp.status = falcon.HTTP_200 + def on_delete(self, req, resp): + values = req.params + if self._db.delete('movbanco', values['id']): + resp.status = falcon.HTTP_200 + else: + resp.status = falcon.HTTP_204 + class AppFolios(object): diff --git a/source/app/models/db.py b/source/app/models/db.py index a74fc7e..4b8c81d 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -17,6 +17,9 @@ class StorageEngine(object): def _get_configtimbrar(self, values): return main.config_timbrar() + def _get_saldocuenta(self, values): + return main.CuentasBanco.get_saldo(values['id']) + def _get_validartimbrar(self, values): return main.validar_timbrar() @@ -169,6 +172,8 @@ class StorageEngine(object): return main.SATImpuestos.remove(id) if table == 'cuentasbanco': return main.CuentasBanco.remove(id) + if table == 'movbanco': + return main.MovimientosBanco.remove(id) return False def _get_client(self, values): diff --git a/source/app/models/main.py b/source/app/models/main.py index 21838bb..c927365 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -219,6 +219,28 @@ class Usuarios(BaseModel): order_by = ('nombre', 'apellidos') +class Registro(BaseModel): + usuario = TextField() + accion = TextField(default='') + tabla = TextField(default='') + fecha = DateTimeField(default=util.now) + + def __str__(self): + t = '{} {}-{} ({})' + return t.format(self.usuario, self.accion, self.tabla, self.fecha) + + class Meta: + order_by = ('usuario', 'fecha') + + @classmethod + def add(cls, values): + try: + Registro.create(**values) + return + except: + return + + class SATRegimenes(BaseModel): key = TextField(index=True, unique=True) name = TextField(index=True) @@ -960,6 +982,14 @@ class CuentasBanco(BaseModel): q = CuentasBanco.update(**fields).where(CuentasBanco.id==id) return bool(q.execute()) + @classmethod + def get_saldo(cls, id): + try: + obj = CuentasBanco.get(CuentasBanco.id==id) + return obj.saldo + except CuentasBanco.DoesNotExist: + return 0 + @classmethod def remove(cls, id): try: @@ -1093,6 +1123,8 @@ class MovimientosBanco(BaseModel): auto_round=True) saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) + cancelado = BooleanField(default=False) + conciliado = BooleanField(default=False) moneda = TextField(default='MXN') # Complemento de pagos tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6, auto_round=True) @@ -1115,34 +1147,41 @@ class MovimientosBanco(BaseModel): .select() .where( (MovimientosBanco.cuenta==cuenta) & - (MovimientosBanco.fecharow.fecha)) + (MovimientosBanco.fecha>row.fecha) & + (MovimientosBanco.cancelado==False)) ) - saldo = row.saldo - if row.retiro: - importe = row.retiro * -1 - else: - importe = row.deposito - + saldo = round(Decimal(row.saldo), DECIMALES) for mov in query: - mov.saldo = saldo + importe + mov.saldo = saldo + mov.deposito - mov.retiro mov.save() saldo = mov.saldo - CuentasBanco.actualizar_saldo(None, saldo) + CuentasBanco.actualizar_saldo(row.cuenta, saldo) return saldo @classmethod def add(cls, values): - #~ print(values) + ids = values.pop('ids', '') actualizar = False if not 'saldo' in values: actualizar = True @@ -1167,9 +1206,30 @@ class MovimientosBanco(BaseModel): if actualizar: saldo = cls._actualizar_saldos(cls, obj) + if ids: + FacturasPagos.add(obj, util.loads(ids)) return {'ok': True, 'saldo': saldo} + @classmethod + def remove(cls, id): + try: + obj = MovimientosBanco.get(MovimientosBanco.id==id) + except MovimientosBanco.DoesNotExist: + return False + + if obj.conciliado or obj.cancelado: + return False + + with database_proxy.transaction(): + obj.cancelado = True + obj.save() + + obj = cls._movimiento_anterior(cls, obj.cuenta, obj.fecha) + cls._actualizar_saldos(cls, obj) + + return True + @classmethod def con(cls, id): cant = (MovimientosBanco @@ -1191,7 +1251,10 @@ class MovimientosBanco(BaseModel): fd = (MovimientosBanco.fecha.between( util.get_date(rango['start']), util.get_date(rango['end'], True))) - filtros = (fd & (MovimientosBanco.cuenta==cuenta)) + filtros = (fd & + (MovimientosBanco.cuenta==cuenta) & + (MovimientosBanco.cancelado==False) + ) else: year = int(values['year']) mes = int(values['mes']) @@ -1203,7 +1266,10 @@ class MovimientosBanco(BaseModel): fm = (MovimientosBanco.fecha.month > 0) else: fm = (MovimientosBanco.fecha.month == mes) - filtros = (fy & fm & (MovimientosBanco.cuenta==cuenta)) + filtros = (fy & fm & + (MovimientosBanco.cuenta==cuenta) & + (MovimientosBanco.cancelado==False) + ) rows = tuple(MovimientosBanco .select( @@ -2933,22 +2999,22 @@ class FacturasComplementos(BaseModel): return {r.nombre: util.loads(r.valores) for r in query} -class CfdiPagosFacturas(BaseModel): - pago = ForeignKeyField(CfdiPagos) - factura = ForeignKeyField(Facturas) - numero = IntegerField(default=1) - saldo_anterior = DecimalField(default=0.0, max_digits=20, decimal_places=6, - auto_round=True) - importe = DecimalField(default=0.0, max_digits=18, decimal_places=6, - auto_round=True) - saldo = DecimalField(default=0.0, max_digits=18, decimal_places=6, - auto_round=True) +# ~ class 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 Meta: + # ~ order_by = ('pago',) + # ~ indexes = ( + # ~ (('pago', 'factura', 'numero'), True), + # ~ ) class PreFacturasRelacionadas(BaseModel): @@ -3093,21 +3159,28 @@ 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 FacturasPagos(BaseModel): + movimiento = ForeignKeyField(MovimientosBanco) + 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 Meta: + order_by = ('factura',) + indexes = ( + (('factura', 'numero'), True), + ) + + @classmethod + def add(cls, mov, ids): + print (mov) + print (ids) + return class PreFacturasImpuestos(BaseModel): @@ -3168,10 +3241,8 @@ def authenticate(args): respuesta['msg'] = '' respuesta['login'] = True respuesta['user'] = str(obj) - #~ respuesta['user'] = obj respuesta['super'] = obj.es_superusuario #~ respuesta['admin'] = obj.es_superusuario or obj.es_admin - #~ desconectar() return respuesta, obj @@ -3262,15 +3333,15 @@ def _init_values(rfc): def _crear_tablas(rfc): tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion, - Folios, - Emisor, Facturas, FacturasDetalle, FacturasImpuestos, + Folios, Registro, + Emisor, Facturas, FacturasDetalle, FacturasImpuestos, FacturasPagos, FacturasRelacionadas, FacturasComplementos, Almacenes, Productos, PreFacturas, PreFacturasDetalle, PreFacturasImpuestos, PreFacturasRelacionadas, SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes, SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos, Socios, Tags, Usuarios, CuentasBanco, TipoCambio, MovimientosBanco, - CfdiPagos, CfdiPagosFacturas, + CfdiPagos, Emisor.regimenes.get_through_model(), Socios.tags.get_through_model(), Productos.impuestos.get_through_model(), diff --git a/source/static/js/controller/bancos.js b/source/static/js/controller/bancos.js index 3a89579..836593b 100644 --- a/source/static/js/controller/bancos.js +++ b/source/static/js/controller/bancos.js @@ -1,4 +1,6 @@ var msg = '' +var msg_importe = '' + var bancos_controllers = { init: function(){ @@ -7,6 +9,7 @@ var bancos_controllers = { $$('cmd_agregar_deposito').attachEvent('onItemClick', cmd_agregar_deposito_click) $$('cmd_guardar_retiro').attachEvent('onItemClick', cmd_guardar_retiro_click) $$('cmd_guardar_deposito').attachEvent('onItemClick', cmd_guardar_deposito_click) + $$('cmd_cancelar_movimiento').attachEvent('onItemClick', cmd_cancelar_movimiento_click) $$('txt_retiro_importe').attachEvent('onChange', txt_retiro_importe_change) $$('txt_deposito_importe').attachEvent('onChange', txt_deposito_importe_change) $$('grid_cfdi_este_deposito').attachEvent('onAfterDrop', grid_cfdi_este_deposito_after_drop) @@ -98,6 +101,20 @@ function get_estado_cuenta(rango){ } +function get_saldo_cuenta(){ + var id = $$('lst_cuentas_banco').getValue() + webix.ajax().get('/values/saldocuenta', {id: id}, function(text, data){ + var value = data.json() + if(value){ + $$('txt_cuenta_saldo').setValue(value) + }else{ + msg = 'No se pudo consultar el saldo' + msg_error(msg) + } + }) +} + + function lst_cuentas_banco_change(nv, ov){ show('Cuenta change') } @@ -147,6 +164,7 @@ function cmd_agregar_retiro_click(){ function cmd_agregar_deposito_click(){ + msg_importe = '' get_bancos_forma_pago(false) get_facturas_por_pagar() $$('multi_bancos').setValue('banco_deposito') @@ -174,6 +192,13 @@ function validate_retiro(values){ return false } + var today = new Date() + if(values.retiro_fecha > today){ + msg = 'Fecha inválida, es una fecha futura' + msg_error(msg) + return + } + var horas = $$('time_retiro').getText().split(':') var seg = parseInt(horas[2]) var min = parseInt(horas[1]) @@ -266,7 +291,6 @@ function cmd_guardar_retiro_click(){ function txt_retiro_importe_change(new_value, old_value){ - showvar(new_value) if(!isFinite(new_value)){ this.config.value = old_value this.refresh() @@ -383,6 +407,13 @@ function validate_deposito(values){ return false } + var today = new Date() + if(values.deposito_fecha > today){ + msg = 'Fecha inválida, es una fecha futura' + msg_error(msg) + return + } + var horas = $$('time_deposito').getText().split(':') var seg = parseInt(horas[2]) var min = parseInt(horas[1]) @@ -423,6 +454,10 @@ function validate_deposito(values){ msg_error(msg) return false } + if(suma < importe){ + msg_importe = 'El importe del depósito en mayor a la suma de facturas. ' + msg_importe += 'Asegurate de que esto sea correcto' + } } return true @@ -435,7 +470,7 @@ function guardar_deposito(values){ var data = new Object() data['cuenta'] = $$('lst_cuentas_banco').getValue() - data['fecha'] = values.retiro_fecha + data['fecha'] = values.deposito_fecha data['hora'] = $$('time_deposito').getText() data['numero_operacion'] = values.deposito_referencia.trim() data['forma_pago'] = $$('lst_deposito_forma_pago').getValue() @@ -443,6 +478,14 @@ function guardar_deposito(values){ data['retiro'] = 0.0 data['descripcion'] = values.deposito_descripcion + if(grid.count()){ + var ids = new Object() + grid.data.each(function(obj){ + ids[obj.id] = obj.importe.to_float() + }) + data['ids'] = ids + } + webix.ajax().post('/movbanco', data, { error:function(text, data, XmlHttpRequest){ msg = 'Ocurrio un error, consulta a soporte técnico' @@ -466,6 +509,7 @@ function guardar_deposito(values){ function cmd_guardar_deposito_click(){ var form = $$('form_banco_deposito') + var grid = $$('grid_cfdi_este_deposito') if(!form.validate()) { msg_error('Valores inválidos') @@ -494,7 +538,11 @@ function cmd_guardar_deposito_click(){ } }) }else{ - msg = 'Todos los datos son correctos.

¿Deseas agregar este depósito?' + if(!msg_importe){ + msg_importe = 'Se van a relacionar ' + grid.count() + ' facturas.' + } + msg = 'Todos los datos son correctos.

' + msg_importe + '

' + msg += '¿Deseas agregar este depósito?' webix.confirm({ title: 'Guardar depósito', ok: 'Si', @@ -509,3 +557,48 @@ function cmd_guardar_deposito_click(){ }) } } + + +function cancelar_movimiento(id){ + var grid = $$('grid_cuentabanco') + + webix.ajax().del('/movbanco', {id: id}, function(text, xml, xhr){ + if(xhr.status == 200){ + get_estado_cuenta() + get_saldo_cuenta() + msg_sucess('Movimiento cancelado correctamente') + }else{ + msg_error('No se pudo eliminar') + } + }) +} + + +function cmd_cancelar_movimiento_click(){ + var grid = $$('grid_cuentabanco') + + var row = grid.getSelectedItem() + if(row == undefined){ + msg_error('Selecciona un movimiento') + return + } + if(row.descripcion == 'Saldo inicial'){ + msg_error('No es posible eliminar el saldo inicial') + return + } + + var msg = '¿Estás seguro de cancelar el movimiento seleccionado?' + msg += '

ESTA ACCIÓN NO SE PUEDE DESHACER

' + webix.confirm({ + title:'Cancelar Movimiento', + ok:'Si', + cancel:'No', + type:'confirm-error', + text:msg, + callback:function(result){ + if (result){ + cancelar_movimiento(row['id']) + } + } + }) +} \ No newline at end of file diff --git a/source/static/js/ui/bancos.js b/source/static/js/ui/bancos.js index 3d05ff1..5426d9e 100644 --- a/source/static/js/ui/bancos.js +++ b/source/static/js/ui/bancos.js @@ -17,10 +17,17 @@ var toolbar_filtro_cuenta = [ {view: 'daterangepicker', id: 'filtro_cuenta_fechas', label: 'Fechas', labelAlign: 'right', width: 300}, {}, +] + + +var toolbar_movimientos_banco = [ {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'}, + {}, + {view: 'button', id: 'cmd_cancelar_movimiento', label: 'Cancelar', + type: 'iconButton', autowidth: true, icon: 'ban'}, ] @@ -250,6 +257,7 @@ var multi_bancos = { {id: 'banco_home', rows:[ {view: 'toolbar', elements: toolbar_banco}, {view: 'toolbar', elements: toolbar_filtro_cuenta}, + {view: 'toolbar', elements: toolbar_movimientos_banco}, grid_cuentabanco, ]}, {id: 'banco_retiro', rows: [form_banco_retiro]},