From 9b430b882b30ad8fd3fae98b326d2a9ebef0e395 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 19 Nov 2017 00:42:16 -0600 Subject: [PATCH] Impuestos locales --- source/app/controllers/cfdi_xml.py | 48 +++++++++++++++++- source/app/models/main.py | 65 +++++++++++++++++++++---- source/static/js/controller/invoices.js | 10 +++- source/static/js/controller/util.js | 1 + source/static/js/ui/invoices.js | 1 + source/xslt/cadena.xslt | 3 +- source/xslt/implocal.xslt | 39 +++++++++++++++ 7 files changed, 152 insertions(+), 15 deletions(-) create mode 100644 source/xslt/implocal.xslt diff --git a/source/app/controllers/cfdi_xml.py b/source/app/controllers/cfdi_xml.py index dee3bfa..6520c5a 100644 --- a/source/app/controllers/cfdi_xml.py +++ b/source/app/controllers/cfdi_xml.py @@ -39,6 +39,12 @@ SAT = { 'xmlns': 'http://www.sat.gob.mx/nomina12', 'schema': 'http://www.sat.gob.mx/nomina12 http://www.sat.gob.mx/sitio_internet/cfd/nomina/nomina12.xsd', }, + 'locales': { + 'version': '1.0', + 'prefix': 'implocal', + 'xmlns': 'http://www.sat.gob.mx/implocal', + 'schema': ' http://www.sat.gob.mx/implocal http://www.sat.gob.mx/sitio_internet/cfd/implocal/implocal.xsd', + }, } @@ -49,6 +55,8 @@ class CFDI(object): self._xsi = SAT['xsi'] self._pre = self._sat_cfdi['prefix'] self._cfdi = None + self._complemento = None + self._impuestos_locales = False self.error = '' def _now(self): @@ -68,6 +76,7 @@ class CFDI(object): self._nomina(datos['nomina']) if 'complementos' in datos: self._complementos(datos['complementos']) + self._locales(datos['impuestos']) return self._to_pretty_xml(ET.tostring(self._cfdi, encoding='utf-8')) def add_sello(self, sello): @@ -80,6 +89,10 @@ class CFDI(object): return xml def _validate(self, datos): + if datos['impuestos']['total_locales_trasladados'] or \ + datos['impuestos']['total_locales_retenciones']: + self._impuestos_locales = True + if 'nomina' in datos: return self._validate_nomina(datos) return True @@ -105,7 +118,15 @@ class CFDI(object): attributes = {} attributes['xmlns:{}'.format(self._pre)] = self._sat_cfdi['xmlns'] attributes['xmlns:xsi'] = self._xsi - attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + + shema_locales = '' + if self._impuestos_locales: + name = 'xmlns:{}'.format(SAT['locales']['prefix']) + attributes[name] = SAT['locales']['xmlns'] + shema_locales = SAT['locales']['schema'] + + attributes['xsi:schemaLocation'] = self._sat_cfdi['schema'] + \ + shema_locales attributes.update(datos) if not 'Version' in attributes: @@ -257,8 +278,31 @@ class CFDI(object): ET.SubElement(deducciones, '{}:Deduccion'.format(pre), row) return + def _locales(self, datos): + if not self._impuestos_locales: + return + + if self._complemento is None: + self._complemento = ET.SubElement( + self._cfdi, '{}:Complemento'.format(self._pre)) + + attributes = {} + attributes['version'] = SAT['locales']['version'] + attributes['TotaldeTraslados'] = datos['total_locales_trasladados'] + attributes['TotaldeRetenciones'] = datos['total_locales_retenciones'] + + node = ET.SubElement( + self._complemento, 'implocal:ImpuestosLocales', attributes) + + for retencion in datos['locales_retenciones']: + ET.SubElement(node, 'implocal:RetencionesLocales', retencion) + for traslado in datos['locales_trasladados']: + ET.SubElement(node, 'implocal:TrasladosLocales', traslado) + return + def _complementos(self, datos): - complemento = ET.SubElement(self._cfdi, '{}:Complemento'.format(self._pre)) + self._complemento = ET.SubElement( + self._cfdi, '{}:Complemento'.format(self._pre)) if 'ce' in datos: pre = 'cce11' datos = datos.pop('ce') diff --git a/source/app/models/main.py b/source/app/models/main.py index 4c3c0f2..ef1ac74 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1443,6 +1443,7 @@ class Productos(BaseModel): .select( Productos.id, Productos.clave, + Productos.clave_sat, Productos.descripcion, SATUnidades.name.alias('unidad'), Productos.valor_unitario, @@ -1463,8 +1464,12 @@ class Productos(BaseModel): if name: rows = (Productos .select( - Productos.id, Productos.clave, Productos.descripcion, - SATUnidades.name.alias('unidad'), Productos.valor_unitario) + Productos.id, + Productos.clave, + Productos.clave_sat, + Productos.descripcion, + SATUnidades.name.alias('unidad'), + Productos.valor_unitario) .join(SATUnidades) .switch(Productos) .where(Productos.descripcion.contains(name)) @@ -1984,6 +1989,8 @@ class Facturas(BaseModel): total_trasladados = None total_retenciones = None total_iva = 0 + locales_traslados = 0 + locales_retenciones = 0 for product in products: id_product = product.pop('id') @@ -2015,10 +2022,8 @@ class Facturas(BaseModel): 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 @@ -2027,7 +2032,10 @@ class Facturas(BaseModel): continue import_tax = round(float(tax.tasa) * tax.importe, DECIMALES) - total_trasladados = (total_trasladados or 0) + import_tax + if tax.key == '000': + locales_traslados += import_tax + else: + total_trasladados = (total_trasladados or 0) + import_tax if tax.name == 'IVA': total_iva += import_tax @@ -2046,7 +2054,10 @@ class Facturas(BaseModel): import_tax = round(float(tax.tasa) * total_iva, DECIMALES) else: import_tax = round(float(tax.tasa) * tax.importe, DECIMALES) - total_retenciones = (total_retenciones or 0) + import_tax + if tax.key == '000': + locales_retenciones += import_tax + else: + total_retenciones = (total_retenciones or 0) + import_tax invoice_tax = { 'factura': invoice.id, @@ -2056,7 +2067,9 @@ class Facturas(BaseModel): } FacturasImpuestos.create(**invoice_tax) - total = subtotal + (total_trasladados or 0) - (total_retenciones or 0) + total = subtotal + \ + (total_trasladados or 0) - (total_retenciones or 0) \ + + locales_traslados - locales_retenciones total_mn = round(total * invoice.tipo_cambio, DECIMALES) data = { 'subtotal': subtotal + descuento, @@ -2163,6 +2176,7 @@ class Facturas(BaseModel): } #~ descuento = 0 + #~ tax_locales = False conceptos = [] rows = FacturasDetalle.select().where(FacturasDetalle.factura==invoice) for row in rows: @@ -2187,6 +2201,11 @@ class Facturas(BaseModel): for impuesto in row.producto.impuestos: if impuesto.tipo == 'E': continue + + if impuesto.key == '000': + #~ tax_locales = True + continue + base = row.importe - row.descuento import_tax = round(impuesto.tasa * base, DECIMALES) tipo_factor = 'Tasa' @@ -2211,12 +2230,13 @@ class Facturas(BaseModel): concepto['impuestos'] = taxes conceptos.append(concepto) - #~ if descuento: - #~ comprobante['Descuento'] = FORMAT.format(descuento) - impuestos = {} traslados = [] retenciones = [] + total_locales_trasladados = 0 + total_locales_retenciones = 0 + locales_trasladados = [] + locales_retenciones = [] if not invoice.total_trasladados is None: impuestos['TotalImpuestosTrasladados'] = \ FORMAT.format(invoice.total_trasladados) @@ -2228,6 +2248,25 @@ class Facturas(BaseModel): .select() .where(FacturasImpuestos.factura==invoice)) for tax in taxes: + if tax.impuesto.key == '000': + if tax.impuesto.tipo == 'T': + traslado = { + 'ImpLocTrasladado': tax.impuesto.name, + 'TasadeTraslado': str(round(tax.impuesto.tasa, 2)), + 'Importe': FORMAT.format(tax.importe), + } + locales_trasladados.append(traslado) + total_locales_trasladados += tax.importe + else: + retencion = { + 'ImpLocRetenido': tax.impuesto.name, + 'TasadeRetencion': str(round(tax.impuesto.tasa, 2)), + 'Importe': FORMAT.format(tax.importe), + } + locales_retenciones.append(retencion) + total_locales_retenciones += tax.importe + continue + tipo_factor = 'Tasa' if tax.impuesto.factor != 'T': tipo_factor = 'Cuota' @@ -2248,6 +2287,12 @@ class Facturas(BaseModel): impuestos['traslados'] = traslados impuestos['retenciones'] = retenciones + impuestos['total_locales_trasladados'] = \ + FORMAT.format(total_locales_trasladados) + impuestos['total_locales_retenciones'] = \ + FORMAT.format(total_locales_retenciones) + impuestos['locales_trasladados'] = locales_trasladados + impuestos['locales_retenciones'] = locales_retenciones data = { 'comprobante': comprobante, diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 593cab9..0bce3da 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -260,6 +260,13 @@ function validate_invoice(values){ return false } + var r = grid.data.getRange() + if(r[0].clave_sat != CLAVE_ANTICIPOS){ + msg = 'La clave del SAT para anticipos debe ser: ' + CLAVE_ANTICIPOS + msg_error(msg) + return false + } + query = table_relaciones.chain().data() if(query.length > 0){ msg = 'Los anticipos no deben llevar CFDI relacionados' @@ -419,6 +426,7 @@ function guardar_y_timbrar(values){ for (i = 0; i < rows.length; i++) { delete rows[i]['delete'] delete rows[i]['clave'] + delete rows[i]['clave_sat'] delete rows[i]['unidad'] delete rows[i]['importe'] rows[i]['valor_unitario'] = parseFloat(rows[i]['valor_unitario']) @@ -639,7 +647,7 @@ function set_product(values){ values['importe'] = (precio_final * values['cantidad']).round(DECIMALES) grid.updateItem(row.id, values) } - form.setValues({search_product_id:'', search_product_name:''}, true) + form.setValues({search_product_id: '', search_product_name: ''}, true) for(var v of taxes){ var pt = table_pt.findOne(v) diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index ac5ca32..3a81c1a 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -3,6 +3,7 @@ var RFC_PUBLICO = "XAXX010101000"; var RFC_EXTRANJERO = "XEXX010101000"; var PAIS = "México"; var DECIMALES = 2; +var CLAVE_ANTICIPOS = '84111506'; var db = new loki('data.db'); diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index 6fc27f3..a688939 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -233,6 +233,7 @@ var grid_details_cols = [ {id: "id", header:"ID", hidden: true}, {id: 'delete', header: '', width: 30, css: 'delete'}, {id: "clave", header:{text: 'Clave', css: 'center'}, width: 100}, + {id: "clave_sat", hidden: true}, {id: "descripcion", header:{text: 'Descripción', css: 'center'}, fillspace: true, editor: 'text'}, {id: "unidad", header:{text: 'Unidad', css: 'center'}, width: 100}, diff --git a/source/xslt/cadena.xslt b/source/xslt/cadena.xslt index 2b81c70..bde9ab9 100644 --- a/source/xslt/cadena.xslt +++ b/source/xslt/cadena.xslt @@ -8,12 +8,11 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +