From 2877a68b92e7ad53f4426fdb90d6a0dda86889b0 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 1 Mar 2020 23:18:26 -0600 Subject: [PATCH] Stamp fiscal leyends --- source/app/controllers/cfdi_xml.py | 26 +++++- source/app/controllers/main.py | 23 +++++ source/app/main.py | 3 +- source/app/models/db.py | 9 ++ source/app/models/main.py | 112 +++++++++++++++++++++++- source/static/js/controller/admin.js | 62 ++++++++++++- source/static/js/controller/invoices.js | 37 ++++++++ source/static/js/ui/admin.js | 16 ++-- source/static/js/ui/invoices.js | 35 +++++++- 9 files changed, 303 insertions(+), 20 deletions(-) diff --git a/source/app/controllers/cfdi_xml.py b/source/app/controllers/cfdi_xml.py index 7054bbe..11fea61 100644 --- a/source/app/controllers/cfdi_xml.py +++ b/source/app/controllers/cfdi_xml.py @@ -90,6 +90,12 @@ SAT = { 'xmlns': 'http://www.sat.gob.mx/divisas', 'schema': ' http://www.sat.gob.mx/divisas http://www.sat.gob.mx/sitio_internet/cfd/divisas/divisas.xsd', }, + 'leyendas': { + 'version': '1.0', + 'prefix': 'leyendasFisc', + 'xmlns': 'http://www.sat.gob.mx/leyendasFiscales', + 'schema': ' http://www.sat.gob.mx/leyendasFiscales http://www.sat.gob.mx/sitio_internet/cfd/leyendasFiscales/leyendasFisc.xsd', + }, } @@ -107,6 +113,7 @@ class CFDI(object): self._edu = False self._pagos = False self._is_nomina = False + self._leyendas = False self._divisas = '' self.error = '' @@ -129,8 +136,6 @@ class CFDI(object): if 'nomina' in datos: self._nomina(datos['nomina']) - #~ if 'complementos' in datos: - #~ self._complementos(datos['complementos']) return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8')) @@ -158,6 +163,7 @@ class CFDI(object): if 'ine' in datos['complementos']: self._ine = True self._pagos = bool(datos['complementos'].get('pagos', False)) + self._leyendas = bool(datos['complementos'].get('leyendas', False)) self._divisas = datos['comprobante'].pop('divisas', '') @@ -217,9 +223,15 @@ class CFDI(object): attributes[name] = SAT['pagos']['xmlns'] schema_pagos = SAT['pagos']['schema'] + schema_leyendas = '' + if self._leyendas: + name = 'xmlns:{}'.format(SAT['leyendas']['prefix']) + attributes[name] = SAT['leyendas']['xmlns'] + schema_leyendas = SAT['leyendas']['schema'] + attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \ schema_locales + schema_donativo + schema_ine + schema_edu + \ - schema_divisas + schema_nomina + schema_pagos + schema_divisas + schema_nomina + schema_pagos + schema_leyendas attributes.update(datos) if not 'Version' in attributes: @@ -464,6 +476,14 @@ class CFDI(object): for row in relacionados: ET.SubElement(node_pago, '{}:DoctoRelacionado'.format(pre), row) + if 'leyendas' in datos: + pre = SAT['leyendas']['prefix'] + attributes = {'version': SAT['leyendas']['version']} + node_leyend = ET.SubElement( + self._complemento, '{}:LeyendasFiscales'.format(pre), attributes) + for leyend in datos['leyendas']: + ET.SubElement(node_leyend, '{}:Leyenda'.format(pre), leyend) + if 'ce' in datos: pre = 'cce11' datos = datos.pop('ce') diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 41a8e3e..3efcfc2 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -594,6 +594,29 @@ class AppSATFormaPago(object): resp.status = falcon.HTTP_200 +class AppSATLeyendaFiscales(object): + + def __init__(self, db): + self._db = db + + def on_get(self, req, resp): + values = req.params + req.context['result'] = self._db.sat_leyendas_fiscales_get(values) + resp.status = falcon.HTTP_200 + + def on_post(self, req, resp): + values = req.params + req.context['result'] = self._db.sat_leyendas_fiscales_post(values) + resp.status = falcon.HTTP_200 + + def on_delete(self, req, resp): + values = req.params + if self._db.sat_leyendas_fiscales_delete(values['id']): + resp.status = falcon.HTTP_200 + else: + resp.status = falcon.HTTP_204 + + class AppSociosCuentasBanco(object): def __init__(self, db): diff --git a/source/app/main.py b/source/app/main.py index e7afd80..c03b858 100644 --- a/source/app/main.py +++ b/source/app/main.py @@ -17,7 +17,7 @@ from controllers.main import (AppEmpresas, AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco, AppMovimientosBanco, AppTickets, AppStudents, AppEmployees, AppNomina, AppInvoicePay, AppCfdiPay, AppSATBancos, AppSociosCuentasBanco, - AppSATFormaPago + AppSATFormaPago, AppSATLeyendaFiscales ) @@ -61,6 +61,7 @@ api.add_route('/cfdipay', AppCfdiPay(db)) api.add_route('/satbancos', AppSATBancos(db)) api.add_route('/satformapago', AppSATFormaPago(db)) api.add_route('/socioscb', AppSociosCuentasBanco(db)) +api.add_route('/leyendasfiscales', AppSATLeyendaFiscales(db)) session_options = { diff --git a/source/app/models/db.py b/source/app/models/db.py index cff00e8..7d50926 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -462,6 +462,15 @@ class StorageEngine(object): def nomina(self, values, user): return main.CfdiNomina.post(values, user) + def sat_leyendas_fiscales_get(self, values): + return main.SATLeyendasFiscales.get_values(values) + + def sat_leyendas_fiscales_post(self, values): + return main.SATLeyendasFiscales.post(values) + + def sat_leyendas_fiscales_delete(self, values): + return main.SATLeyendasFiscales.remove(values) + # Companies only in MV def _get_empresas(self, values): return main.companies_get() diff --git a/source/app/models/main.py b/source/app/models/main.py index fbfd1ac..b8f062c 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -308,6 +308,7 @@ def config_timbrar(): 'cfdi_add_same_product': Configuracion.get_bool('chk_config_add_same_product'), 'cfdi_tax_locales_truncate': Configuracion.get_bool('chk_config_tax_locales_truncate'), 'cfdi_folio_custom': Configuracion.get_bool('chk_folio_custom'), + 'cfdi_leyendasfiscales': Configuracion.get_bool('chk_config_leyendas_fiscales'), } return conf @@ -2479,6 +2480,83 @@ class SATRiesgoPuesto(BaseModel): return +class SATLeyendasFiscales(BaseModel): + texto_leyenda = TextField(index=True) + norma = TextField(default='') + disposicion_fiscal = TextField(default='') + active = BooleanField(default=True) + default = BooleanField(default=False) + + class Meta: + order_by = ('texto_leyenda',) + indexes = ( + (('texto_leyenda', 'norma', 'disposicion_fiscal'), True), + ) + + def _get_all(self, values): + rows = (SATLeyendasFiscales.select( + SATLeyendasFiscales.id, + SQL(" '-' AS delete"), + SATLeyendasFiscales.texto_leyenda, + SATLeyendasFiscales.norma, + SATLeyendasFiscales.disposicion_fiscal) + .dicts() + ) + return tuple(rows) + + def _get_active(self, values): + rows = (SATLeyendasFiscales.select( + SATLeyendasFiscales.id, + SATLeyendasFiscales.texto_leyenda, + SATLeyendasFiscales.norma, + SATLeyendasFiscales.disposicion_fiscal) + .where(SATLeyendasFiscales.active==True) + .dicts() + ) + return tuple(rows) + + @classmethod + def get_by_id(cls, ids): + rows = (SATLeyendasFiscales.select( + SATLeyendasFiscales.texto_leyenda.alias('textoLeyenda'), + SATLeyendasFiscales.norma, + SATLeyendasFiscales.disposicion_fiscal.alias('disposicionFiscal')) + .where(SATLeyendasFiscales.id.in_(ids)) + .dicts() + ) + for row in rows: + if not row['norma']: + del row['norma'] + if not row['disposicionFiscal']: + del row['disposicionFiscal'] + return tuple(rows) + + @classmethod + def get_values(cls, values): + opt = values.pop('opt') + return getattr(cls, '_get_{}'.format(opt))(cls, values) + + def _new(self, values): + try: + obj = SATLeyendasFiscales.create(**values) + values['id'] = obj.id + values['delete'] = '-' + result = {'ok': True, 'row': values} + except Exception as e: + result = {'ok': False, 'msg': 'La Leyenda Fiscal ya existe'} + return result + + @classmethod + def post(cls, values): + opt = values.pop('opt') + return getattr(cls, '_{}'.format(opt))(cls, values) + + @classmethod + def remove(cls, id): + q = SATLeyendasFiscales.delete().where(SATLeyendasFiscales.id==id) + return bool(q.execute()) + + class TipoCambio(BaseModel): dia = DateField(default=util.now) moneda = ForeignKeyField(SATMonedas) @@ -4642,6 +4720,8 @@ class Facturas(BaseModel): divisas = '' values['divisas'] = divisas + leyendas_fiscales = utils.loads(values.pop('leyendas_fiscales', '[]')) + emisor = Emisor.select()[0] values['serie'] = cls._get_serie(cls, user, values['serie']) if Configuracion.get_bool('chk_folio_custom') and folio_custom: @@ -4666,6 +4746,7 @@ class Facturas(BaseModel): totals = cls._calculate_totals(cls, obj, productos, tipo_comprobante) cls._guardar_relacionados(cls, obj, relacionados) cls._guardar_ine(cls, obj, ine) + cls._save_leyendas_fiscales(cls, obj, leyendas_fiscales) obj.subtotal = totals['subtotal'] obj.descuento = totals['descuento'] obj.total_trasladados = totals['total_trasladados'] @@ -4696,6 +4777,18 @@ class Facturas(BaseModel): data = {'ok': True, 'row': row, 'new': True, 'error': False, 'msg': msg} return data + def _save_leyendas_fiscales(self, invoice, valores): + if not valores: + return + + data = { + 'factura': invoice, + 'nombre': 'leyendas', + 'valores': utils.dumps(valores), + } + FacturasComplementos.create(**data) + return + def _make_xml(self, invoice, auth): tax_decimals = Configuracion.get_bool('chk_config_tax_decimals') decimales_precios = Configuracion.get_bool('chk_config_decimales_precios') @@ -4710,11 +4803,15 @@ class Facturas(BaseModel): comprobante = {} relacionados = {} donativo = {} - complementos = FacturasComplementos.get_(invoice) + complementos = FacturasComplementos.get_by_invoice(invoice) comprobante['divisas'] = invoice.divisas if invoice.divisas: complementos['divisas'] = True + if 'leyendas' in complementos: + ids = complementos['leyendas'] + complementos['leyendas'] = SATLeyendasFiscales.get_by_id(ids) + if invoice.donativo: donativo['noAutorizacion'] = emisor.autorizacion donativo['fechaAutorizacion'] = str(emisor.fecha_autorizacion) @@ -5855,6 +5952,14 @@ class FacturasComplementos(BaseModel): ) return {r.nombre: utils.loads(r.valores) for r in query} + @classmethod + def get_by_invoice(cls, invoice): + query = (FacturasComplementos + .select() + .where(FacturasComplementos.factura==invoice) + ) + return {r.nombre: utils.loads(r.valores) for r in query} + class PreFacturasRelacionadas(BaseModel): factura = ForeignKeyField(PreFacturas, related_name='original') @@ -9082,7 +9187,7 @@ def _crear_tablas(rfc): SATNivelesEducativos, SATEstados, SATRiesgoPuesto, SATPeriodicidadPago, SATOrigenRecurso, SATTipoContrato, SATTipoDeduccion, SATTipoHoras, SATTipoIncapacidad, SATTipoJornada, SATTipoNomina, SATTipoOtroPago, - SATTipoPercepcion, SATTipoRegimen, + SATTipoPercepcion, SATTipoRegimen, SATLeyendasFiscales, Socios, SociosCuentasBanco, Contactos, ContactoCorreos, ContactoDirecciones, Empleados, ContactoTelefonos, Departamentos, Puestos, @@ -9152,7 +9257,7 @@ def _migrate_tables(rfc=''): CfdiNominaOtroPago, CfdiNominaOtros, CfdiNominaPercepciones, CfdiNominaRelacionados, CfdiNominaSeparacion, CfdiNominaSubcontratos, CfdiNominaTotales, SATNivelesEducativos, Roles, Permisos, - SociosCuentasBanco + SociosCuentasBanco, SATLeyendasFiscales ] log.info('Creando tablas nuevas...') database_proxy.create_tables(tablas, True) @@ -10376,6 +10481,5 @@ def main(args): if __name__ == '__main__': args = _process_command_line_arguments() main(args) - # ~ main2() diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 7d21936..310da10 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -2605,8 +2605,53 @@ function cmd_admin_leyendas_fiscales_click(){ } +function get_leyendas_fiscales(){ + webix.ajax().sync().get('/leyendasfiscales', {'opt': 'all'}, { + 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() + var grid = $$('grid_admin_leyendas_fiscales') + grid.clearAll() + grid.parse(values) + grid.refresh() + } + }) +} + + function add_admin_leyenda_fiscal_click(){ - msg_ok('Add') + var form = $$('form_admin_leyendas_fiscales') + var grid = $$('grid_admin_leyendas_fiscales') + + if (!form.validate()){ + msg = 'Valores inválidos' + msg_error(msg) + return + } + var values = form.getValues() + values['opt'] = 'new' + + var empty = {texto_leyenda: '', norma: '', disposicion_fiscal: ''} + + webix.ajax().post('/leyendasfiscales', values, { + 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){ + msg_ok('Leyenda guardada correctamente') + form.setValues(empty) + grid.add(values.row) + }else{ + msg_error(values.msg) + } + } + }) } @@ -2630,3 +2675,18 @@ function grid_admin_leyendas_fiscales_click(id){ }) } + +function delete_leyenda_fiscal(id){ + var grid = $$('grid_admin_leyendas_fiscales') + + webix.ajax().del('/leyendasfiscales', {id: id}, function(text, xml, xhr){ + msg = 'Leyenda Fiscal eliminada correctamente' + if(xhr.status == 200){ + grid.remove(id) + msg_ok(msg) + }else{ + msg = 'No se pudo eliminar' + msg_error(msg) + } + }) +} diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 1983bcd..a5ccf61 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -205,6 +205,13 @@ function default_config(){ }else{ $$('tv_invoice').getTabbar().showOption('INE') } + if(!values.cfdi_leyendasfiscales){ + $$('tv_invoice').getTabbar().hideOption('Leyendas Fiscales') + }else{ + get_leyendas_fiscales() + $$('tv_invoice').getTabbar().showOption('Leyendas Fiscales') + } + cfg_invoice['leyendasfiscales'] = values.cfdi_leyendasfiscales cfg_invoice['edu'] = values.cfdi_edu cfg_invoice['open_pdf'] = values.cfdi_open_pdf cfg_invoice['tax_locales'] = values.cfdi_tax_locales @@ -642,6 +649,8 @@ function guardar_y_timbrar(values){ data['notas'] = values.notas data['folio_custom'] = $$('txt_folio_custom').getValue() data['divisas'] = $$('opt_divisas').getValue() + data['leyendas_fiscales'] = $$('grid_leyendas_fiscales').getSelectedId(true, true) + $$('grid_leyendas_fiscales').unselectAll() var usar_ine = $$('chk_cfdi_usar_ine').getValue() if(usar_ine){ @@ -709,6 +718,17 @@ function cmd_timbrar_click(id, e, node){ msg += 'El Tipo de Comprobante es Traslado, el total será puesto a 0 (Cero), asegurate de que sea el tipo de comprobante correcto

