From 423937f1cac13cc48d40887a7166148f44519ef8 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sat, 11 Nov 2017 20:21:00 -0600 Subject: [PATCH 1/9] Agregar SAT Bancos --- source/app/models/db.py | 6 ++ source/app/models/main.py | 66 +++++++++++++++++-- source/app/settings.py | 1 + source/db/valores_iniciales.json | 98 ++++++++++++++++++++++++++++ source/static/js/controller/admin.js | 33 +++++++++- source/static/js/ui/admin.js | 38 ++++++++++- 6 files changed, 231 insertions(+), 11 deletions(-) diff --git a/source/app/models/db.py b/source/app/models/db.py index b7db743..ad2cf08 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -91,12 +91,18 @@ class StorageEngine(object): def _get_allcurrencies(self, values): return main.SATMonedas.get_() + def _get_allbancos(self, values): + return main.SATBancos.get_() + def _get_taxupdate(self, values): return main.SATImpuestos.actualizar(values) def _get_currencyupdate(self, values): return main.SATMonedas.actualizar(values) + def _get_bancoupdate(self, values): + return main.SATBancos.actualizar(values) + def _get_satkey(self, values): return main.get_sat_key(values['key']) diff --git a/source/app/models/main.py b/source/app/models/main.py index 2a353a1..e3b11a1 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -15,7 +15,7 @@ if __name__ == '__main__': from controllers import util from settings import log, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, \ - INIT_VALUES + INIT_VALUES, DEFAULT_PASSWORD FORMAT = '{0:.2f}' @@ -756,6 +756,60 @@ class SATTipoRelacion(BaseModel): +class SATBancos(BaseModel): + key = TextField(index=True, unique=True) + name = TextField(index=True) + razon_social = TextField(default='') + rfc = TextField(default='') + activo = BooleanField(default=False) + + class Meta: + order_by = ('-activo', 'name') + indexes = ( + (('key', 'name'), True), + ) + + def __str__(self): + return 'Banco: {} ({})'.format(self.name, self.key) + + @classmethod + def get_(self): + rows = SATBancos.select().dicts() + return tuple(rows) + + @classmethod + def actualizar(self, values): + id = int(values['id']) + if values['field'] == 'activo': + v = {'0': False, '1': True} + q = (SATBancos + .update(**{'activo': v[values['value']]}) + .where(SATBancos.id==id)) + result = bool(q.execute()) + return {'ok': result} + + +class CuentasBanco(BaseModel): + de_emisor = BooleanField(default=False) + nombre = TextField() + banco = ForeignKeyField(SATBancos) + fecha_apertura = DateField(null=True) + cuenta = TextField(default='') + clabe = TextField(default='') + moneda = ForeignKeyField(SATMonedas) + saldo_inicial = DecimalField(default=0.0, max_digits=20, decimal_places=6, + auto_round=True) + + class Meta: + order_by = ('nombre',) + indexes = ( + (('banco', 'cuenta'), True), + ) + + def __str__(self): + return '{} ({})'.format(self.banco.name, self.cuenta[-4:]) + + class SATUsoCfdi(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) @@ -2450,8 +2504,8 @@ def _crear_tablas(rfc): PreFacturas, PreFacturasDetalle, PreFacturasImpuestos, PreFacturasRelacionadas, SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes, - SATTipoRelacion, SATUnidades, SATUsoCfdi, - Socios, Tags, Usuarios, + SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos, + Socios, Tags, Usuarios, CuentasBanco, Emisor.regimenes.get_through_model(), Socios.tags.get_through_model(), Productos.impuestos.get_through_model(), @@ -2461,10 +2515,10 @@ def _crear_tablas(rfc): database_proxy.create_tables(tablas, True) log.info('Tablas creadas correctamente...') - contraseña = 'blades3.3' usuarios = ( - {'usuario': 'superadmin', 'contraseña': contraseña, 'es_superusuario': True}, - {'usuario': 'admin', 'contraseña': contraseña, 'es_admin': True}, + {'usuario': 'superadmin', 'contraseña': DEFAULT_PASSWORD, + 'es_superusuario': True}, + {'usuario': 'admin', 'contraseña': DEFAULT_PASSWORD, 'es_admin': True}, ) for usuario in usuarios: diff --git a/source/app/settings.py b/source/app/settings.py index bc7eade..7880a4c 100644 --- a/source/app/settings.py +++ b/source/app/settings.py @@ -91,3 +91,4 @@ PRE = { } CURRENT_CFDI = '3.3' +DEFAULT_PASSWORD = 'blades3.3' diff --git a/source/db/valores_iniciales.json b/source/db/valores_iniciales.json index c66fe65..57ffeba 100644 --- a/source/db/valores_iniciales.json +++ b/source/db/valores_iniciales.json @@ -99,6 +99,104 @@ {"key": "615", "name": "Régimen de los ingresos por obtención de premios", "fisica": true, "activo": false} ] }, +{ + "tabla": "SATBancos", + "datos": [ + {"key": "002", "name": "BANAMEX", "razon_social": "Banco Nacional de México, S.A., Institución de Banca Múltiple, Grupo Financiero Banamex"}, + {"key": "006", "name": "BANCOMEXT", "razon_social": "Banco Nacional de Comercio Exterior, Sociedad Nacional de Crédito, Institución de Banca de Desarrollo"}, + {"key": "009", "name": "BANOBRAS", "razon_social": "Banco Nacional de Obras y Servicios Públicos, Sociedad Nacional de Crédito, Institución de Banca de Desarrollo"}, + {"key": "012", "name": "BBVABANCOMER", "razon_social": "BBVA Bancomer, S.A., Institución de Banca Múltiple, Grupo Financiero BBVA Bancomer"}, + {"key": "014", "name": "SANTANDER", "razon_social": "Banco Santander (México), S.A., Institución de Banca Múltiple, Grupo Financiero Santander"}, + {"key": "019", "name": "BANJERCITO", "razon_social": "Banco Nacional del Ejército, Fuerza Aérea y Armada, Sociedad Nacional de Crédito, Institución de Banca de Desarrollo"}, + {"key": "021", "name": "HSBC", "razon_social": "HSBC México, S.A., institución De Banca Múltiple, Grupo Financiero HSBC"}, + {"key": "030", "name": "BAJIO", "razon_social": "Banco del Bajío, S.A., Institución de Banca Múltiple"}, + {"key": "106", "name": "BAMSA", "razon_social": "Bank of America México, S.A., Institución de Banca Múltiple, Grupo Financiero Bank of America"}, + {"key": "032", "name": "IXE", "razon_social": "IXE Banco, S.A., Institución de Banca Múltiple, IXE Grupo Financiero"}, + {"key": "036", "name": "INBURSA", "razon_social": "Banco Inbursa, S.A., Institución de Banca Múltiple, Grupo Financiero Inbursa"}, + {"key": "037", "name": "INTERACCIONES", "razon_social": "Banco Interacciones, S.A., Institución de Banca Múltiple"}, + {"key": "042", "name": "MIFEL", "razon_social": "Banca Mifel, S.A., Institución de Banca Múltiple, Grupo Financiero Mifel"}, + {"key": "044", "name": "SCOTIABANK", "razon_social": "Scotiabank Inverlat, S.A."}, + {"key": "059", "name": "INVEX", "razon_social": "Banco Invex, S.A., Institución de Banca Múltiple, Invex Grupo Financiero"}, + {"key": "058", "name": "BANREGIO", "razon_social": "Banco Regional de Monterrey, S.A., Institución de Banca Múltiple, Banregio Grupo Financiero"}, + {"key": "060", "name": "BANSI", "razon_social": "Bansi, S.A., Institución de Banca Múltiple"}, + {"key": "062", "name": "AFIRME", "razon_social": "Banca Afirme, S.A., Institución de Banca Múltiple"}, + {"key": "072", "name": "BANORTE", "razon_social": "Banco Mercantil del Norte, S.A., Institución de Banca Múltiple, Grupo Financiero Banorte"}, + {"key": "102", "name": "THE ROYAL BANK", "razon_social": "The Royal Bank of Scotland México, S.A., Institución de Banca Múltiple"}, + {"key": "103", "name": "AMERICAN EXPRESS", "razon_social": "American Express Bank (México), S.A., Institución de Banca Múltiple"}, + {"key": "108", "name": "TOKYO", "razon_social": "Bank of Tokyo-Mitsubishi UFJ (México), S.A."}, + {"key": "110", "name": "JP MORGAN", "razon_social": "Banco J.P. Morgan, S.A., Institución de Banca Múltiple, J.P. Morgan Grupo Financiero"}, + {"key": "112", "name": "BMONEX", "razon_social": "Banco Monex, S.A., Institución de Banca Múltiple"}, + {"key": "113", "name": "VE POR MAS", "razon_social": "Banco Ve Por Mas, S.A. Institución de Banca Múltiple"}, + {"key": "116", "name": "ING", "razon_social": "ING Bank (México), S.A., Institución de Banca Múltiple, ING Grupo Financiero"}, + {"key": "124", "name": "DEUTSCHE", "razon_social": "Deutsche Bank México, S.A., Institución de Banca Múltiple"}, + {"key": "126", "name": "CREDIT SUISSE", "razon_social": "Banco Credit Suisse (México), S.A. Institución de Banca Múltiple, Grupo Financiero Credit Suisse (México)"}, + {"key": "127", "name": "AZTECA", "razon_social": "Banco Azteca, S.A. Institución de Banca Múltiple."}, + {"key": "128", "name": "AUTOFIN", "razon_social": "Banco Autofin México, S.A. Institución de Banca Múltiple"}, + {"key": "129", "name": "BARCLAYS", "razon_social": "Barclays Bank México, S.A., Institución de Banca Múltiple, Grupo Financiero Barclays México"}, + {"key": "130", "name": "COMPARTAMOS", "razon_social": "Banco Compartamos, S.A., Institución de Banca Múltiple"}, + {"key": "131", "name": "BANCO FAMSA", "razon_social": "Banco Ahorro Famsa, S.A., Institución de Banca Múltiple"}, + {"key": "132", "name": "BMULTIVA", "razon_social": "Banco Multiva, S.A., Institución de Banca Múltiple, Multivalores Grupo Financiero"}, + {"key": "133", "name": "ACTINVER", "razon_social": "Banco Actinver, S.A. Institución de Banca Múltiple, Grupo Financiero Actinver"}, + {"key": "134", "name": "WAL-MART", "razon_social": "Banco Wal-Mart de México Adelante, S.A., Institución de Banca Múltiple"}, + {"key": "135", "name": "NAFIN", "razon_social": "Nacional Financiera, Sociedad Nacional de Crédito, Institución de Banca de Desarrollo"}, + {"key": "136", "name": "INTERBANCO", "razon_social": "Inter Banco, S.A. Institución de Banca Múltiple"}, + {"key": "137", "name": "BANCOPPEL", "razon_social": "BanCoppel, S.A., Institución de Banca Múltiple"}, + {"key": "138", "name": "ABC CAPITAL", "razon_social": "ABC Capital, S.A., Institución de Banca Múltiple"}, + {"key": "139", "name": "UBS BANK", "razon_social": "UBS Bank México, S.A., Institución de Banca Múltiple, UBS Grupo Financiero"}, + {"key": "140", "name": "CONSUBANCO", "razon_social": "Consubanco, S.A. Institución de Banca Múltiple"}, + {"key": "141", "name": "VOLKSWAGEN", "razon_social": "Volkswagen Bank, S.A., Institución de Banca Múltiple"}, + {"key": "143", "name": "CIBANCO", "razon_social": "CIBanco, S.A."}, + {"key": "145", "name": "BBASE", "razon_social": "Banco Base, S.A., Institución de Banca Múltiple"}, + {"key": "166", "name": "BANSEFI", "razon_social": "Banco del Ahorro Nacional y Servicios Financieros, Sociedad Nacional de Crédito, Institución de Banca de Desarrollo"}, + {"key": "168", "name": "HIPOTECARIA FEDERAL", "razon_social": "Sociedad Hipotecaria Federal, Sociedad Nacional de Crédito, Institución de Banca de Desarrollo"}, + {"key": "600", "name": "MONEXCB", "razon_social": "Monex Casa de Bolsa, S.A. de C.V. Monex Grupo Financiero"}, + {"key": "601", "name": "GBM", "razon_social": "GBM Grupo Bursátil Mexicano, S.A. de C.V. Casa de Bolsa"}, + {"key": "602", "name": "MASARI", "razon_social": "Masari Casa de Bolsa, S.A."}, + {"key": "605", "name": "VALUE", "razon_social": "Value, S.A. de C.V. Casa de Bolsa"}, + {"key": "606", "name": "ESTRUCTURADORES", "razon_social": "Estructuradores del Mercado de Valores Casa de Bolsa, S.A. de C.V."}, + {"key": "607", "name": "TIBER", "razon_social": "Casa de Cambio Tiber, S.A. de C.V."}, + {"key": "608", "name": "VECTOR", "razon_social": "Vector Casa de Bolsa, S.A. de C.V."}, + {"key": "610", "name": "B&B", "razon_social": "B y B, Casa de Cambio, S.A. de C.V."}, + {"key": "614", "name": "ACCIVAL", "razon_social": "Acciones y Valores Banamex, S.A. de C.V., Casa de Bolsa"}, + {"key": "615", "name": "MERRILL LYNCH", "razon_social": "Merrill Lynch México, S.A. de C.V. Casa de Bolsa"}, + {"key": "616", "name": "FINAMEX", "razon_social": "Casa de Bolsa Finamex, S.A. de C.V."}, + {"key": "617", "name": "VALMEX", "razon_social": "Valores Mexicanos Casa de Bolsa, S.A. de C.V."}, + {"key": "618", "name": "UNICA", "razon_social": "Unica Casa de Cambio, S.A. de C.V."}, + {"key": "619", "name": "MAPFRE", "razon_social": "MAPFRE Tepeyac, S.A."}, + {"key": "620", "name": "PROFUTURO", "razon_social": "Profuturo G.N.P., S.A. de C.V., Afore"}, + {"key": "621", "name": "CB ACTINVER", "razon_social": "Actinver Casa de Bolsa, S.A. de C.V."}, + {"key": "622", "name": "OACTIN", "razon_social": "OPERADORA ACTINVER, S.A. DE C.V."}, + {"key": "623", "name": "SKANDIA", "razon_social": "Skandia Vida, S.A. de C.V."}, + {"key": "626", "name": "CBDEUTSCHE", "razon_social": "Deutsche Securities, S.A. de C.V. CASA DE BOLSA"}, + {"key": "627", "name": "ZURICH", "razon_social": "Zurich Compañía de Seguros, S.A."}, + {"key": "628", "name": "ZURICHVI", "razon_social": "Zurich Vida, Compañía de Seguros, S.A."}, + {"key": "629", "name": "SU CASITA", "razon_social": "Hipotecaria Su Casita, S.A. de C.V. SOFOM ENR"}, + {"key": "630", "name": "CB INTERCAM", "razon_social": "Intercam Casa de Bolsa, S.A. de C.V."}, + {"key": "631", "name": "CI BOLSA", "razon_social": "CI Casa de Bolsa, S.A. de C.V."}, + {"key": "632", "name": "BULLTICK CB", "razon_social": "Bulltick Casa de Bolsa, S.A., de C.V."}, + {"key": "633", "name": "STERLING", "razon_social": "Sterling Casa de Cambio, S.A. de C.V."}, + {"key": "634", "name": "FINCOMUN", "razon_social": "Fincomún, Servicios Financieros Comunitarios, S.A. de C.V."}, + {"key": "636", "name": "HDI SEGUROS", "razon_social": "HDI Seguros, S.A. de C.V."}, + {"key": "637", "name": "ORDER", "razon_social": "Order Express Casa de Cambio, S.A. de C.V"}, + {"key": "638", "name": "AKALA", "razon_social": "Akala, S.A. de C.V., Sociedad Financiera Popular"}, + {"key": "640", "name": "CB JPMORGAN", "razon_social": "J.P. Morgan Casa de Bolsa, S.A. de C.V. J.P. Morgan Grupo Financiero"}, + {"key": "642", "name": "REFORMA", "razon_social": "Operadora de Recursos Reforma, S.A. de C.V., S.F.P."}, + {"key": "646", "name": "STP", "razon_social": "Sistema de Transferencias y Pagos STP, S.A. de C.V.SOFOM ENR"}, + {"key": "647", "name": "TELECOMM", "razon_social": "Telecomunicaciones de México"}, + {"key": "648", "name": "EVERCORE", "razon_social": "Evercore Casa de Bolsa, S.A. de C.V."}, + {"key": "649", "name": "SKANDIA", "razon_social": "Skandia Operadora de Fondos, S.A. de C.V."}, + {"key": "651", "name": "SEGMTY", "razon_social": "Seguros Monterrey New York Life, S.A de C.V"}, + {"key": "652", "name": "ASEA", "razon_social": "Solución Asea, S.A. de C.V., Sociedad Financiera Popular"}, + {"key": "653", "name": "KUSPIT", "razon_social": "Kuspit Casa de Bolsa, S.A. de C.V."}, + {"key": "655", "name": "SOFIEXPRESS", "razon_social": "J.P. SOFIEXPRESS, S.A. de C.V., S.F.P."}, + {"key": "656", "name": "UNAGRA", "razon_social": "UNAGRA, S.A. de C.V., S.F.P."}, + {"key": "659", "name": "OPCIONES EMPRESARIALES DEL NOROESTE", "razon_social": "OPCIONES EMPRESARIALES DEL NORESTE, S.A. DE C.V., S.F.P."}, + {"key": "670", "name": "LIBERTAD", "razon_social": "Libertad Servicios Financieros, S.A. De C.V."}, + {"key": "901", "name": "CLS", "razon_social": "Cls Bank International"}, + {"key": "902", "name": "INDEVAL", "razon_social": "SD. Indeval, S.A. de C.V."}, + {"key": "999", "name": "N/A"} + ] +}, { "tabla": "SATUsoCfdi", "datos": [ diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 408e280..3e5d159 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -26,6 +26,7 @@ var controllers = { tb_sat.attachEvent('onChange', tab_sat_change) $$('grid_admin_taxes').attachEvent('onCheck', grid_admin_taxes_on_check) $$('grid_admin_monedas').attachEvent('onCheck', grid_admin_monedas_on_check) + $$('grid_admin_bancos').attachEvent('onCheck', grid_admin_bancos_on_check) //~ Opciones tb_options = $$('tab_options').getTabbar() tb_options.attachEvent('onChange', tab_options_change) @@ -217,6 +218,7 @@ function get_admin_impuestos(){ }) } + function get_admin_monedas(){ webix.ajax().sync().get('/values/allcurrencies', function(text, data){ var values = data.json() @@ -226,6 +228,15 @@ function get_admin_monedas(){ } +function get_admin_bancos(){ + webix.ajax().sync().get('/values/allbancos', function(text, data){ + var values = data.json() + $$('grid_admin_bancos').clearAll() + $$('grid_admin_bancos').parse(values, 'json') + }) +} + + function get_config_values(opt){ if(opt == undefined){ return @@ -754,10 +765,10 @@ function tab_options_change(nv, ov){ function tab_sat_change(nv, ov){ - //~ show(nv) - if(nv = 'Monedas'){ + if(nv == 'Monedas'){ get_admin_monedas() - return + }else if(nv == 'Bancos'){ + get_admin_bancos() } } @@ -792,3 +803,19 @@ function grid_admin_monedas_on_check(row, column, state){ } }) } + + +function grid_admin_bancos_on_check(row, column, state){ + + var values = { + id: row, + field: column, + value: state, + } + webix.ajax().get('/values/bancoupdate', values, { + error: function(text, data, xhr) { + }, + success: function(text, data, xhr) { + } + }) +} diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index 5a81982..52a59f3 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -388,6 +388,15 @@ var grid_admin_monedas_cols = [ ] +var grid_admin_bancos_cols = [ + {id: 'id', header: 'ID', hidden: true}, + {id: 'key', header: 'Clave', footer: {content: 'rowCount', css: 'right'}}, + {id: 'name', header: 'Nombre', adjust: 'data', footer: 'Bancos'}, + {id: 'activo', header: 'Activo', template: '{common.checkbox()}', + editor: 'checkbox'}, +] + + var grid_admin_taxes = { view: 'datatable', id: 'grid_admin_taxes', @@ -414,6 +423,19 @@ var grid_admin_monedas = { } +var grid_admin_bancos = { + view: 'datatable', + id: 'grid_admin_bancos', + select: 'cell', + multiselect: true, + adjust: true, + autowidth: true, + headermenu: true, + footer: true, + columns: grid_admin_bancos_cols +} + + var msg_tax = 'Activa los impuestos que uses. El predeterminado se muestra primero' var sat_impuestos = [ {maxHeight: 20}, @@ -422,6 +444,7 @@ var sat_impuestos = [ {cols: [{maxWidth: 15}, grid_admin_taxes, {}]}, {}] + var msg_moneda = 'Activa las monedas que uses. La predeterminada se muestra primero' var sat_monedas = [ {maxHeight: 20}, @@ -431,6 +454,15 @@ var sat_monedas = [ {}] +var msg_bancos = 'Activa los bancos que uses.' +var sat_bancos = [ + {maxHeight: 20}, + {cols: [{maxWidth: 15}, {view: 'label', label: msg_bancos}, {}]}, + {maxHeight: 20}, + {cols: [{maxWidth: 15}, grid_admin_bancos, {}]} +] + + var tab_sat = { view: 'tabview', id: 'tab_sat', @@ -438,13 +470,15 @@ var tab_sat = { tabbar: {options: [ 'Impuestos', 'Monedas', - 'Unidades']}, + 'Bancos', + 'Unidades' + ]}, animate: true, cells: [ {id: 'Impuestos', rows: sat_impuestos}, {id: 'Monedas', rows: sat_monedas}, + {id: 'Bancos', rows: sat_bancos}, {id: 'Unidades', rows: [{}]}, - {}, ] } From ea298c52d5ef53a175dfe182a414e25577b974c0 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sat, 11 Nov 2017 20:23:06 -0600 Subject: [PATCH 2/9] Ajustar margen en tabla bancos --- source/static/js/ui/admin.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index 52a59f3..d181c25 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -459,7 +459,8 @@ var sat_bancos = [ {maxHeight: 20}, {cols: [{maxWidth: 15}, {view: 'label', label: msg_bancos}, {}]}, {maxHeight: 20}, - {cols: [{maxWidth: 15}, grid_admin_bancos, {}]} + {cols: [{maxWidth: 15}, grid_admin_bancos, {}]}, + {maxHeight: 20}, ] From a290a034f8ff8d9b7d04c01ac21353d09e49bfb1 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sat, 11 Nov 2017 23:58:11 -0600 Subject: [PATCH 3/9] Agregar cuentas de banco del emisor --- source/app/controllers/main.py | 17 ++++ source/app/main.py | 3 +- source/app/models/db.py | 12 +++ source/app/models/main.py | 82 +++++++++++++++- source/static/js/controller/admin.js | 136 +++++++++++++++++++++++++++ source/static/js/controller/util.js | 5 + source/static/js/ui/admin.js | 78 ++++++++++++++- 7 files changed, 327 insertions(+), 6 deletions(-) diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 721a503..bc7a47a 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -238,6 +238,23 @@ class AppEmisor(object): resp.status = falcon.HTTP_204 +class AppCuentasBanco(object): + + def __init__(self, db): + self._db = db + + def on_get(self, req, resp): + values = req.params + session = req.env['beaker.session'] + #~ req.context['result'] = self._db.get_emisor(session['rfc']) + resp.status = falcon.HTTP_200 + + def on_post(self, req, resp): + values = req.params + req.context['result'] = self._db.cuentasbanco(values) + resp.status = falcon.HTTP_200 + + class AppFolios(object): def __init__(self, db): diff --git a/source/app/main.py b/source/app/main.py index e6a6710..ed80b25 100644 --- a/source/app/main.py +++ b/source/app/main.py @@ -15,7 +15,7 @@ from models.db import StorageEngine from controllers.main import ( AppLogin, AppLogout, AppAdmin, AppEmisor, AppConfig, AppMain, AppValues, AppPartners, AppProducts, AppInvoices, AppFolios, - AppDocumentos, AppFiles, AppPreInvoices + AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco ) from settings import DEBUG @@ -45,6 +45,7 @@ api.add_route('/partners', AppPartners(db)) api.add_route('/products', AppProducts(db)) api.add_route('/invoices', AppInvoices(db)) api.add_route('/preinvoices', AppPreInvoices(db)) +api.add_route('/cuentasbanco', AppCuentasBanco(db)) if DEBUG: diff --git a/source/app/models/db.py b/source/app/models/db.py index ad2cf08..b196f3c 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -103,6 +103,9 @@ class StorageEngine(object): def _get_bancoupdate(self, values): return main.SATBancos.actualizar(values) + def _get_emisorcuentasbanco(self, values): + return main.CuentasBanco.emisor() + def _get_satkey(self, values): return main.get_sat_key(values['key']) @@ -112,6 +115,12 @@ class StorageEngine(object): def _get_monedas(self, values): return main.SATMonedas.get_activos() + def _get_monedasid(self, values): + return main.SATMonedas.get_activos_by_id() + + def _get_bancosid(self, values): + return main.SATBancos.get_activos_by_id() + def _get_regimenes(self, values): return main.Emisor.get_regimenes() @@ -182,6 +191,9 @@ class StorageEngine(object): def emisor(self, values): return main.Emisor.add(values) + def cuentasbanco(self, values): + return main.CuentasBanco.add(values) + def get_folios(self): return main.Folios.get_() diff --git a/source/app/models/main.py b/source/app/models/main.py index e3b11a1..c56a31c 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -658,6 +658,17 @@ class SATMonedas(BaseModel): ) return tuple(rows) + @classmethod + def get_activos_by_id(cls): + rows = (SATMonedas + .select( + SATMonedas.id, + SATMonedas.name.alias('value')) + .where(SATMonedas.activo==True) + .dicts() + ) + return tuple(rows) + @classmethod def actualizar(self, values): id = int(values['id']) @@ -773,12 +784,23 @@ class SATBancos(BaseModel): return 'Banco: {} ({})'.format(self.name, self.key) @classmethod - def get_(self): + def get_(cls): rows = SATBancos.select().dicts() return tuple(rows) @classmethod - def actualizar(self, values): + def get_activos_by_id(cls): + rows = (SATBancos + .select( + SATBancos.id, + SATBancos.name.alias('value')) + .where(SATBancos.activo==True) + .dicts() + ) + return tuple(rows) + + @classmethod + def actualizar(cls, values): id = int(values['id']) if values['field'] == 'activo': v = {'0': False, '1': True} @@ -791,6 +813,7 @@ class SATBancos(BaseModel): class CuentasBanco(BaseModel): de_emisor = BooleanField(default=False) + activa = BooleanField(default=True) nombre = TextField() banco = ForeignKeyField(SATBancos) fecha_apertura = DateField(null=True) @@ -799,6 +822,8 @@ class CuentasBanco(BaseModel): moneda = ForeignKeyField(SATMonedas) saldo_inicial = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) + saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, + auto_round=True) class Meta: order_by = ('nombre',) @@ -809,6 +834,59 @@ class CuentasBanco(BaseModel): def __str__(self): return '{} ({})'.format(self.banco.name, self.cuenta[-4:]) + @classmethod + def emisor(cls): + rows = (CuentasBanco + .select( + CuentasBanco.id, + CuentasBanco.activa, + CuentasBanco.nombre, + SATBancos.name.alias('banco'), + CuentasBanco.fecha_apertura, + CuentasBanco.cuenta, + CuentasBanco.clabe, + SATMonedas.name.alias('moneda'), + CuentasBanco.saldo + ) + .join(SATBancos).switch(CuentasBanco) + .join(SATMonedas).switch(CuentasBanco) + .where(CuentasBanco.de_emisor==True).dicts() + ) + return tuple(rows) + + @classmethod + def add(cls, values): + w = '37137137137137137' + dv = str( + 10 - + sum([(int(v) * int(values['clabe'][i])) % 10 for i, v in enumerate(w)]) + % 10) + if dv != values['clabe'][-1]: + msg = 'Digito de control de la CLABE es incorrecto' + return {'ok': False, 'msg': msg} + + with database_proxy.transaction(): + obj = CuentasBanco.create(**values) + + rows = (CuentasBanco + .select( + CuentasBanco.id, + CuentasBanco.activa, + CuentasBanco.nombre, + SATBancos.name.alias('banco'), + CuentasBanco.fecha_apertura, + CuentasBanco.cuenta, + CuentasBanco.clabe, + SATMonedas.name.alias('moneda'), + CuentasBanco.saldo + ) + .join(SATBancos).switch(CuentasBanco) + .join(SATMonedas).switch(CuentasBanco) + .where(CuentasBanco.id==obj.id).dicts() + ) + data = {'ok': True, 'row': row[0]} + return data + class SATUsoCfdi(BaseModel): key = TextField(index=True, unique=True) diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 3e5d159..664734f 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -21,6 +21,8 @@ var controllers = { $$('cmd_probar_correo').attachEvent('onItemClick', cmd_probar_correo_click) $$('cmd_guardar_correo').attachEvent('onItemClick', cmd_guardar_correo_click) $$('emisor_logo').attachEvent('onItemClick', emisor_logo_click) + $$('cmd_emisor_agregar_cuenta').attachEvent('onItemClick', cmd_emisor_agregar_cuenta_click) + $$('emisor_cuenta_saldo_inicial').attachEvent('onChange', emisor_cuenta_saldo_inicial_change) //~ SAT tb_sat = $$('tab_sat').getTabbar() tb_sat.attachEvent('onChange', tab_sat_change) @@ -178,6 +180,38 @@ function get_certificado(){ } +function get_cuentas_banco(){ + + webix.ajax().get('/values/monedasid', function(text, data){ + var values = data.json() + pre = values[0] + $$('lst_emisor_cuenta_moneda').getList().parse(values) + $$('lst_emisor_cuenta_moneda').setValue(pre.id) + }) + + webix.ajax().get('/values/bancosid', function(text, data){ + var values = data.json() + pre = values[0] + $$('lst_emisor_banco').getList().parse(values) + if(values.length == 1){ + $$('lst_emisor_banco').setValue(pre.id) + } + }) + + webix.ajax().get('/values/emisorcuentasbanco', {}, { + error: function(text, data, xhr) { + msg = 'Error al consultar' + msg_error(msg) + }, + success: function(text, data, xhr) { + var values = data.json() + $$('grid_emisor_cuentas_banco').parse(values) + } + }) + +} + + function get_table_folios(){ webix.ajax().get("/folios", {}, { error: function(text, data, xhr) { @@ -263,6 +297,7 @@ function multi_admin_change(prevID, nextID){ $$('tab_emisor').setValue('Datos Fiscales') get_emisor() get_certificado() + get_cuentas_banco() return } @@ -819,3 +854,104 @@ function grid_admin_bancos_on_check(row, column, state){ } }) } + + +function emisor_cuenta_saldo_inicial_change(new_value, old_value){ + if(!isFinite(new_value)){ + this.config.value = old_value + this.refresh() + } +} + + +function cmd_emisor_agregar_cuenta_click(){ + var form = $$('form_emisor_cuenta_banco') + + if (!form.validate()){ + msg = 'Valores inválidos' + msg_error(msg) + return + } + + var values = form.getValues() + + var si = parseFloat(values.emisor_cuenta_saldo_inicial.replace('$', '').replace(',', '')) + var cuenta = { + de_emisor: true, + activa: true, + nombre: values.emisor_cuenta_nombre.trim(), + banco: values.emisor_banco, + fecha_apertura: values.emisor_cuenta_fecha, + cuenta: values.emisor_cuenta.trim(), + clabe: values.emisor_clabe.trim(), + moneda: values.emisor_cuenta_moneda, + saldo_inicial: si + } + + if(!cuenta.nombre){ + msg = 'El nombre de la cuenta es requerido' + msg_error(msg) + return + } + + if(!cuenta.cuenta){ + msg = 'La cuenta es requerida' + msg_error(msg) + return + } + + if(!cuenta.cuenta.is_number()){ + msg = 'Solo digitos en la cuenta' + msg_error(msg) + return + } + + if(cuenta.cuenta.length < 9){ + msg = 'Longitud incorrecta de la cuenta' + msg_error(msg) + return + } + + if(!cuenta.clabe){ + msg = 'La CLABE es requerida' + msg_error(msg) + return + } + + if(cuenta.clabe.length != 18){ + msg = 'La CLABE debe ser de 18 digitos' + msg_error(msg) + return + } + + if(!cuenta.clabe.is_number()){ + msg = 'Solo digitos en la CLABE' + msg_error(msg) + return + } + + //~ if(!isFinite(cuenta.saldo_inicial)){ + if(cuenta.saldo_inicial <= 0){ + msg = 'El saldo inicial no puede ser negativo o cero' + msg_error(msg) + return + } + + cuenta.saldo = cuenta.saldo_inicial + + webix.ajax().post('/cuentasbanco', cuenta, { + 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){ + $$('grid_emisor_cuentas_banco').add(values.row) + }else{ + msg_error(values.msg) + } + } + }) + +} diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index f945d78..dd26684 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -35,6 +35,11 @@ Number.prototype.round = function(decimals){ } +String.prototype.is_number = function(){ + return /^\d+$/.test(this) +} + + webix.protoUI({ $cssName: "text", name: "currency", diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index d181c25..2c7da08 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -132,6 +132,78 @@ var emisor_certificado = [ ] +var grid_emisor_cuentas_banco_cols = [ + {id: 'id', header: 'ID', hidden: true}, + {id: 'activa', header: 'Activa', template: '{common.checkbox()}', + editor: 'checkbox', width: 50}, + {id: 'nombre', header: 'Nombre', fillspace: 1}, + {id: 'banco', header: 'Banco', fillspace: 1}, + {id: 'fecha_apertura', header: 'Fecha de Apertura', fillspace: 1}, + {id: 'cuenta', header: 'Cuenta', fillspace: 1}, + {id: 'clabe', header: 'CLABE', fillspace: 1}, + {id: 'moneda', header: 'Moneda', fillspace: 1}, + {id: 'saldo', header: 'Saldo', width: 150, format: webix.i18n.priceFormat, + css: 'right'}, +] + + +var grid_emisor_cuentas_banco = { + view: 'datatable', + id: 'grid_emisor_cuentas_banco', + select: 'row', + adjust: true, + autoheight: true, + headermenu: true, + columns: grid_emisor_cuentas_banco_cols +} + + +var emisor_cuentas_banco = [ + {template: 'Agregar cuenta de banco', type: 'section'}, + {view: 'form', id: 'form_emisor_cuenta_banco', rows: [ + {cols: [ + {view: 'text', id: 'emisor_cuenta_nombre', name: 'emisor_cuenta_nombre', + label: 'Nombre: ', required: true}, {}]}, + {cols: [ + {view: 'richselect', id: 'lst_emisor_banco', name: 'emisor_banco', + label: 'Banco: ', required: true, options: []}, + {view: 'datepicker', id: 'emisor_cuenta_fecha', format: '%d-%M-%Y', + name: 'emisor_cuenta_fecha', label: 'Fecha de apertura: ', + required: true}, + ]}, + {cols: [ + {view: 'text', id: 'emisor_cuenta', name: 'emisor_cuenta', + label: 'Cuenta: ', required: true}, + {view: 'text', id: 'emisor_clabe', name: 'emisor_clabe', + label: 'CLABE: ', required: true}, + ]}, + {cols: [ + {view: 'richselect', id: 'lst_emisor_cuenta_moneda', + name: 'emisor_cuenta_moneda', label: 'Moneda: ', required: true, + options: []}, + {view: 'currency', type: 'text', id: 'emisor_cuenta_saldo_inicial', + name: 'emisor_cuenta_saldo_inicial', label: 'Saldo inicial', + required: true, invalidMessage: 'Captura un valor númerico', + inputAlign: 'right', value: 0}, + ]}, + {minHeight: 10}, + {cols: [{}, {view: 'button', id: 'cmd_emisor_agregar_cuenta', + label: 'Agregar cuenta'}, {}]}, + + ], + + rules: { + emisor_cuenta_saldo_inicial: function(value){return value.trim() != "$";}, + } + + }, + {minHeight: 20}, + {template: 'Cuentas de banco existentes', type: 'section'}, + grid_emisor_cuentas_banco, + {minHeight: 50}, +] + + var controls_emisor = [ { view: 'tabview', @@ -139,13 +211,14 @@ var controls_emisor = [ tabbar: {options: [ 'Datos Fiscales', 'Otros Datos', - 'Certificado']}, + 'Certificado', + 'Cuentas de Banco']}, animate: true, cells: [ {id: 'Datos Fiscales', rows: emisor_datos_fiscales}, {id: 'Otros Datos', rows: emisor_otros_datos}, {id: 'Certificado', rows: emisor_certificado}, - {}, + {id: 'Cuentas de Banco', rows: emisor_cuentas_banco} ] } ] @@ -580,7 +653,6 @@ var ui_admin = { {view: 'toolbar', padding: 3, elements: [ {view: 'button', type: 'icon', icon: 'bars', width: 37, align: 'left', css: 'app_button', click: function(){ - //~ $$('$sidebar1').toggle() $$('sidebar_admin').toggle() } }, From 582e318dae6f05054e9ddd27e555973f259d13cc Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 12 Nov 2017 17:49:06 -0600 Subject: [PATCH 4/9] =?UTF-8?q?Administrar=20unidades=20en=20configuraci?= =?UTF-8?q?=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/app/models/db.py | 6 ++++ source/app/models/main.py | 27 +++++++++++++++++ source/static/js/controller/admin.js | 28 ++++++++++++++++++ source/static/js/controller/invoices.js | 3 -- source/static/js/ui/admin.js | 39 +++++++++++++++++++++++-- 5 files changed, 98 insertions(+), 5 deletions(-) diff --git a/source/app/models/db.py b/source/app/models/db.py index b196f3c..ea6fe5c 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -94,6 +94,9 @@ class StorageEngine(object): def _get_allbancos(self, values): return main.SATBancos.get_() + def _get_allunidades(self, values): + return main.SATUnidades.get_() + def _get_taxupdate(self, values): return main.SATImpuestos.actualizar(values) @@ -103,6 +106,9 @@ class StorageEngine(object): def _get_bancoupdate(self, values): return main.SATBancos.actualizar(values) + def _get_unidadupdate(self, values): + return main.SATUnidades.actualizar(values) + def _get_emisorcuentasbanco(self, values): return main.CuentasBanco.emisor() diff --git a/source/app/models/main.py b/source/app/models/main.py index c56a31c..4546800 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -574,6 +574,32 @@ class SATUnidades(BaseModel): (('key', 'name'), True), ) + @classmethod + def get_(self): + rows = SATUnidades.select().dicts() + return tuple(rows) + + @classmethod + def actualizar(self, values): + id = int(values['id']) + if values['field'] == 'activo': + v = {'0': False, '1': True} + q = (SATUnidades + .update(**{'activo': v[values['value']]}) + .where(SATUnidades.id==id)) + result = bool(q.execute()) + elif values['field'] == 'default': + q = SATUnidades.update(**{'default': False}) + q.execute() + + v = {'false': False, 'true': True} + q = (SATUnidades + .update(**{'default': v[values['value']]}) + .where(SATUnidades.id==id)) + result = bool(q.execute()) + + return {'ok': result} + @classmethod def get_activos(cls): rows = (SATUnidades @@ -1319,6 +1345,7 @@ class Facturas(BaseModel): fecha_cancelacion = DateTimeField(null=True) acuse = TextField(default='') donativo = BooleanField(default=False) + anticipo = BooleanField(default=False) tipo_relacion = TextField(default='') error = TextField(default='') diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 664734f..187d6c9 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -29,6 +29,7 @@ var controllers = { $$('grid_admin_taxes').attachEvent('onCheck', grid_admin_taxes_on_check) $$('grid_admin_monedas').attachEvent('onCheck', grid_admin_monedas_on_check) $$('grid_admin_bancos').attachEvent('onCheck', grid_admin_bancos_on_check) + $$('grid_admin_unidades').attachEvent('onCheck', grid_admin_unidades_on_check) //~ Opciones tb_options = $$('tab_options').getTabbar() tb_options.attachEvent('onChange', tab_options_change) @@ -271,6 +272,15 @@ function get_admin_bancos(){ } +function get_admin_unidades(){ + webix.ajax().sync().get('/values/allunidades', function(text, data){ + var values = data.json() + $$('grid_admin_unidades').clearAll() + $$('grid_admin_unidades').parse(values, 'json') + }) +} + + function get_config_values(opt){ if(opt == undefined){ return @@ -804,6 +814,8 @@ function tab_sat_change(nv, ov){ get_admin_monedas() }else if(nv == 'Bancos'){ get_admin_bancos() + }else if(nv == 'Unidades'){ + get_admin_unidades() } } @@ -856,6 +868,22 @@ function grid_admin_bancos_on_check(row, column, state){ } +function grid_admin_unidades_on_check(row, column, state){ + + var values = { + id: row, + field: column, + value: state, + } + webix.ajax().get('/values/unidadupdate', values, { + error: function(text, data, xhr) { + }, + success: function(text, data, xhr) { + } + }) +} + + function emisor_cuenta_saldo_inicial_change(new_value, old_value){ if(!isFinite(new_value)){ this.config.value = old_value diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index f486f06..dea788e 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -1320,6 +1320,3 @@ function filter_cfdi_month_change(nv, ov){ cmd_filter_relacionados_click() } - - - diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index 2c7da08..758bba8 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -470,6 +470,17 @@ var grid_admin_bancos_cols = [ ] +var grid_admin_unidades_cols = [ + {id: 'id', header: 'ID', hidden: true}, + {id: 'key', header: 'Clave'}, + {id: 'name', header: 'Nombre', adjust: 'data'}, + {id: 'activo', header: 'Activa', template: '{common.checkbox()}', + editor: 'checkbox'}, + {id: 'default', header: 'Predeterminada', template: '{common.radio()}', + adjust: 'header'}, +] + + var grid_admin_taxes = { view: 'datatable', id: 'grid_admin_taxes', @@ -509,6 +520,20 @@ var grid_admin_bancos = { } +var grid_admin_unidades = { + view: 'datatable', + id: 'grid_admin_unidades', + select: 'cell', + multiselect: true, + adjust: true, + autowidth: true, + autoheight: true, + headermenu: true, + footer: true, + columns: grid_admin_unidades_cols +} + + var msg_tax = 'Activa los impuestos que uses. El predeterminado se muestra primero' var sat_impuestos = [ {maxHeight: 20}, @@ -527,7 +552,7 @@ var sat_monedas = [ {}] -var msg_bancos = 'Activa los bancos que uses.' +var msg_bancos = 'Activar los bancos necesarios' var sat_bancos = [ {maxHeight: 20}, {cols: [{maxWidth: 15}, {view: 'label', label: msg_bancos}, {}]}, @@ -537,6 +562,16 @@ var sat_bancos = [ ] +var msg_unidades = 'Activar las unidades necesarias' +var sat_unidades = [ + {maxHeight: 20}, + {cols: [{maxWidth: 15}, {view: 'label', label: msg_unidades}, {}]}, + {maxHeight: 20}, + {cols: [{maxWidth: 15}, grid_admin_unidades, {}]}, + {}, +] + + var tab_sat = { view: 'tabview', id: 'tab_sat', @@ -552,7 +587,7 @@ var tab_sat = { {id: 'Impuestos', rows: sat_impuestos}, {id: 'Monedas', rows: sat_monedas}, {id: 'Bancos', rows: sat_bancos}, - {id: 'Unidades', rows: [{}]}, + {id: 'Unidades', rows: sat_unidades}, ] } From ff5f98f18747d37135d7f32d65fd2711eac0b5e2 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 12 Nov 2017 18:50:41 -0600 Subject: [PATCH 5/9] Validar y filtrar CFDI de anticipo --- source/app/models/main.py | 11 ++++--- source/static/js/controller/invoices.js | 42 +++++++++++++++++++++++++ source/static/js/ui/admin.js | 2 +- source/static/js/ui/invoices.js | 11 +++++-- 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/source/app/models/main.py b/source/app/models/main.py index 4546800..de7f418 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1599,12 +1599,14 @@ class Facturas(BaseModel): else: f_ids = (Facturas.id > 0) + filters = (fy & fm & cliente & f_ids) if folios: - filters = (fy & fm & folios & cliente & f_ids) + filters = filters & folios elif uuid: - filters = (fy & fm & f_uuid & cliente & f_ids) - else: - filters = (fy & fm & cliente & f_ids) + filters = filters & f_uuid + + if values['anticipo'] == '1': + filters = filters & (Facturas.anticipo == True) rows = tuple(Facturas .select(Facturas.id, Facturas.serie, Facturas.folio, @@ -1770,6 +1772,7 @@ class Facturas(BaseModel): values['folio'] = cls._get_folio(cls, values['serie']) values['tipo_cambio'] = float(values['tipo_cambio']) values['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal + values['anticipo'] = util.get_bool(values['anticipo']) with database_proxy.atomic() as txn: obj = Facturas.create(**values) diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index dea788e..127006e 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -3,6 +3,7 @@ var grid = null var msg = '' var result = false var tipo_relacion = '' +var anticipo = false function get_condicion_pago(){ @@ -244,6 +245,29 @@ function validate_invoice(values){ return false } + anticipo = $$('chk_cfdi_anticipo').getValue() + if(anticipo){ + var mp = $$('lst_metodo_pago').getValue() + if(mp != 'PUE'){ + msg = 'En anticipos, el método de pago debe ser: Pago en una sola exhibición' + msg_error(msg) + return false + } + + if(grid.count() != 1){ + msg = 'Los anticipos solo llevan un concepto' + msg_error(msg) + return false + } + + query = table_relaciones.chain().data() + if(query.length > 0){ + msg = 'Los anticipos no deben llevar CFDI relacionados' + msg_error(msg) + return false + } + } + return true } @@ -373,6 +397,7 @@ function guardar_y_timbrar(values){ data['regimen_fiscal'] = $$('lst_regimen_fiscal').getValue() data['relacionados'] = ids data['tipo_relacion'] = tipo_relacion + data['anticipo'] = anticipo if(!save_invoice(data)){ return @@ -380,6 +405,7 @@ function guardar_y_timbrar(values){ table_relaciones.clear() tipo_relacion = '' + anticipo = false $$('form_invoice').setValues({id_partner: 0, lbl_partner: 'Ninguno'}) $$('multi_invoices').setValue('invoices_home') @@ -404,6 +430,9 @@ function cmd_timbrar_click(id, e, node){ if(query.length > 0){ msg += 'La factura tiene CFDI relacionados

' } + if(anticipo){ + msg += 'La factura es Anticipo

' + } msg += '¿Estás seguro de timbrar esta factura?' webix.confirm({ @@ -1201,6 +1230,7 @@ function get_facturas_por_cliente(){ 'month': m, 'id_cliente': id, 'cfdis': ids, + 'anticipo': $$('chk_relacionados_anticipo').getValue(), 'folio': $$('filter_cfdi_folio').getValue(), 'uuid': $$('filter_cfdi_uuid').getValue(), 'opt': 'relacionados' @@ -1320,3 +1350,15 @@ function filter_cfdi_month_change(nv, ov){ cmd_filter_relacionados_click() } + +function lst_tipo_relacion_change(nv, ov){ + $$('chk_relacionados_anticipo').setValue(0) + $$('chk_relacionados_anticipo').disable() + if(nv=='07'){ + $$('chk_relacionados_anticipo').enable() + $$('chk_relacionados_anticipo').setValue(1) + cmd_filter_relacionados_click() + } + +} + diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index 758bba8..00d5a26 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -184,7 +184,7 @@ var emisor_cuentas_banco = [ {view: 'currency', type: 'text', id: 'emisor_cuenta_saldo_inicial', name: 'emisor_cuenta_saldo_inicial', label: 'Saldo inicial', required: true, invalidMessage: 'Captura un valor númerico', - inputAlign: 'right', value: 0}, + inputAlign: 'right', value: ''}, ]}, {minHeight: 10}, {cols: [{}, {view: 'button', id: 'cmd_emisor_agregar_cuenta', diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index fb2c11f..ac69e57 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -97,8 +97,11 @@ var body_cfdi_relacionados = {rows: [ {view: 'label', id: 'lbl_cfdi_title', label: 'Cliente: ', autowidth: true}, {view: 'label', id: 'lbl_cfdi_cliente', label: '', align: 'left'}]}, - {view: 'richselect', id: 'lst_tipo_relacion', label: 'Tipo de Relación', - labelWidth: 150, required: true, options: []}, + {cols: [ + {view: 'richselect', id: 'lst_tipo_relacion', label: 'Tipo de Relación', + labelWidth: 140, required: true, options: []}, + {view: 'checkbox', id: 'chk_relacionados_anticipo', + labelRight: 'Solo Anticipos', width: 200, disabled: true}]}, {minHeight: 10, maxHeight: 10}, {cols: [ {view: 'richselect', id: 'filter_cfdi_year', label: 'Año', width: 100, @@ -145,7 +148,8 @@ var ui_invoice = { $$('cmd_guardar_relacionados').attachEvent('onItemClick', cmd_guardar_relacionados_click) $$('cmd_limpiar_relacionados').attachEvent('onItemClick', cmd_limpiar_relacionados_click) $$('cmd_filter_relacionados').attachEvent('onItemClick', cmd_filter_relacionados_click) - $$('filter_cfdi_year').attachEvent('onChange', filter_cfdi_year_change) + $$('lst_tipo_relacion').attachEvent('onChange', lst_tipo_relacion_change) + $$('filter_cfdi_month').attachEvent('onChange', filter_cfdi_month_change) $$('filter_cfdi_month').attachEvent('onChange', filter_cfdi_month_change) }} @@ -175,6 +179,7 @@ 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}, ]} From 65e8c89a85a3b93a1c537dfbb630eea28ede3faf Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 12 Nov 2017 21:56:45 -0600 Subject: [PATCH 6/9] Fix - Uso del CFDI --- source/app/models/db.py | 5 +- source/app/models/main.py | 99 +++++++++++++++++++++---- source/static/js/controller/invoices.js | 48 ++++++++++++ source/static/js/controller/main.js | 5 +- source/static/js/controller/partners.js | 8 +- source/static/js/ui/partners.js | 2 + 6 files changed, 146 insertions(+), 21 deletions(-) diff --git a/source/app/models/db.py b/source/app/models/db.py index ea6fe5c..d3fab7e 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -131,7 +131,7 @@ class StorageEngine(object): return main.Emisor.get_regimenes() def _get_usocfdi(self, values): - return main.SATUsoCfdi.get_activos(values) + return main.SATUsoCfdi.get_activos() def delete(self, table, id): if table == 'partner': @@ -191,6 +191,9 @@ class StorageEngine(object): def _get_timbrar(self, values): return main.Facturas.timbrar(int(values['id'])) + def _get_anticipoegreso(self, values): + return main.Facturas.anticipo_egreso(int(values['id'])) + def get_emisor(self, rfc): return main.Emisor.get_(rfc) diff --git a/source/app/models/main.py b/source/app/models/main.py index de7f418..90f8429 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -910,7 +910,7 @@ class CuentasBanco(BaseModel): .join(SATMonedas).switch(CuentasBanco) .where(CuentasBanco.id==obj.id).dicts() ) - data = {'ok': True, 'row': row[0]} + data = {'ok': True, 'row': rows[0]} return data @@ -932,13 +932,16 @@ class SATUsoCfdi(BaseModel): return 'Uso del CFDI: {} ({})'.format(self.name, self.key) @classmethod - def get_activos(cls, values): - field = SATUsoCfdi.id - if values: - field = SATUsoCfdi.key.alias('id') + def get_id(self, key): + if key is None: + return + return SATUsoCfdi.get(SATUsoCfdi.key==key).id + + @classmethod + def get_activos(cls): rows = (SATUsoCfdi .select( - field, + SATUsoCfdi.key.alias('id'), SATUsoCfdi.name.alias('value'), SATUsoCfdi.fisica, SATUsoCfdi.moral, @@ -1009,7 +1012,8 @@ class Socios(BaseModel): fields['rfc'] = fields['rfc'].upper() fields['nombre'] = util.spaces(fields['nombre']) fields['slug'] = util.to_slug(fields['nombre']) - fields['uso_cfdi'] = fields.pop('uso_cfdi_socio', None) + uso_cfdi = fields.pop('uso_cfdi_socio', None) + fields['uso_cfdi'] = SATUsoCfdi.get_id(uso_cfdi) fields['condicion_pago'] = \ CondicionesPago.get_or(fields.get('condicion_pago', None)) @@ -1030,7 +1034,14 @@ class Socios(BaseModel): str(CondicionesPago.get(id=row['condicion_pago'])) return row - rows = Socios.select(Socios.id, Socios.rfc, Socios.nombre).dicts() + rows = (Socios + .select( + Socios.id, + Socios.rfc, + Socios.nombre, + Socios.saldo_cliente) + .dicts() + ) return {'ok': True, 'rows': tuple(rows)} @classmethod @@ -1047,7 +1058,6 @@ class Socios(BaseModel): .where((Socios.id==id) & (Socios.es_cliente==True)) .dicts() ) - #~ print (id, row) if len(row): return {'ok': True, 'row': row[0]} return {'ok': False} @@ -1077,12 +1087,13 @@ class Socios(BaseModel): data = {'ok': False, 'row': {}, 'new': True, 'msg': msg} return data - #~ ToDo Agregar Condicion de pago y tags + #~ ToDo Agregar tags row = { 'id': obj.id, 'rfc': obj.rfc, 'nombre': obj.nombre, + 'saldo_cliente': obj.saldo_cliente, } data = {'ok': True, 'row': row, 'new': True} return data @@ -1091,7 +1102,6 @@ class Socios(BaseModel): def actualizar(cls, values, id): fields = cls._clean(cls, values) try: - #~ print (fields) q = Socios.update(**fields).where(Socios.id==id) q.execute() except IntegrityError: @@ -1346,6 +1356,7 @@ class Facturas(BaseModel): acuse = TextField(default='') donativo = BooleanField(default=False) anticipo = BooleanField(default=False) + egreso_anticipo = BooleanField(default=False) tipo_relacion = TextField(default='') error = TextField(default='') @@ -1472,8 +1483,6 @@ class Facturas(BaseModel): obj = SATTipoRelacion.get(SATTipoRelacion.key==invoice.tipo_relacion) values['tiporelacion'] = str(obj) - print ('\nTR', invoice.tipo_relacion) - return values @classmethod @@ -1511,6 +1520,21 @@ class Facturas(BaseModel): def _send(self, id, rfc): return Facturas.send(id, rfc) + @util.run_in_thread + def _actualizar_saldo_cliente(self, invoice): + if invoice.tipo_comprobante == 'T': + return + + importe = invoice.total_mn + if invoice.tipo_comprobante == 'E': + importe *= -1 + + q = (Socios + .update(saldo_cliente=Socios.saldo_cliente + importe) + .where(Socios.id==invoice.cliente.id) + ) + return bool(q.execute()) + @classmethod def send(cls, id, rfc): values = Configuracion.get_({'fields': 'correo'}) @@ -1940,6 +1964,42 @@ class Facturas(BaseModel): obj.save() return obj.estatus_sat + @classmethod + def anticipo_egreso(cls, id): + origen = Facturas.get(Facturas.id == id) + relacionadas = (FacturasRelacionadas + .select(FacturasRelacionadas.factura_origen) + .where(FacturasRelacionadas.factura==origen)) + conceptos = (FacturasDetalle + .select() + .where(FacturasDetalle.factura==origen)) + impuestos = (FacturasImpuestos + .select() + .where(FacturasImpuestos.factura==origen)) + + #~ egreso_anticipo = BooleanField(default=False) + + serie = Folios.get_egreso(origen.serie) + nueva = { + 'cliente': origen.cliente, + 'tipo_comprobante': 'E', + 'forma_pago': '30', + 'serie': serie, + 'folio': cls._get_folio(cls, serie), + 'tipo_relacion': '07', + 'pagada': True, + 'lugar_expedicion': origen.lugar_expedicion, + 'uso_cfdi': origen.uso_cfdi, + 'moneda': origen.moneda, + 'tipo_cambio': origen.tipo_cambio, + 'regimen_fiscal': origen.regimen_fiscal, + 'subtotal': origen.subtotal, + 'total': origen.total, + 'total_trasladados': origen.total_trasladados, + 'total_retenciones': origen.total_retenciones, + } + return + @classmethod def timbrar(cls, id): obj = Facturas.get(Facturas.id == id) @@ -1950,6 +2010,7 @@ class Facturas(BaseModel): enviar_correo = util.get_bool(Configuracion.get_('correo_directo')) auth = Emisor.get_auth() + anticipo = False msg = 'Factura timbrada correctamente' result = util.timbra_xml(obj.xml, auth) if result['ok']: @@ -1962,6 +2023,9 @@ class Facturas(BaseModel): row = {'uuid': obj.uuid, 'estatus': 'Timbrada'} if enviar_correo: cls._send(cls, id, auth['RFC']) + if obj.tipo_comprobante == 'I' and obj.tipo_relacion == '07': + anticipo = True + cls._actualizar_saldo_cliente(cls, obj) else: msg = result['error'] obj.estatus = 'Error' @@ -1969,7 +2033,14 @@ class Facturas(BaseModel): obj.save() row = {'estatus': 'Error'} - return {'ok': result['ok'], 'msg': msg, 'row': row} + result = { + 'ok': result['ok'], + 'msg': msg, + 'row': row, + 'anticipo': anticipo + } + + return result class PreFacturas(BaseModel): diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 127006e..5d1ab53 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -280,12 +280,54 @@ function update_grid_invoices(values){ } } + +function send_anticipo_egreso(id){ + webix.ajax().get('/values/anticipoegreso', {id: id}, function(text, data){ + var values = data.json() + if(values.ok){ + msg_sucess(values.msg) + gi.add(values.row) + }else{ + webix.alert({ + title: 'Error al Timbrar', + text: values.msg, + type: 'alert-error' + }) + } + }) + +} + + +function generar_anticipo_egreso(id){ + msg = 'La factura tiene un CFDI de anticipo relacionado

' + msg += '¿Deseas generar la factura de egreso correspondiente?' + + webix.confirm({ + title: 'Generar Egreso', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if(result){ + send_anticipo_egreso(id) + } + } + }) +} + + function send_timbrar(id){ webix.ajax().get('/values/timbrar', {id: id}, function(text, data){ var values = data.json() if(values.ok){ msg_sucess(values.msg) gi.updateItem(id, values.row) + if(values.anticipo){ + //~ generar_anticipo_egreso(id) + //~ show('Generar egreso de anticipo') + } }else{ webix.alert({ title: 'Error al Timbrar', @@ -406,6 +448,8 @@ function guardar_y_timbrar(values){ table_relaciones.clear() tipo_relacion = '' anticipo = false + $$('chk_cfdi_anticipo').setValue(0) + $$('form_invoice').setValues({id_partner: 0, lbl_partner: 'Ninguno'}) $$('multi_invoices').setValue('invoices_home') @@ -1362,3 +1406,7 @@ function lst_tipo_relacion_change(nv, ov){ } + +function lst_serie_change(nv, ov){ + show(nv) +} diff --git a/source/static/js/controller/main.js b/source/static/js/controller/main.js index e9bcb1c..ab9fcdb 100644 --- a/source/static/js/controller/main.js +++ b/source/static/js/controller/main.js @@ -54,6 +54,7 @@ var controllers = { $$('cmd_prefactura').attachEvent('onItemClick', cmd_prefactura_click) $$('cmd_cfdi_relacionados').attachEvent('onItemClick', cmd_cfdi_relacionados_click) $$('lst_metodo_pago').attachEvent('onChange', lst_metodo_pago_change) + $$('lst_serie').attachEvent('onChange', lst_serie_change) var tb_invoice = $$('tv_invoice').getTabbar() tb_invoice.attachEvent('onChange', tb_invoice_change) @@ -66,8 +67,8 @@ var controllers = { } -function get_uso_cfdi_to_table(args){ - webix.ajax().sync().get('/values/usocfdi', args, function(text, data){ +function get_uso_cfdi_to_table(){ + webix.ajax().sync().get('/values/usocfdi', function(text, data){ var values = data.json() table_usocfdi.clear() table_usocfdi.insert(values) diff --git a/source/static/js/controller/partners.js b/source/static/js/controller/partners.js index ae208eb..7b331ae 100644 --- a/source/static/js/controller/partners.js +++ b/source/static/js/controller/partners.js @@ -22,7 +22,7 @@ function cmd_new_partner_click(id, e, node){ $$('multi_partners').setValue('partners_new') $$('tab_partner').setValue('Datos Fiscales') - get_uso_cfdi_to_table({}) + get_uso_cfdi_to_table() query = table_usocfdi.chain().find({fisica: true}).data() $$('lst_uso_cfdi_socio').getList().parse(query) } @@ -54,7 +54,7 @@ function cmd_edit_partner_click(id, e, node){ $$('form_partner').setValues(values) $$('forma_pago').getList().load('/values/formapago') - get_uso_cfdi_to_table({}) + get_uso_cfdi_to_table() if(values.tipo_persona == 1){ query = table_usocfdi.chain().find({fisica: true}).data() }else if(values.tipo_persona == 2){ @@ -339,6 +339,6 @@ function rfc_lost_focus(prev_view){ function multi_partners_change(prevID, nextID){ - webix.message(prevID) - webix.message(nextID) + //~ webix.message(prevID) + //~ webix.message(nextID) } diff --git a/source/static/js/ui/partners.js b/source/static/js/ui/partners.js index e1613e7..96c925e 100644 --- a/source/static/js/ui/partners.js +++ b/source/static/js/ui/partners.js @@ -18,6 +18,8 @@ var grid_partners_cols = [ sort: 'string', footer: {text: 'Clientes y Proveedores', colspan: 2}}, {id: 'nombre', header: ['Razón Social', {content: 'textFilter'}], fillspace:true, sort: 'string'}, + {id: 'saldo_cliente', header: ['Saldo Cliente', {content: 'numberFilter'}], + width: 150, sort: 'int', format: webix.i18n.priceFormat, css: 'right'}, ] From cb616a79c53248c8969a4812363f6b3d2e1ff86a Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 12 Nov 2017 22:06:06 -0600 Subject: [PATCH 7/9] Fix - Seleccionar series --- source/static/js/controller/invoices.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 5d1ab53..05e37ac 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -1408,5 +1408,14 @@ function lst_tipo_relacion_change(nv, ov){ function lst_serie_change(nv, ov){ - show(nv) + query = table_series.chain().find({id: nv}).data()[0] + if(query.usarcon){ + $$('lst_tipo_comprobante').setValue(query.usarcon) + $$('lst_tipo_comprobante').config.readonly = true + }else{ + $$('lst_tipo_comprobante').setValue('I') + $$('lst_tipo_comprobante').config.readonly = false + } + $$('lst_tipo_comprobante').refresh() } + From 28fcb803ab064ddf319e408475a9c2f28f52934f Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 12 Nov 2017 23:27:40 -0600 Subject: [PATCH 8/9] Agregar descuentos --- source/app/models/main.py | 65 +++++++++++++++++++------ source/static/js/controller/invoices.js | 28 +++++++++-- source/static/js/ui/invoices.js | 11 +++-- 3 files changed, 83 insertions(+), 21 deletions(-) diff --git a/source/app/models/main.py b/source/app/models/main.py index 90f8429..2afdbc3 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -952,6 +952,15 @@ class SATUsoCfdi(BaseModel): return tuple(rows) +class TipoCambio(BaseModel): + dia = DateField(default=util.now) + moneda = ForeignKeyField(SATMonedas) + tipo_cambio = DecimalField(max_digits=15, decimal_places=6, auto_round=True) + + class Meta: + order_by = ('-dia',) + + class Addendas(BaseModel): nombre = TextField(unique=True) addenda = TextField() @@ -1173,8 +1182,13 @@ class Productos(BaseModel): id = int(values.get('id', 0)) if id: row = (Productos - .select(Productos.id, Productos.clave, Productos.descripcion, - SATUnidades.name.alias('unidad'), Productos.valor_unitario) + .select( + Productos.id, + Productos.clave, + Productos.descripcion, + SATUnidades.name.alias('unidad'), + Productos.valor_unitario, + Productos.descuento) .join(SATUnidades).switch(Productos) .where(Productos.id==id).dicts()) if len(row): @@ -1330,7 +1344,8 @@ class Facturas(BaseModel): descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) moneda = TextField(default='MXN') - tipo_cambio = DecimalField(default=1.0, decimal_places=6, auto_round=True) + tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6, + auto_round=True) total = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_mn = DecimalField(default=0.0, max_digits=20, decimal_places=6, @@ -1714,29 +1729,43 @@ class Facturas(BaseModel): for product in products: id_product = product.pop('id') p = Productos.get(Productos.id==id_product) - #~ product['descripcion'] = p.descripcion + product['unidad'] = p.unidad.key product['clave'] = p.clave product['clave_sat'] = p.clave_sat product['factura'] = invoice.id product['producto'] = id_product - product['importe'] = round( - float(product['cantidad']) * float(product['valor_unitario']), 2) - subtotal += product['importe'] + + cantidad = float(product['cantidad']) + valor_unitario = float(product['valor_unitario']) + descuento = float(product['descuento']) + precio_final = valor_unitario - descuento + importe = round(cantidad * precio_final, 2) + + product['cantidad'] = cantidad + product['valor_unitario'] = valor_unitario + product['descuento'] = descuento + product['precio_final'] = precio_final + product['importe'] = round(cantidad * valor_unitario, 2) + + subtotal += importe FacturasDetalle.create(**product) + for tax in p.impuestos: if tax.id in totals_tax: - totals_tax[tax.id].importe += product['importe'] + #~ totals_tax[tax.id].importe += product['importe'] + totals_tax[tax.id].importe += importe else: - tax.importe = product['importe'] + #~ tax.importe = product['importe'] + tax.importe = importe totals_tax[tax.id] = tax - #~ totals_tax[tax.id]['importe'] = product['importe'] for tax in totals_tax.values(): if tax.tipo == 'E' or tax.tipo == 'R': continue + import_tax = round(float(tax.tasa) * tax.importe, 2) total_trasladados = (total_trasladados or 0) + import_tax if tax.name == 'IVA': @@ -1770,7 +1799,7 @@ class Facturas(BaseModel): total = subtotal + (total_trasladados or 0) - (total_retenciones or 0) total_mn = round(total * invoice.tipo_cambio, 2) data = { - 'subtotal': subtotal, + 'subtotal': subtotal + descuento, 'total': total, 'total_mn': total_mn, 'total_trasladados': total_trasladados, @@ -1868,6 +1897,7 @@ class Facturas(BaseModel): 'UsoCFDI': invoice.uso_cfdi, } + descuento = 0 conceptos = [] rows = FacturasDetalle.select().where(FacturasDetalle.factura==invoice) for row in rows: @@ -1881,6 +1911,9 @@ class Facturas(BaseModel): 'ValorUnitario': FORMAT.format(row.valor_unitario), 'Importe': FORMAT.format(row.importe), } + if row.descuento: + concepto['Descuento'] = FORMAT.format(row.descuento) + descuento += row.descuento taxes = {} traslados = [] @@ -1889,12 +1922,13 @@ class Facturas(BaseModel): for impuesto in row.producto.impuestos: if impuesto.tipo == 'E': continue - import_tax = round(impuesto.tasa * row.importe, 2) + base = row.importe - row.descuento + import_tax = round(impuesto.tasa * base, 2) tipo_factor = 'Tasa' if impuesto.factor != 'T': tipo_factor = 'Cuota' tax = { - "Base": FORMAT.format(row.importe), + "Base": FORMAT.format(base), "Impuesto": impuesto.key, "TipoFactor": tipo_factor, "TasaOCuota": str(impuesto.tasa), @@ -1912,6 +1946,9 @@ class Facturas(BaseModel): concepto['impuestos'] = taxes conceptos.append(concepto) + if descuento: + comprobante['Descuento'] = FORMAT.format(descuento) + impuestos = {} traslados = [] retenciones = [] @@ -2684,7 +2721,7 @@ def _crear_tablas(rfc): PreFacturasRelacionadas, SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes, SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos, - Socios, Tags, Usuarios, CuentasBanco, + Socios, Tags, Usuarios, CuentasBanco, TipoCambio, Emisor.regimenes.get_through_model(), Socios.tags.get_through_model(), Productos.impuestos.get_through_model(), diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 05e37ac..862b221 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -422,6 +422,7 @@ function guardar_y_timbrar(values){ delete rows[i]['unidad'] delete rows[i]['importe'] rows[i]['valor_unitario'] = parseFloat(rows[i]['valor_unitario']) + rows[i]['descuento'] = parseFloat(rows[i]['descuento']) } var data = new Object() @@ -687,7 +688,7 @@ function search_product_id_key_press(code, e){ function grid_details_before_edit_start(id){ - var columns = ['', 'descripcion', 'cantidad', 'valor_unitario'] + var columns = ['', 'descripcion', 'cantidad', 'valor_unitario', 'descuento'] if(!columns.indexOf(id.column)){ return !this.getItem(id.row)[id.column] } @@ -722,7 +723,8 @@ function grid_details_before_edit_stop(state, editor){ grid.unblockEvent() return true } - var valor_unitario = row['valor_unitario'] + var valor_unitario = parseFloat(row['valor_unitario']) + var descuento = parseFloat(row['descuento']) } if(editor.column == 'valor_unitario'){ @@ -736,10 +738,28 @@ function grid_details_before_edit_stop(state, editor){ grid.unblockEvent() return true } - var cantidad = row['cantidad'] + var cantidad = parseFloat(row['cantidad']) + var descuento = parseFloat(row['descuento']) } - row['importe'] = (cantidad * valor_unitario).round(DECIMALES) + if(editor.column == 'descuento'){ + var descuento = parseFloat(state.value) + if(isNaN(descuento)){ + msg = 'El descuento debe ser un número' + webix.message({type:'error', text: msg}) + grid.blockEvent() + state.value = state.old + grid.editCancel() + grid.unblockEvent() + return true + } + var cantidad = parseFloat(row['cantidad']) + var valor_unitario = parseFloat(row['valor_unitario']) + } + + var precio_final = valor_unitario - descuento + row['importe'] = (cantidad * precio_final).round(DECIMALES) + grid.refresh() calculate_taxes() } diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index ac69e57..b99c319 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -254,9 +254,14 @@ var grid_details_cols = [ {id: "unidad", header:{text: 'Unidad', css: 'center'}, width: 100}, {id: 'cantidad', header: {text: 'Cantidad', css: 'center'}, width: 100, format: webix.i18n.numberFormat, css:'right', editor: 'text'}, - {id: "valor_unitario", header:{text: 'Valor Unitario', css: 'center'}, width: 100, - format: webix.i18n.priceFormat, css:'right', editor: 'text'}, - {id: "importe", header:{text: 'Importe', css: 'center'}, width: 150, format: webix.i18n.priceFormat, css:'right'}, + {id: "valor_unitario", header:{text: 'Valor Unitario', css: 'center'}, + width: 100, format: webix.i18n.priceFormat, css:'right', editor: 'text'}, + {id: 'descuento', header:{text: 'Descuento', css: 'center'}, + width: 80, format: webix.i18n.priceFormat, css:'right', editor: 'text'}, + {id: 'precio_final', hidden: true, header: 'precio_final', width: 80, + format: webix.i18n.priceFormat, css:'right'}, + {id: "importe", header:{text: 'Importe', css: 'center'}, width: 150, + format: webix.i18n.priceFormat, css:'right'}, ] From 7da92890824a89356fe6de4202745218416216d0 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 12 Nov 2017 23:49:53 -0600 Subject: [PATCH 9/9] Descuentos en prefacturas --- source/app/controllers/util.py | 2 + source/app/models/main.py | 49 ++++++++++++++++++------- source/static/js/controller/invoices.js | 1 + 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index a73090d..a82ca4a 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -729,7 +729,9 @@ class LIBO(object): #~ Si no se encuentra, copia las celdas hacia abajo de #~ {subtotal.titulo} y {subtotal} + print (data['descuento']) if 'descuento' in data: + self._copy_cell(cell_title) self._copy_cell(cell_value) cell_title = self._set_cell(v='Descuento', cell=cell_title) diff --git a/source/app/models/main.py b/source/app/models/main.py index 2afdbc3..db6c618 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1721,6 +1721,7 @@ class Facturas(BaseModel): def _calculate_totals(self, invoice, products): subtotal = 0 + descuento_cfdi = 0 totals_tax = {} total_trasladados = None total_retenciones = None @@ -1749,6 +1750,7 @@ class Facturas(BaseModel): product['precio_final'] = precio_final product['importe'] = round(cantidad * valor_unitario, 2) + descuento_cfdi += descuento subtotal += importe FacturasDetalle.create(**product) @@ -1800,6 +1802,7 @@ class Facturas(BaseModel): total_mn = round(total * invoice.tipo_cambio, 2) data = { 'subtotal': subtotal + descuento, + 'descuento': descuento_cfdi, 'total': total, 'total_mn': total_mn, 'total_trasladados': total_trasladados, @@ -1832,6 +1835,7 @@ class Facturas(BaseModel): totals = cls._calculate_totals(cls, obj, productos) cls._guardar_relacionados(cls, obj, relacionados) obj.subtotal = totals['subtotal'] + obj.descuento = totals['descuento'] obj.total_trasladados = totals['total_trasladados'] obj.total_retenciones = totals['total_retenciones'] obj.total = totals['total'] @@ -1879,6 +1883,9 @@ class Facturas(BaseModel): comprobante['TipoDeComprobante'] = invoice.tipo_comprobante comprobante['MetodoPago'] = invoice.metodo_pago comprobante['LugarExpedicion'] = invoice.lugar_expedicion + if invoice.descuento: + comprobante['Descuento'] = FORMAT.format(invoice.descuento) + if invoice.tipo_relacion: relacionados = { 'tipo': invoice.tipo_relacion, @@ -1897,7 +1904,7 @@ class Facturas(BaseModel): 'UsoCFDI': invoice.uso_cfdi, } - descuento = 0 + #~ descuento = 0 conceptos = [] rows = FacturasDetalle.select().where(FacturasDetalle.factura==invoice) for row in rows: @@ -1913,7 +1920,7 @@ class Facturas(BaseModel): } if row.descuento: concepto['Descuento'] = FORMAT.format(row.descuento) - descuento += row.descuento + #~ descuento += row.descuento taxes = {} traslados = [] @@ -1946,8 +1953,8 @@ class Facturas(BaseModel): concepto['impuestos'] = taxes conceptos.append(concepto) - if descuento: - comprobante['Descuento'] = FORMAT.format(descuento) + #~ if descuento: + #~ comprobante['Descuento'] = FORMAT.format(descuento) impuestos = {} traslados = [] @@ -2224,6 +2231,9 @@ class PreFacturas(BaseModel): data['totales']['subtotal'] = str(data['comprobante']['subtotal']) data['totales']['total'] = str(data['comprobante']['total']) + if obj['descuento']: + data['totales']['descuento'] = float(obj['descuento']) + taxes = PreFacturasImpuestos.get_(id) data['totales']['traslados'] = taxes['traslados'] data['totales']['retenciones'] = taxes['retenciones'] @@ -2310,6 +2320,7 @@ class PreFacturas(BaseModel): def _calculate_totals(self, invoice, products): subtotal = 0 + descuento_cfdi = 0 totals_tax = {} total_trasladados = None total_retenciones = None @@ -2318,25 +2329,36 @@ class PreFacturas(BaseModel): for product in products: id_product = product.pop('id') p = Productos.get(Productos.id==id_product) - #~ product['descripcion'] = p.descripcion + product['unidad'] = p.unidad.key product['clave'] = p.clave product['clave_sat'] = p.clave_sat product['factura'] = invoice.id product['producto'] = id_product - product['importe'] = round( - float(product['cantidad']) * float(product['valor_unitario']), 2) - subtotal += product['importe'] + + cantidad = float(product['cantidad']) + valor_unitario = float(product['valor_unitario']) + descuento = float(product['descuento']) + precio_final = valor_unitario - descuento + importe = round(cantidad * precio_final, 2) + + product['cantidad'] = cantidad + product['valor_unitario'] = valor_unitario + product['descuento'] = descuento + product['precio_final'] = precio_final + product['importe'] = round(cantidad * valor_unitario, 2) + + descuento_cfdi += descuento + subtotal += importe PreFacturasDetalle.create(**product) 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 - #~ totals_tax[tax.id]['importe'] = product['importe'] for tax in totals_tax.values(): if tax.tipo == 'E' or tax.tipo == 'R': @@ -2374,7 +2396,8 @@ class PreFacturas(BaseModel): total = subtotal + (total_trasladados or 0) - (total_retenciones or 0) total_mn = round(total * invoice.tipo_cambio, 2) data = { - 'subtotal': subtotal, + 'subtotal': subtotal + descuento, + 'descuento': descuento_cfdi, 'total': total, 'total_mn': total_mn, 'total_trasladados': total_trasladados, @@ -2384,7 +2407,6 @@ class PreFacturas(BaseModel): @classmethod def add(cls, values): - print ('VALUES', values) productos = util.loads(values.pop('productos')) emisor = Emisor.select()[0] @@ -2397,6 +2419,7 @@ class PreFacturas(BaseModel): obj = PreFacturas.create(**values) totals = cls._calculate_totals(cls, obj, productos) obj.subtotal = totals['subtotal'] + obj.descuento = totals['descuento'] obj.total_trasladados = totals['total_trasladados'] obj.total_retenciones = totals['total_retenciones'] obj.total = totals['total'] diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 862b221..ddf2a1b 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -1045,6 +1045,7 @@ function cmd_prefactura_click(){ delete rows[i]['unidad'] delete rows[i]['importe'] rows[i]['valor_unitario'] = parseFloat(rows[i]['valor_unitario']) + rows[i]['descuento'] = parseFloat(rows[i]['descuento']) } var data = new Object()