diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index e55adf8..f739874 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -78,12 +78,13 @@ class AppPartners(object): def on_get(self, req, resp): values = req.params + print ('GET VALUES', values) req.context['result'] = self._db.get_partners(values) resp.status = falcon.HTTP_200 def on_post(self, req, resp): values = req.params - #~ print ('VALUES', values) + print ('POST VALUES', values) req.context['result'] = self._db.partner(values) resp.status = falcon.HTTP_200 diff --git a/source/app/middleware.py b/source/app/middleware.py index 3067cbb..127271c 100644 --- a/source/app/middleware.py +++ b/source/app/middleware.py @@ -40,6 +40,9 @@ class AuthMiddleware(object): class JSONTranslator(object): + #~ def process_request(self, req, resp): + #~ pass + def process_response(self, req, resp, resource): if 'result' not in req.context: return diff --git a/source/app/models/db.py b/source/app/models/db.py index 82a9262..59a2a45 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -51,8 +51,11 @@ class StorageEngine(object): return main.Productos.remove(id) return False - def _get_partner(self, values): - return main.Socios.get_by(values) + def _get_client(self, values): + return main.Socios.get_by_client(values) + + def _get_product(self, values): + return main.Productos.get_by(values) def get_partners(self, values): return main.Socios.get_(values) diff --git a/source/app/models/main.py b/source/app/models/main.py index 52799ad..0dfa407 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -416,12 +416,16 @@ class Socios(BaseModel): return {'ok': True, 'rows': tuple(rows)} @classmethod - def get_by(cls, values): + def get_by_client(cls, values): id = int(values.get('id', 0)) if id: row = (Socios - .select(Socios.id, Socios.nombre, Socios.rfc) - .where(Socios.id==id).dicts()) + .select( + Socios.id, Socios.nombre, Socios.rfc) + .where( + (Socios.id==id) & (Socios.es_cliente==True)) + .dicts() + ) if len(row): return {'ok': True, 'row': row[0]} return {'ok': False} @@ -430,11 +434,10 @@ class Socios(BaseModel): if name: rows = (Socios .select(Socios.id, Socios.nombre, Socios.rfc) - .where( - Socios.rfc.contains(name) | - Socios.nombre.contains(name)) + .where((Socios.es_cliente==True) & + (Socios.rfc.contains(name) | + Socios.nombre.contains(name))) .dicts()) - print (tuple(rows)) return tuple(rows) return {'ok': False} @@ -520,6 +523,35 @@ class Productos(BaseModel): value += 1 return {'value': value} + @classmethod + def get_by(cls, values): + id = int(values.get('id', 0)) + if id: + row = (Productos + .select(Productos.id, Productos.clave, Productos.descripcion, + SATUnidades.name.alias('unidad'), Productos.valor_unitario) + .join(SATUnidades).switch(Productos) + .where(Productos.id==id).dicts()) + if len(row): + model_pt = Productos.impuestos.get_through_model() + taxes = tuple(model_pt + .select( + model_pt.productos_id.alias('product'), + model_pt.satimpuestos_id.alias('tax')) + .where(model_pt.productos_id==id).dicts()) + return {'ok': True, 'row': row[0], 'taxes': taxes} + return {'ok': False} + + name = values.get('name', '') + if name: + rows = (Products + .select(Products.id, Products.key, Products.description, + SATUnidades.name.alias('unit'), Products.price) + .join(SATUnidades).switch(Products) + .where(Products.description.contains(name)).dicts()) + return tuple(rows) + return {'ok': False} + @classmethod def get_(cls, values): if values: diff --git a/source/static/css/app.css b/source/static/css/app.css index 0e5b00a..4e8eb53 100644 --- a/source/static/css/app.css +++ b/source/static/css/app.css @@ -30,6 +30,14 @@ font-size: 125%; } +.delete { + text-align: center; + font-weight: bold; + font-size: 250%; + color: red; +} + + .cmd_close_partner div button { background-color: red !important; border-color: red !important; diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index bcf5448..02e3892 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -52,6 +52,11 @@ function get_uso_cfdi(){ function default_config(){ + webix.ajax().sync().get('/values/taxes', function(text, data){ + var values = data.json() + table_taxes.clear() + table_taxes.insert(values) + }) get_series() get_forma_pago() get_monedas() @@ -68,12 +73,12 @@ function cmd_new_invoice_click(id, e, node){ default_config() form.adjust() - form.setValues({id: 0, id_partner: 0, lbl_partner: 'Ninguno'}) + form.setValues({id: 0, id_partner: 0, lbl_client: 'Ninguno'}) grid.clearAll() grid_totals.clearAll() grid_totals.add({id: 1, concepto: 'SubTotal', importe: 0}) $$('multi_invoices').setValue('invoices_new') - form.focus('search_partner_id') + form.focus('search_client_id') } @@ -110,16 +115,16 @@ function cmd_close_invoice_click(id, e, node){ } -function search_partner_by_id(id){ +function search_client_by_id(id){ var msg = '' - webix.ajax().get('/values/partner', {'id': id}, { + webix.ajax().get('/values/client', {'id': id}, { error: function(text, data, xhr) { webix.message({type: 'error', text: 'Error al consultar'}) }, success: function(text, data, xhr){ var values = data.json() if (values.ok){ - set_partner(values.row) + set_client(values.row) }else{ msg = 'No se encontró un cliente con la clave: ' + id webix.message({type:'error', text: msg}) @@ -130,31 +135,176 @@ function search_partner_by_id(id){ } -function set_partner(row){ +function set_client(row){ var form = $$('form_invoice') var html = '' form.setValues({ - id_partner:row.id, search_partner_id:'', search_partner_name:''}, true) + id_partner:row.id, search_client_id:'', search_client_name:''}, true) html += row.nombre + ' (' + row.rfc + ')' - $$('lbl_partner').setValue(html) + $$('lbl_client').setValue(html) form.focus('search_product_id') } -function grid_partners_found_click(obj){ - set_partner(obj) +function grid_clients_found_click(obj){ + set_client(obj) } -function search_partner_id_key_press(code, e){ +function search_client_id_key_press(code, e){ var value = this.getValue() if(code == 13 && value.length > 0){ var id = parseInt(value, 10) if (isNaN(id)){ webix.message({type:'error', text:'Captura una clave válida'}); }else{ - search_partner_by_id(id) + search_client_by_id(id) } } } + + +function calculate_taxes(){ + var tmp = null + table_totals.clear() + var subtotal = 0 + var total_iva = 0 + var id = 2 + var grid_totals = $$('grid_totals') + + grid_totals.clearAll() + grid_totals.add({id: 1, concepto: 'SubTotal', importe: 0}) + + grid.eachRow(function(row){ + var product = grid.getItem(row) + subtotal += parseFloat(product.importe) + query = table_pt.chain().find({'product': product.id}).data() + for(var tax of query){ + tmp = table_totals.findOne({'tax': tax.tax}) + if(tmp === null){ + table_totals.insert( + {'tax': tax.tax, 'importe': parseFloat(product.importe)}) + tmp = table_totals.findOne({'tax': tax.tax}) + }else{ + tmp.importe += parseFloat(product.importe) + table_totals.update(tmp) + } + } + }) + + var tax = null + var tipo = 'Traslado ' + var concepto = '' + var total_tax = 0 + query = table_totals.chain().data() + for(var t of query){ + tax = table_taxes.findOne({'id': t.tax}) + if(tax.tipo == 'E' || tax.tipo == 'R'){ + continue + } + concepto = tipo + tax.name + ' (' + tax.tasa + ')' + total_tax = (tax.tasa * t.importe).round(2) + grid_totals.add({id: id, concepto: concepto, importe: total_tax}) + id += 1 + if(tax.name == 'IVA'){ + total_iva += total_tax + } + } + + tipo = 'Retención ' + for(var t of query){ + tax = table_taxes.findOne({'id': t.tax}) + if(tax.tipo == 'E' || tax.tipo == 'T'){ + continue + } + concepto = tipo + tax.name + ' (' + tax.tasa + ')' + if(tax.tasa == (2/3).round(6)){ + total_tax = (tax.tasa * total_iva * -1).round(2) + concepto = tipo + tax.name + ' (2/3)' + }else{ + total_tax = (tax.tasa * t.importe * -1).round(2) + } + grid_totals.add({id: id, concepto: concepto, importe: total_tax}) + id += 1 + } + + row = {importe: subtotal} + grid_totals.updateItem(1, row) +} + + +function set_product(values){ + var taxes = values.taxes + var values = values.row + var form = $$('form_invoice') + var row = grid.getItem(values.id) + + values['delete'] = '-' + if (row == undefined){ + values['cantidad'] = 1 + values['importe'] = values['valor_unitario'] + grid.add(values) + } else { + values['cantidad'] = parseFloat(row.cantidad) + 1 + values['importe'] = values['valor_unitario'] * values['cantidad'] + grid.updateItem(row.id, values) + } + form.setValues({search_product_id:'', search_product_name:''}, true) + + for(var v of taxes){ + var pt = table_pt.findOne(v) + if(pt === null){ + table_pt.insert(v) + } + } + calculate_taxes() +} + + +function grid_products_found_click(obj){ + search_product_by_id(obj.id) +} + + +function search_product_by_id(id){ + var msg = '' + + webix.ajax().get('/values/product', {'id': id}, { + error: function(text, data, xhr) { + webix.message({type: 'error', text: 'Error al consultar'}) + }, + success: function(text, data, xhr){ + var values = data.json() + if (values.ok){ + set_product(values) + } else { + msg = 'No se encontró un producto con la clave: ' + id + webix.message({type: 'error', text: msg}) + } + } + }) + +} + + +function search_product_id_key_press(code, e){ + var value = this.getValue() + if(code == 13 && value.length > 0){ + var id = parseInt(value, 10) + if (isNaN(id)){ + webix.message({type: 'error', text: 'Captura una clave válida'}); + }else{ + search_product_by_id(id) + } + } +} + + +function grid_details_click(id, e, node){ + if(id.column != 'delete'){ + return + } + grid.remove(id.row) + calculate_taxes() +} diff --git a/source/static/js/controller/main.js b/source/static/js/controller/main.js index b2d1317..396a881 100644 --- a/source/static/js/controller/main.js +++ b/source/static/js/controller/main.js @@ -21,11 +21,11 @@ var controllers = { $$("rfc").attachEvent( "onBlur", rfc_lost_focus) $$('multi').attachEvent('onViewChange', multi_change) //~ Products - $$("cmd_new_product").attachEvent("onItemClick", cmd_new_product_click); - $$("cmd_edit_product").attachEvent("onItemClick", cmd_edit_product_click); - $$("cmd_delete_product").attachEvent("onItemClick", cmd_delete_product_click); - $$("cmd_save_product").attachEvent("onItemClick", cmd_save_product_click); - $$("cmd_cancel_product").attachEvent("onItemClick", cmd_cancel_product_click); + $$("cmd_new_product").attachEvent("onItemClick", cmd_new_product_click) + $$("cmd_edit_product").attachEvent("onItemClick", cmd_edit_product_click) + $$("cmd_delete_product").attachEvent("onItemClick", cmd_delete_product_click) + $$("cmd_save_product").attachEvent("onItemClick", cmd_save_product_click) + $$("cmd_cancel_product").attachEvent("onItemClick", cmd_cancel_product_click) $$("chk_automatica").attachEvent("onChange", chk_automatica_change) $$("valor_unitario").attachEvent("onChange", valor_unitario_change) //~ Invoices @@ -34,8 +34,11 @@ var controllers = { $$("cmd_delete_invoice").attachEvent("onItemClick", cmd_delete_invoice_click) $$('cmd_timbrar').attachEvent('onItemClick', cmd_timbrar_click) $$('cmd_close_invoice').attachEvent('onItemClick', cmd_close_invoice_click) - $$('search_partner_id').attachEvent('onKeyPress', search_partner_id_key_press) - $$('grid_partners_found').attachEvent('onValueSuggest', grid_partners_found_click); + $$('search_client_id').attachEvent('onKeyPress', search_client_id_key_press) + $$('grid_clients_found').attachEvent('onValueSuggest', grid_clients_found_click) + $$('search_product_id').attachEvent('onKeyPress', search_product_id_key_press) + $$('grid_products_found').attachEvent('onValueSuggest', grid_products_found_click) + $$('grid_details').attachEvent('onItemClick', grid_details_click) } } diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index 952c10b..6ae639f 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -18,6 +18,11 @@ function show(values){ } +Number.prototype.round = function(decimals){ + return Number((Math.round(this + "e" + decimals) + "e-" + decimals)) +} + + webix.protoUI({ $cssName: "text", name: "currency", diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index fdc432c..8da3482 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -52,22 +52,22 @@ var grid_invoices = { var grid_details_cols = [ {id: "id", header:"ID", hidden: true}, {id: 'delete', header: '', width: 30, css: 'delete'}, - {id: "key", header:{text: 'Clave', css: 'center'}, width: 100}, - {id: "description", header:{text: 'Descripción', css: 'center'}, + {id: "clave", header:{text: 'Clave', css: 'center'}, width: 100}, + {id: "descripcion", header:{text: 'Descripción', css: 'center'}, fillspace: true}, - {id: "unit", header:{text: 'Unidad', css: 'center'}, width: 100}, - {id: 'cant', header: {text: 'Cantidad', css: 'center'}, width: 100, + {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: "price", header:{text: 'Valor Unitario', css: 'center'}, width: 100, + {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'}, ] var grid_details = { - view: "datatable", - id: "grid_details", - select: "row", + view: 'datatable', + id: 'grid_details', + select: 'row', adjust: true, autoheight: true, columns: grid_details_cols, @@ -100,8 +100,8 @@ var grid_totals = { var suggest_partners = { view: 'gridsuggest', - id: 'grid_partners_found', - name: 'grid_partners_found', + id: 'grid_clients_found', + name: 'grid_clients_found', body: { autoConfig: false, header: false, @@ -111,7 +111,7 @@ var suggest_partners = { {id: 'rfc', adjust: 'data'}], dataFeed:function(text){ if (text.length > 2){ - this.load('/values/partner?name=' + text) + this.load('/values/client?name=' + text) }else{ this.hide() } @@ -215,21 +215,32 @@ var controls_generate = [ {cols: [ {rows:[ {view: 'fieldset', label: 'Buscar Cliente', body: {rows: [ {cols: [ - {view:"search", id:"search_partner_id", name:"search_partner_id", + {view:"search", id:"search_client_id", name:"search_client_id", label:"por Clave", labelPosition:'top', maxWidth:200, placeholder:'Captura la clave'}, - {view: 'search', id: 'search_partner_name', - name: 'search_partner_name', label: 'por Nombre o RFC', + {view: 'search', id: 'search_client_name', + name: 'search_client_name', label: 'por Nombre o RFC', labelPosition: 'top', suggest: suggest_partners, placeholder: 'Captura al menos tres letras'}, ]}, - {cols: [{view: "label", id: "lbl_partner_title", name: "lbl_partner_title", label: 'Seleccionado: ', autowidth:true}, - {view: "label", id: "lbl_partner", name: "lbl_partner", label: 'Ninguno'}, + {cols: [{ + view: 'label', id: 'lbl_client_title', + name: "lbl_client_title", label: 'Seleccionado: ', + autowidth:true}, + {view: 'label', id: 'lbl_client', name: 'lbl_client', + label: 'Ninguno'}, ]} ]}}, {view: 'fieldset', label: 'Buscar Producto', body: {rows: [ - {cols: [{view:"search", id:"search_product_id", name:"search_product_id", label:"por Clave", labelPosition:'top', maxWidth:200, placeholder:'Captura la clave'}, - {view:"search", id:"search_product_name", name:"search_product_name", label:"por Descripción", labelPosition:'top', suggest:suggest_products, placeholder:'Captura al menos tres letras'}, + {cols: [ + {view: "search", id: "search_product_id", + name: "search_product_id", label: "por Clave", + labelPosition:'top', maxWidth:200, + placeholder:'Captura la clave'}, + {view: "search", id: "search_product_name", + name: "search_product_name", label: "por Descripción", + labelPosition:'top', suggest: suggest_products, + placeholder:'Captura al menos tres letras'}, ]}, ]}} ]},