From e795494ec33647f4217ad25dcf25340e195002d0 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 5 Nov 2017 00:13:48 -0600 Subject: [PATCH] Generar prefactura --- source/app/controllers/main.py | 23 +++ source/app/main.py | 3 +- source/app/models/db.py | 15 +- source/app/models/main.py | 175 +++++++++++++++++++++ source/static/js/controller/invoices.js | 199 +++++++++++++++++++++++- source/static/js/controller/main.js | 18 ++- source/static/js/ui/invoices.js | 21 +-- 7 files changed, 441 insertions(+), 13 deletions(-) diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 88b911b..a5f03e5 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -189,6 +189,29 @@ class AppInvoices(object): resp.status = falcon.HTTP_204 +class AppPreInvoices(object): + + def __init__(self, db): + self._db = db + + def on_get(self, req, resp): + values = req.params + req.context['result'] = self._db.get_preinvoices(values) + resp.status = falcon.HTTP_200 + + def on_post(self, req, resp): + values = req.params + req.context['result'] = self._db.preinvoice(values) + resp.status = falcon.HTTP_200 + + def on_delete(self, req, resp): + values = req.params + if self._db.delete('preinvoice', values['id']): + resp.status = falcon.HTTP_200 + else: + resp.status = falcon.HTTP_204 + + class AppEmisor(object): def __init__(self, db): diff --git a/source/app/main.py b/source/app/main.py index 9329f90..e6a6710 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 + AppDocumentos, AppFiles, AppPreInvoices ) from settings import DEBUG @@ -44,6 +44,7 @@ api.add_route('/doc/{type_doc}/{id_doc}', AppDocumentos(db)) api.add_route('/partners', AppPartners(db)) api.add_route('/products', AppProducts(db)) api.add_route('/invoices', AppInvoices(db)) +api.add_route('/preinvoices', AppPreInvoices(db)) if DEBUG: diff --git a/source/app/models/db.py b/source/app/models/db.py index dc4138e..7dbffb5 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -45,7 +45,9 @@ class StorageEngine(object): return main.Facturas.get_status_sat(values['id']) def _get_filteryears(self, values): - return main.Facturas.filter_years() + years1 = main.Facturas.filter_years() + years2 = main.PreFacturas.filter_years() + return [years1, years2] def _get_cert(self, values): return main.Certificado.get_data() @@ -101,6 +103,8 @@ class StorageEngine(object): return main.Facturas.remove(id) if table == 'folios': return main.Folios.remove(id) + if table == 'preinvoice': + return main.PreFacturas.remove(id) return False def _get_client(self, values): @@ -133,9 +137,18 @@ class StorageEngine(object): return main.Facturas.actualizar(values, id) return main.Facturas.add(values) + def preinvoice(self, values): + id = int(values.pop('id', '0')) + #~ if id: + #~ return main.PreFacturas.actualizar(values, id) + return main.PreFacturas.add(values) + def get_invoices(self, values): return main.Facturas.get_(values) + def get_preinvoices(self, values): + return main.PreFacturas.get_(values) + def _get_timbrar(self, values): return main.Facturas.timbrar(int(values['id'])) diff --git a/source/app/models/main.py b/source/app/models/main.py index b4039f2..7a1f0bb 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1655,6 +1655,181 @@ class PreFacturas(BaseModel): class Meta: order_by = ('fecha',) + @classmethod + def remove(cls, id): + obj = PreFacturas.get(PreFacturas.id==id) + + q = PreFacturasDetalle.delete().where( + PreFacturasDetalle.factura==obj) + q.execute() + q = PreFacturasImpuestos.delete().where( + PreFacturasImpuestos.factura==obj) + q.execute() + q = PreFacturasRelacionadas.delete().where( + PreFacturasRelacionadas.factura==obj) + q.execute() + return bool(obj.delete_instance()) + + @classmethod + def filter_years(cls): + data = [{'id': -1, 'value': 'Todos'}] + rows = (PreFacturas + .select(PreFacturas.fecha.year) + .group_by(PreFacturas.fecha.year) + .order_by(PreFacturas.fecha.year) + .scalar(as_tuple=True) + ) + if not rows is None: + data += [{'id': int(row), 'value': int(row)} for row in rows] + return tuple(data) + + @classmethod + def get_(cls, values): + if values['year'] == '-1': + fy = (PreFacturas.fecha.year > 0) + else: + fy = (PreFacturas.fecha.year == int(values['year'])) + if values['month'] == '-1': + fm = (PreFacturas.fecha.month > 0) + else: + fm = (PreFacturas.fecha.month == int(values['month'])) + filters = (fy & fm) + + rows = tuple(PreFacturas + .select( + PreFacturas.id, + PreFacturas.folio, + PreFacturas.fecha, + PreFacturas.tipo_comprobante, + PreFacturas.total_mn, + Socios.nombre.alias('cliente')) + .where(filters) + .join(Socios) + .switch(PreFacturas).dicts() + ) + return {'ok': True, 'rows': rows} + + def _get_folio(self, serie): + inicio = (PreFacturas + .select(fn.Max(PreFacturas.folio).alias('mf')) + .where(PreFacturas.serie==serie) + .order_by(SQL('mf')) + .scalar()) + + if inicio is None: + inicio = 1 + else: + inicio += 1 + + return inicio + + def _calculate_totals(self, invoice, products): + subtotal = 0 + totals_tax = {} + total_trasladados = None + total_retenciones = None + total_iva = 0 + + 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'] + + PreFacturasDetalle.create(**product) + for tax in p.impuestos: + if tax.id in totals_tax: + totals_tax[tax.id].importe += product['importe'] + else: + tax.importe = product['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': + total_iva += import_tax + + invoice_tax = { + 'factura': invoice.id, + 'impuesto': tax.id, + 'base': tax.importe, + 'importe': import_tax, + } + PreFacturasImpuestos.create(**invoice_tax) + + for tax in totals_tax.values(): + if tax.tipo == 'E' or tax.tipo == 'T': + continue + if tax.tasa == round(Decimal(2/3), 6): + import_tax = round(float(tax.tasa) * total_iva, 2) + else: + import_tax = round(float(tax.tasa) * tax.importe, 2) + total_retenciones = (total_retenciones or 0) + import_tax + + invoice_tax = { + 'factura': invoice.id, + 'impuesto': tax['id'], + 'base': tax['importe'], + 'importe': import_tax, + } + PreFacturasImpuestos.create(**invoice_tax) + + total = subtotal + (total_trasladados or 0) - (total_retenciones or 0) + total_mn = round(total * invoice.tipo_cambio, 2) + data = { + 'subtotal': subtotal, + 'total': total, + 'total_mn': total_mn, + 'total_trasladados': total_trasladados, + 'total_retenciones': total_retenciones, + } + return data + + @classmethod + def add(cls, values): + print ('VALUES', values) + productos = util.loads(values.pop('productos')) + + emisor = Emisor.select()[0] + values['serie'] = 'PRE' + 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 + + with database_proxy.atomic() as txn: + obj = PreFacturas.create(**values) + totals = cls._calculate_totals(cls, obj, productos) + obj.subtotal = totals['subtotal'] + obj.total_trasladados = totals['total_trasladados'] + obj.total_retenciones = totals['total_retenciones'] + obj.total = totals['total'] + obj.total_mn = totals['total_mn'] + obj.save() + + msg = 'Factura guardada correctamente' + row = { + 'id': obj.id, + 'folio': obj.folio, + 'fecha': obj.fecha, + 'tipo_comprobante': obj.tipo_comprobante, + 'total_mn': obj.total_mn, + 'cliente': obj.cliente.nombre, + } + data = {'ok': True, 'row': row, 'new': True, 'error': False, 'msg': msg} + return data + class FacturasRelacionadas(BaseModel): factura = ForeignKeyField(Facturas, related_name='original') diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 5733ff9..a936824 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -293,6 +293,38 @@ function save_invoice(data){ } +function save_preinvoice(data){ + var result = false + var values = NaN + + webix.ajax().sync().post('preinvoices', data, { + error:function(text, data, XmlHttpRequest){ + msg = 'Ocurrio un error, consulta a soporte técnico' + msg_error(msg) + }, + success:function(text, data, XmlHttpRequest){ + values = data.json(); + if(values.ok){ + msg_sucess('Pre Factura generada correctamente') + result = true + }else{ + msg_error(values.msg) + } + } + }) + + if(result){ + table_pt.clear() + table_totals.clear() + grid.clearAll() + $$('grid_totals').clearAll() + + } + + return result +} + + function cmd_timbrar_click(id, e, node){ var form = this.getFormView(); @@ -830,6 +862,23 @@ function cmd_invoice_sat_click(){ } +function reset_invoice(){ + var form = $$('form_invoice') + var grid_totals = $$('grid_totals') + + form.adjust() + form.setValues({id: 0, id_partner: 0, lbl_client: 'Ninguno'}) + grid.clearAll() + grid_totals.clearAll() + grid_totals.add({id: 1, concepto: 'SubTotal', importe: 0}) + + table_pt.clear() + table_totals.clear() + form.focus('search_client_name') + +} + + function cmd_prefactura_click(){ var form = this.getFormView() @@ -842,7 +891,36 @@ function cmd_prefactura_click(){ if(!validate_invoice(values)){ return } - show('PreFactura') + + var rows = grid.data.getRange() + for (i = 0; i < rows.length; i++) { + delete rows[i]['delete'] + delete rows[i]['clave'] + delete rows[i]['unidad'] + delete rows[i]['importe'] + rows[i]['valor_unitario'] = parseFloat(rows[i]['valor_unitario']) + } + + var data = new Object() + data['id'] = values.id + data['cliente'] = values.id_partner + data['productos'] = rows + data['serie'] = $$('lst_serie').getText() + data['forma_pago'] = $$('lst_forma_pago').getValue() + data['condiciones_pago'] = $$('txt_condicion_pago').getValue().trim() + data['moneda'] = $$('lst_moneda').getValue() + data['tipo_cambio'] = $$('txt_tipo_cambio').getValue() + data['tipo_comprobante'] = $$('lst_tipo_comprobante').getValue() + data['metodo_pago'] = $$('lst_metodo_pago').getValue() + data['uso_cfdi'] = $$('lst_uso_cfdi').getValue() + data['regimen_fiscal'] = $$('lst_regimen_fiscal').getValue() + + if(!save_preinvoice(data)){ + return + } + + reset_invoice() + $$('tv_invoice').getTabbar().setValue('PreFacturas') } @@ -851,3 +929,122 @@ function lst_metodo_pago_change(nv, ov){ $$('lst_forma_pago').setValue('99') } } + + +function get_prefacturas(){ + var fy = $$('prefilter_year') + var fm = $$('prefilter_month') + + var y = fy.getValue() + var m = fm.getValue() + rango = {'year': y, 'month': m} + + var grid = $$('grid_preinvoices') + webix.ajax().get('/preinvoices', rango, { + error: function(text, data, xhr) { + webix.message({type: 'error', text: 'Error al consultar'}) + }, + success: function(text, data, xhr) { + var values = data.json(); + grid.clearAll(); + if (values.ok){ + grid.parse(values.rows, 'json') + } + } + }) +} + + +function tb_invoice_change(nv, ov){ + if(nv == 'PreFacturas'){ + get_prefacturas() + } +} + + +function prefilter_year_change(nv, ov){ + get_prefacturas() +} + + +function prefilter_month_change(nv, ov){ + get_prefacturas() +} + + +function delete_preinvoice(id){ + webix.ajax().del('/preinvoices', {id: id}, function(text, xml, xhr){ + if(xhr.status == 200){ + $$('grid_preinvoices').remove(id) + msg_sucess('PreFactura eliminada correctamente') + }else{ + msg_error('No se pudo eliminar') + } + }) +} + + +function cmd_delete_preinvoice_click(id, e, node){ + var grid = $$('grid_preinvoices') + + if(grid.count() == 0){ + return + } + + var row = grid.getSelectedItem() + if (row == undefined){ + msg_error('Selecciona una prefactura') + return + } + + var msg = '¿Estás seguro de eliminar la siguiente PREFactura?