' } + if(cfg_invoice['leyendasfiscales']){ + var rows = $$('grid_leyendas_fiscales').getSelectedId(true, true) + if(rows.length == 0){ + msg += 'No se agregará ninguna leyenda fiscal

' + }else if(rows.length == 1){ + msg += 'Se agregará una leyenda fiscal

' + }else{ + msg += 'Se agregarán ' + rows.length + ' leyendas fiscales

' + } + } + msg += '¿Estás seguro de timbrar esta factura?

' webix.confirm({ @@ -2352,3 +2372,20 @@ function search_by_click(){ search_by(value) } } + + +function get_leyendas_fiscales(){ + webix.ajax().get('/leyendasfiscales', {'opt': 'active'}, { + 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() + var grid = $$('grid_leyendas_fiscales') + grid.clearAll() + grid.parse(values) + grid.refresh() + } + }) +} diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index 84cc8e6..43d6f5b 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -1428,9 +1428,9 @@ var body_win_emisor_logo = [ var grid_cols_admin_leyendas_fiscales = [ {id: 'id', header: 'ID', hidden: true}, {id: 'delete', header: '', width: 30, css: 'delete'}, - {id: 'leyenda', header: 'Leyenda', fillspace: 1}, + {id: 'texto_leyenda', header: 'Leyenda', fillspace: 1}, {id: 'norma', header: 'Norma', fillspace: 1}, - {id: 'disposicion', header: 'Disposición Fiscal', fillspace: 1}, + {id: 'disposicion_fiscal', header: 'Disposición Fiscal', fillspace: 1}, //~ {id: 'active', header: 'Activa', template: '{common.checkbox()}', //~ editor: 'checkbox'}, ] @@ -1439,7 +1439,7 @@ var grid_cols_admin_leyendas_fiscales = [ var grid_admin_leyendas_fiscales = { view: 'datatable', id: 'grid_admin_leyendas_fiscales', - select: 'cell', + select: 'row', adjust: true, autoheight: true, headermenu: true, @@ -1456,15 +1456,15 @@ var grid_admin_leyendas_fiscales = { var form_controls_admin_leyendas_fiscales = [ {cols: [ - {view: 'text', id: 'txt_leyenda_fiscal', label: 'Leyenda', width: 200, - labelPosition: 'top', name: 'txt_leyenda_fiscal', required: true}, + {view: 'text', id: 'txt_texto_leyenda', label: 'Leyenda', width: 200, + labelPosition: 'top', name: 'texto_leyenda', required: true}, {view: "text", id: 'txt_norma_fiscal', label: 'Norma', width: 200, - name: 'txt_leyenda_fiscal', labelPosition: 'top'}, + name: 'norma', labelPosition: 'top'}, {view: "text", id: 'txt_disposicion_fiscal', label: 'Disposición', - name: 'txt_disposicion_fiscal', labelPosition: 'top', width: 200} + name: 'disposicion_fiscal', labelPosition: 'top', width: 200} ]}, {cols: [{}, - {view: 'button', type: 'icon', icon: 'plus', label: 'Agregar', autowidth: true, + {view: 'button', type: 'form', icon: 'plus', label: 'Agregar', autowidth: true, click: function(){add_admin_leyenda_fiscal_click()} }, {}]}, grid_admin_leyendas_fiscales, diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index 9ff88b3..8e52024 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -701,7 +701,7 @@ var opt_tipo_comite = [ ] -var controles_ine = [ +var controls_ine = [ {maxHeight: 15}, {cols: [{maxWidth: 15}, {view: 'checkbox', id: 'chk_cfdi_usar_ine', labelWidth: 0, @@ -727,16 +727,45 @@ var controles_ine = [ ] +var grid_cols_leyendas_fiscales = [ + {id: 'id', header: 'ID', hidden: true}, + {id: 'texto_leyenda', header: 'Leyenda', fillspace: 2}, + {id: 'norma', header: 'Norma', fillspace: 1}, + {id: 'disposicion_fiscal', header: 'Disposición Fiscal', fillspace: 1}, +] + + +var grid_leyendas_fiscales = { + view: 'datatable', + id: 'grid_leyendas_fiscales', + select: 'row', + multiselect: true, + adjust: true, + autoheight: true, + headermenu: true, + columns: grid_cols_leyendas_fiscales, +} + +var controls_leyendas_fiscales = [ + {maxHeight: 15}, + {cols: [{maxWidth: 15}, + {view: 'label', id: 'lbl_title', label: 'Selecciona las Leyendas Fiscales a integrar en esta factura'}, + {}]}, + {maxHeight: 15}, + grid_leyendas_fiscales, +] + + var controls_invoices = [ { view: 'tabview', id: 'tv_invoice', - //~ tabbar: {options: ['Generar', 'PreFacturas', 'INE']}, animate: true, cells: [ {id: 'Generar', rows: controls_generate}, {id: 'PreFacturas', rows: controls_prefactura}, - {id: 'INE', rows: controles_ine}, + {id: 'INE', rows: controls_ine}, + {id: 'Leyendas Fiscales', rows: controls_leyendas_fiscales}, ] }, ]