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 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+