' + msg += '(' + row['folio'] + ') ' + row['cliente'] + msg += '

ESTA ACCIÓN NO SE PUEDE DESHACER' + webix.confirm({ + title:'Eliminar Pre Factura', + ok:'Si', + cancel:'No', + type:'confirm-error', + text:msg, + callback:function(result){ + if (result){ + delete_preinvoice(row['id']) + } + } + }) +} + + +function refacturar_preinvoice(id){ + $$('tv_invoice').getTabbar().setValue('Generar') +} + + +function cmd_facturar_preinvoice_click(id, e, node){ + var grid = $$('grid_preinvoices') + + if(grid.count() == 0){ + return + } + + var row = grid.getSelectedItem() + if (row == undefined){ + msg_error('Selecciona una prefactura') + return + } + + var msg = '¿Estás seguro de facturar la siguiente PREFactura?

' + msg += '(' + row['folio'] + ') ' + row['cliente'] + webix.confirm({ + title: 'Generar Factura', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if (result){ + refacturar_preinvoice(row['id']) + } + } + }) +} diff --git a/source/static/js/controller/main.js b/source/static/js/controller/main.js index db3c2b8..9aef6e1 100644 --- a/source/static/js/controller/main.js +++ b/source/static/js/controller/main.js @@ -53,6 +53,13 @@ var controllers = { $$('filter_dates').attachEvent('onChange', filter_dates_change) $$('cmd_prefactura').attachEvent('onItemClick', cmd_prefactura_click) $$('lst_metodo_pago').attachEvent('onChange', lst_metodo_pago_change) + + var tb_invoice = $$('tv_invoice').getTabbar() + tb_invoice.attachEvent('onChange', tb_invoice_change) + $$('prefilter_year').attachEvent('onChange', prefilter_year_change) + $$('prefilter_month').attachEvent('onChange', prefilter_month_change) + $$('cmd_delete_preinvoice').attachEvent('onItemClick', cmd_delete_preinvoice_click) + $$('cmd_facturar_preinvoice').attachEvent('onItemClick', cmd_facturar_preinvoice_click) } } @@ -110,20 +117,29 @@ function menu_user_click(id, e, node){ function current_dates(){ var fy = $$('filter_year') var fm = $$('filter_month') + var pfy = $$('prefilter_year') + var pfm = $$('prefilter_month') var d = new Date() fy.blockEvent() fm.blockEvent() + pfy.blockEvent() + pfm.blockEvent() fm.setValue(d.getMonth() + 1) + pfm.setValue(d.getMonth() + 1) webix.ajax().sync().get('/values/filteryears', function(text, data){ var values = data.json() - fy.getList().parse(values) + fy.getList().parse(values[0]) + pfy.getList().parse(values[1]) fy.setValue(d.getFullYear()) + pfy.setValue(d.getFullYear()) }) fy.unblockEvent() fm.unblockEvent() + pfy.unblockEvent() + pfm.unblockEvent() } diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index 02f5600..e98e36d 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -310,7 +310,15 @@ var controls_generate = [ {view: "button", id: 'cmd_prefactura', label: "PreFactura", type: "form", autowidth: true, align:"center"}, {}] - } + }, + {rows: [ + {template:"", type: "section" }, + {margin: 10, cols: [{}, + {view: 'button', id: 'cmd_close_invoice', label: 'Cancelar', + type: 'danger', autowidth: true, align: 'center'} + ] + }, + ]} ] @@ -355,6 +363,7 @@ var grid_preinvoices = { adjust: true, footer: true, resizeColumn: true, + autoheight: true, headermenu: true, columns: grid_preinvoices_cols, } @@ -370,25 +379,19 @@ var controls_prefactura = [ var controls_invoices = [ { view: 'tabview', + id: 'tv_invoice', tabbar: {options: ['Generar', 'PreFacturas']}, animate: true, cells: [ {id: 'Generar', rows: controls_generate}, {id: 'PreFacturas', rows: controls_prefactura}, ] }, - {rows: [ - {template:"", type: "section" }, - {margin: 10, cols: [{}, - {view: 'button', id: 'cmd_close_invoice', label: 'Cancelar', - type: 'danger', autowidth: true, align: 'center'} - ] - }, - ]} ] var form_invoice = { type: 'space', + responsive: true, cols: [{ view: 'form', id: 'form_invoice',