From 4eb4cc8fa58b589e6293658460bec2d84f3637a0 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Wed, 10 Oct 2018 22:25:10 -0500 Subject: [PATCH] Error #287 --- CHANGELOG.md | 8 + VERSION | 2 +- docs/empresalibre/docs/notas.md | 9 +- source/app/controllers/main.py | 20 +- source/app/main.py | 4 +- source/app/models/db.py | 10 +- source/app/models/main.py | 457 ++++++++++++++------------ source/app/settings.py | 2 +- source/static/js/controller/bancos.js | 30 +- source/static/js/controller/util.js | 13 +- 10 files changed, 318 insertions(+), 237 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 108a56d..0b1b5fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +v 1.21.0 [11-oct-2018] +---------------------- + - Error #287 + - Mejora: Complemento de pago con datos de cuentas + +* IMPORTANTE: Es necesario realizar una migración, despues de actualizar. + + v 1.20.0 [08-oct-2018] ---------------------- - Error #295 diff --git a/VERSION b/VERSION index 3989355..3500250 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.20.0 +1.21.0 diff --git a/docs/empresalibre/docs/notas.md b/docs/empresalibre/docs/notas.md index a16e9e0..e2f225f 100644 --- a/docs/empresalibre/docs/notas.md +++ b/docs/empresalibre/docs/notas.md @@ -6,11 +6,18 @@ siempre actualizado.** Solo se da soporte sobre la ultima versión de **Empresa Libre**. +### 1.21.0 [11-oct-2018] +- Error [#287](https://gitlab.com/mauriciobaeza/empresa-libre/issues/287) +- Mejora: Complemento de pago con datos de cuentas + +* IMPORTANTE: Es necesario realizar una migración, despues de actualizar. + + ### 1.20.0 [08-oct-2018] - Error [#295](https://gitlab.com/mauriciobaeza/empresa-libre/issues/295) - Mejora - Cuentas de banco para clientes -* IMPORTANTE: Es necesario realizar una migración, despues de actualizar la rama principal. +* IMPORTANTE: Es necesario realizar una migración, despues de actualizar. ### 1.19.1 [03-oct-2018] diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 41a169c..353f7ad 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -552,12 +552,28 @@ class AppSATBancos(object): def on_get(self, req, resp): values = req.params - req.context['result'] = self._db.get_satbancos(values) + req.context['result'] = self._db.get_sat_bancos(values) resp.status = falcon.HTTP_200 def on_post(self, req, resp): values = req.params - req.context['result'] = self._db.satbancos(values) + req.context['result'] = self._db.sat_bancos(values) + resp.status = falcon.HTTP_200 + + +class AppSATFormaPago(object): + + def __init__(self, db): + self._db = db + + def on_get(self, req, resp): + values = req.params + req.context['result'] = self._db.get_sat_forma_pago(values) + resp.status = falcon.HTTP_200 + + def on_post(self, req, resp): + values = req.params + req.context['result'] = self._db.sat_forma_pago(values) resp.status = falcon.HTTP_200 diff --git a/source/app/main.py b/source/app/main.py index 66298f2..ec12f0c 100644 --- a/source/app/main.py +++ b/source/app/main.py @@ -17,7 +17,8 @@ from controllers.main import (AppEmpresas, AppMain, AppValues, AppPartners, AppProducts, AppInvoices, AppFolios, AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco, AppMovimientosBanco, AppTickets, AppStudents, AppEmployees, AppNomina, - AppInvoicePay, AppCfdiPay, AppSATBancos, AppSociosCuentasBanco + AppInvoicePay, AppCfdiPay, AppSATBancos, AppSociosCuentasBanco, + AppSATFormaPago ) @@ -59,6 +60,7 @@ api.add_route('/nomina', AppNomina(db)) api.add_route('/invoicepay', AppInvoicePay(db)) api.add_route('/cfdipay', AppCfdiPay(db)) api.add_route('/satbancos', AppSATBancos(db)) +api.add_route('/satformapago', AppSATFormaPago(db)) api.add_route('/socioscb', AppSociosCuentasBanco(db)) diff --git a/source/app/models/db.py b/source/app/models/db.py index 45a8108..a3de267 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -436,9 +436,12 @@ class StorageEngine(object): def get_cfdipay(self, values): return main.CfdiPagos.get_values(values) - def get_satbancos(self, values): + def get_sat_bancos(self, values): return main.SATBancos.get_values(values) + def get_sat_forma_pago(self, values): + return main.SATFormaPago.get_values(values) + def get_partners_accounts_bank(self, values): return main.SociosCuentasBanco.get_values(values) @@ -448,8 +451,11 @@ class StorageEngine(object): def bankmovement(self, values): return main.MovimientosBanco.post(values) - def satbancos(self, values): + def sat_bancos(self, values): return main.SATBancos.post(values) + def sat_forma_pago(self, values): + return main.SATFormaPago.post(values) + def partners_accounts_bank(self, values): return main.SociosCuentasBanco.post(values) diff --git a/source/app/models/main.py b/source/app/models/main.py index ee1a788..fcfbb0e 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1244,6 +1244,31 @@ class SATFormaPago(BaseModel): def __str__(self): return 'Forma de pago: ({}) {}'.format(self.key, self.name) + @classmethod + def get_values(cls, values): + opt = values.pop('opt') + return getattr(cls, '_get_{}'.format(opt))(cls, values) + + def _get_active_by_id(self, values): + rows = (SATFormaPago + .select( + SATFormaPago.id, + SATFormaPago.name.alias('value')) + .where(SATFormaPago.activo==True) + .dicts() + ) + return tuple(rows) + + def _get_active_by_key(self, values): + rows = (SATFormaPago + .select( + SATFormaPago.key.alias('id'), + SATFormaPago.name.alias('value')) + .where(SATFormaPago.activo==True) + .dicts() + ) + return tuple(rows) + @classmethod def get_(self): rows = SATFormaPago.select().dicts() @@ -1878,218 +1903,6 @@ class CuentasBanco(BaseModel): return data -class MovimientosBanco(BaseModel): - cuenta = ForeignKeyField(CuentasBanco) - fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) - descripcion = TextField(default='') - forma_pago = ForeignKeyField(SATFormaPago) - retiro = DecimalField(default=0.0, max_digits=20, decimal_places=6, - auto_round=True) - deposito = DecimalField(default=0.0, max_digits=20, decimal_places=6, - auto_round=True) - saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, - auto_round=True) - 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) - numero_operacion = TextField(default='') - origen_rfc = TextField(default='') - origen_nombre = TextField(default='') - origen_cuenta = TextField(default='') - destino_rfc = TextField(default='') - destino_cuenta = TextField(default='') - tipo_cadena_pago = TextField(default='') - certificado_pago = TextField(default='') - cadena_pago = TextField(default='') - sello_pago = TextField(default='') - - class Meta: - order_by = ('fecha', 'id') - - def _ultimo_saldo(self, cuenta, fecha): - query = (MovimientosBanco - .select() - .where( - (MovimientosBanco.cuenta==cuenta) & - (MovimientosBanco.fecha<=fecha) & - (MovimientosBanco.cancelado==False))[-1] - ) - return round(float(query.saldo), DECIMALES) - - def _movimiento_anterior(self, cuenta, fecha): - query = (MovimientosBanco - .select() - .where( - (MovimientosBanco.cuenta==cuenta) & - (MovimientosBanco.fecharow.fecha) & - (MovimientosBanco.cancelado==False)) - ) - - saldo = round(Decimal(row.saldo), DECIMALES) - for mov in query: - mov.saldo = saldo + mov.deposito - mov.retiro - mov.save() - saldo = mov.saldo - CuentasBanco.actualizar_saldo(row.cuenta, saldo) - return saldo - - @classmethod - def post(cls, values): - opt = values.pop('opt') - return getattr(cls, '_{}'.format(opt))(cls, values) - - def _add(self, values): - ids = values.pop('ids', '') - - if ids and not Facturas.validate_count_partners(util.loads(ids)): - msg = 'Facturas relacionadas a diferentes clientes' - data = {'ok': False, 'msg': msg} - return data - - actualizar = False - if 'saldo' in values: - saldo = values['saldo'] - else: - actualizar = True - hora = values.pop('hora') - account = CuentasBanco.get(CuentasBanco.id==int(values['cuenta'])) - values['fecha'] = '{}T{}'.format(values['fecha'][:10], hora) - values['cuenta'] = account - values['moneda'] = account.moneda.key - values['retiro'] = util.get_float(values['retiro']) - values['deposito'] = util.get_float(values['deposito']) - values['tipo_cambio'] = util.get_float( - values.get('tipo_cambio', 1.00), True) - values['forma_pago'] = int(values['forma_pago']) - - ultimo_saldo = self._ultimo_saldo( - self, values['cuenta'], values['fecha']) - values['saldo'] = \ - ultimo_saldo - values['retiro'] + values['deposito'] - - with database_proxy.transaction(): - try: - obj = MovimientosBanco.create(**values) - except IntegrityError: - msg = 'Este movimiento ya existe' - return {'ok': False, 'msg': msg} - - if actualizar: - saldo = self._actualizar_saldos(self, obj) - if ids: - FacturasPagos.add(obj, util.loads(ids)) - - return {'ok': True, 'saldo': saldo} - - def _cancel(self, values): - id = int(values['id']) - try: - obj = MovimientosBanco.get(MovimientosBanco.id==id) - except MovimientosBanco.DoesNotExist: - msg = 'No se encontró el movimiento' - return {'ok': False, 'msg': msg} - - if obj.cancelado: - msg = 'El movimiento ya esta cancelado' - return {'ok': False, 'msg': msg} - - if obj.conciliado: - msg = 'El movimiento esta conciliado, no se puede cancelar' - return {'ok': False, 'msg': msg} - - # ~ filters = (CfdiPagos.movimiento==obj) - # ~ cp = CfdiPagos.select().where(filters).count() - # ~ if cp > 0: - # ~ msg = 'El movimiento tiene Factura de Pago, no se puede cancelar' - # ~ return {'ok': False, 'msg': msg} - - with database_proxy.transaction(): - obj.cancelado = True - obj.save() - FacturasPagos.cancelar(obj) - - obj = self._movimiento_anterior(self, obj.cuenta, obj.fecha) - self._actualizar_saldos(self, obj) - - balance = CuentasBanco.get_saldo(obj.cuenta.id) - - msg = 'Movimiento cancelado correctamente' - return {'ok': True, 'msg': msg, 'balance': balance} - - @classmethod - def con(cls, id): - cant = (MovimientosBanco - .select(MovimientosBanco.id) - .where(MovimientosBanco.cuenta==id) - .count() - ) - if cant > 2: - return {'ok': True} - - return {'ok': False} - - - @classmethod - def get_(cls, values): - cuenta = int(values['cuenta']) - if 'fechas' in values: - rango = util.loads(values['fechas']) - fd = (MovimientosBanco.fecha.between( - util.get_date(rango['start']), - util.get_date(rango['end'], True))) - filtros = (fd & - (MovimientosBanco.cuenta==cuenta) & - (MovimientosBanco.cancelado==False) - ) - else: - year = int(values['year']) - mes = int(values['mes']) - if year == -1: - fy = (MovimientosBanco.fecha.year > 0) - else: - fy = (MovimientosBanco.fecha.year == year) - if mes == -1: - fm = (MovimientosBanco.fecha.month > 0) - else: - fm = (MovimientosBanco.fecha.month == mes) - filtros = (fy & fm & - (MovimientosBanco.cuenta==cuenta) & - (MovimientosBanco.cancelado==False) - ) - - rows = tuple(MovimientosBanco.select( - MovimientosBanco.id, - MovimientosBanco.fecha, - MovimientosBanco.numero_operacion, - SATFormaPago.name.alias('way_payment'), - MovimientosBanco.descripcion, - MovimientosBanco.retiro, - MovimientosBanco.deposito, - MovimientosBanco.saldo, - fn.COUNT(CfdiPagos.id).alias('invoice') - ) - .join(SATFormaPago).switch(MovimientosBanco) - .join(CfdiPagos, JOIN.LEFT_OUTER).switch(MovimientosBanco) - .where(filtros) - .group_by(MovimientosBanco.id, SATFormaPago.name) - .dicts() - ) - - return {'ok': True, 'rows': rows} - - class SATUsoCfdi(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) @@ -2853,6 +2666,215 @@ class ContactoCorreos(BaseModel): ) +class MovimientosBanco(BaseModel): + cuenta = ForeignKeyField(CuentasBanco) + fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) + descripcion = TextField(default='') + forma_pago = ForeignKeyField(SATFormaPago) + retiro = DecimalField(default=0.0, max_digits=20, decimal_places=6, + auto_round=True) + deposito = DecimalField(default=0.0, max_digits=20, decimal_places=6, + auto_round=True) + saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, + auto_round=True) + 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) + numero_operacion = TextField(default='') + cuenta_socio = ForeignKeyField(SociosCuentasBanco, null=True) + tipo_cadena_pago = TextField(default='') + certificado_pago = TextField(default='') + cadena_pago = TextField(default='') + sello_pago = TextField(default='') + + class Meta: + order_by = ('fecha', 'id') + + def _ultimo_saldo(self, cuenta, fecha): + query = (MovimientosBanco + .select() + .where( + (MovimientosBanco.cuenta==cuenta) & + (MovimientosBanco.fecha<=fecha) & + (MovimientosBanco.cancelado==False))[-1] + ) + return round(float(query.saldo), DECIMALES) + + def _movimiento_anterior(self, cuenta, fecha): + query = (MovimientosBanco + .select() + .where( + (MovimientosBanco.cuenta==cuenta) & + (MovimientosBanco.fecharow.fecha) & + (MovimientosBanco.cancelado==False)) + ) + + saldo = round(Decimal(row.saldo), DECIMALES) + for mov in query: + mov.saldo = saldo + mov.deposito - mov.retiro + mov.save() + saldo = mov.saldo + CuentasBanco.actualizar_saldo(row.cuenta, saldo) + return saldo + + @classmethod + def post(cls, values): + opt = values.pop('opt') + return getattr(cls, '_{}'.format(opt))(cls, values) + + def _add(self, values): + ids = values.pop('ids', '') + + if ids and not Facturas.validate_count_partners(util.loads(ids)): + msg = 'Facturas relacionadas a diferentes clientes' + data = {'ok': False, 'msg': msg} + return data + + actualizar = False + if 'saldo' in values: + saldo = values['saldo'] + else: + actualizar = True + hora = values.pop('hora') + account = CuentasBanco.get(CuentasBanco.id==int(values['cuenta'])) + values['fecha'] = '{}T{}'.format(values['fecha'][:10], hora) + values['cuenta'] = account + values['moneda'] = account.moneda.key + values['retiro'] = util.get_float(values['retiro']) + values['deposito'] = util.get_float(values['deposito']) + values['tipo_cambio'] = util.get_float( + values.get('tipo_cambio', 1.00), True) + values['forma_pago'] = int(values['forma_pago']) + + ultimo_saldo = self._ultimo_saldo( + self, values['cuenta'], values['fecha']) + + values['saldo'] = \ + ultimo_saldo - values['retiro'] + values['deposito'] + + with database_proxy.transaction(): + try: + obj = MovimientosBanco.create(**values) + except IntegrityError: + msg = 'Error al agregar el movimiento' + return {'ok': False, 'msg': msg} + + if actualizar: + saldo = self._actualizar_saldos(self, obj) + if ids: + FacturasPagos.add(obj, util.loads(ids)) + + return {'ok': True, 'saldo': saldo} + + def _cancel(self, values): + id = int(values['id']) + try: + obj = MovimientosBanco.get(MovimientosBanco.id==id) + except MovimientosBanco.DoesNotExist: + msg = 'No se encontró el movimiento' + return {'ok': False, 'msg': msg} + + if obj.cancelado: + msg = 'El movimiento ya esta cancelado' + return {'ok': False, 'msg': msg} + + if obj.conciliado: + msg = 'El movimiento esta conciliado, no se puede cancelar' + return {'ok': False, 'msg': msg} + + # ~ filters = (CfdiPagos.movimiento==obj) + # ~ cp = CfdiPagos.select().where(filters).count() + # ~ if cp > 0: + # ~ msg = 'El movimiento tiene Factura de Pago, no se puede cancelar' + # ~ return {'ok': False, 'msg': msg} + + with database_proxy.transaction(): + obj.cancelado = True + obj.save() + FacturasPagos.cancelar(obj) + + obj = self._movimiento_anterior(self, obj.cuenta, obj.fecha) + self._actualizar_saldos(self, obj) + + balance = CuentasBanco.get_saldo(obj.cuenta.id) + + msg = 'Movimiento cancelado correctamente' + return {'ok': True, 'msg': msg, 'balance': balance} + + @classmethod + def con(cls, id): + cant = (MovimientosBanco + .select(MovimientosBanco.id) + .where(MovimientosBanco.cuenta==id) + .count() + ) + if cant > 2: + return {'ok': True} + + return {'ok': False} + + + @classmethod + def get_(cls, values): + cuenta = int(values['cuenta']) + if 'fechas' in values: + rango = util.loads(values['fechas']) + fd = (MovimientosBanco.fecha.between( + util.get_date(rango['start']), + util.get_date(rango['end'], True))) + filtros = (fd & + (MovimientosBanco.cuenta==cuenta) & + (MovimientosBanco.cancelado==False) + ) + else: + year = int(values['year']) + mes = int(values['mes']) + if year == -1: + fy = (MovimientosBanco.fecha.year > 0) + else: + fy = (MovimientosBanco.fecha.year == year) + if mes == -1: + fm = (MovimientosBanco.fecha.month > 0) + else: + fm = (MovimientosBanco.fecha.month == mes) + filtros = (fy & fm & + (MovimientosBanco.cuenta==cuenta) & + (MovimientosBanco.cancelado==False) + ) + + rows = tuple(MovimientosBanco.select( + MovimientosBanco.id, + MovimientosBanco.fecha, + MovimientosBanco.numero_operacion, + SATFormaPago.name.alias('way_payment'), + MovimientosBanco.descripcion, + MovimientosBanco.retiro, + MovimientosBanco.deposito, + MovimientosBanco.saldo, + fn.COUNT(CfdiPagos.id).alias('invoice') + ) + .join(SATFormaPago).switch(MovimientosBanco) + .join(CfdiPagos, JOIN.LEFT_OUTER).switch(MovimientosBanco) + .where(filtros) + .group_by(MovimientosBanco.id, SATFormaPago.name) + .dicts() + ) + + return {'ok': True, 'rows': rows} + + class Alumnos(BaseModel): rfc = TextField(null=True) curp = TextField(index=True, unique=True) @@ -8392,6 +8414,17 @@ def _migrate_tables(rfc=''): fecha_cancelacion = DateTimeField(null=True) migrations.append(migrator.add_column('cfdipagos', 'fecha_cancelacion', fecha_cancelacion)) + table = 'movimientosbanco' + columns = [c.name for c in database_proxy.get_columns(table)] + if not 'cuenta_socio' in columns: + cuenta_socio = ForeignKeyField(SociosCuentasBanco, null=True, to_field=SociosCuentasBanco.id) + migrations.append(migrator.add_column(table, 'socioscuentasbanco_id', cuenta_socio)) + migrations.append(migrator.drop_column(table, 'origen_rfc')) + migrations.append(migrator.drop_column(table, 'origen_nombre')) + migrations.append(migrator.drop_column(table, 'origen_cuenta')) + migrations.append(migrator.drop_column(table, 'destino_rfc')) + migrations.append(migrator.drop_column(table, 'destino_cuenta')) + if migrations: with database_proxy.atomic() as txn: migrate(*migrations) diff --git a/source/app/settings.py b/source/app/settings.py index 764f9e9..fc4d0e9 100644 --- a/source/app/settings.py +++ b/source/app/settings.py @@ -47,7 +47,7 @@ except ImportError: DEBUG = DEBUG -VERSION = '1.20.0' +VERSION = '1.21.0' EMAIL_SUPPORT = ('soporte@empresalibre.net',) TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION) diff --git a/source/static/js/controller/bancos.js b/source/static/js/controller/bancos.js index db6e78e..2f5c319 100644 --- a/source/static/js/controller/bancos.js +++ b/source/static/js/controller/bancos.js @@ -206,18 +206,18 @@ function lst_cuentas_banco_change(nv, ov){ } -function get_bancos_forma_pago(retiro){ - var values = table_waypayment.chain().find({'id': { '$ne' : '99' }}).data() - if(retiro){ - lst = $$('lst_retiro_forma_pago') - }else{ - lst = $$('lst_deposito_forma_pago') - } - lst.getList().parse(values) - if(current_way_payment){ - lst.setValue(current_way_payment) - } -} +//~ function get_bancos_forma_pago(retiro){ + //~ var values = table_waypayment.chain().find({'id': { '$ne' : '99' }}).data() + //~ if(retiro){ + //~ lst = $$('lst_retiro_forma_pago') + //~ }else{ + //~ lst = $$('lst_deposito_forma_pago') + //~ } + //~ lst.getList().parse(values) + //~ if(current_way_payment){ + //~ lst.setValue(current_way_payment) + //~ } +//~ } function get_facturas_por_pagar(){ @@ -246,7 +246,8 @@ function get_facturas_por_pagar(){ function cmd_agregar_retiro_click(){ - get_bancos_forma_pago(true) + //~ get_bancos_forma_pago(true) + set_way_payment('lst_retiro_forma_pago', true, current_way_payment) var title = 'Agregar retiro de banco a la cuenta ' + $$('lst_cuentas_banco').getText() + ' en ' + $$('txt_cuenta_moneda').getValue() $$('title_bank_retiro').setValue(title) $$('multi_bancos').setValue('banco_retiro') @@ -255,8 +256,9 @@ function cmd_agregar_retiro_click(){ function cmd_agregar_deposito_click(){ msg_importe = '' - get_bancos_forma_pago(false) + //~ get_bancos_forma_pago(false) get_facturas_por_pagar() + set_way_payment('lst_deposito_forma_pago', true, current_way_payment) var g = $$('grid_cfdi_este_deposito') g.config.columns[g.getColumnIndex('importe')].header = 'Este Pago ' + current_currency diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index 921a933..e5f2fee 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -502,7 +502,7 @@ function get_forma_pago(control){ function get_way_payment(){ - webix.ajax().get('/values/formapago', {key: true}, function(text, data){ + webix.ajax().get('/satformapago', {opt: 'active_by_id'}, function(text, data){ var values = data.json() table_waypayment.clear() table_waypayment.insert(values) @@ -510,9 +510,16 @@ function get_way_payment(){ } -function set_way_payment(control){ - var values = table_waypayment.chain().data() +function set_way_payment(control, filter99=false, current_way_payment=''){ + if(filter99){ + var values = table_waypayment.chain().find({'value': { '$ne' : 'Por definir' }}).data() + }else{ + var values = table_waypayment.chain().data() + } $$(control).getList().parse(values) + if(current_way_payment){ + $$(control).setValue(current_way_payment) + } }