Timbrado de Complemento de Pagos 2.0
This commit is contained in:
parent
a7bc6d6f6c
commit
7ca87d1811
|
@ -91,10 +91,10 @@ SAT = {
|
|||
'schema': ' http://www.sat.gob.mx/iedu http://www.sat.gob.mx/sitio_internet/cfd/iedu/iedu.xsd',
|
||||
},
|
||||
'pagos': {
|
||||
'version': '1.0',
|
||||
'prefix': 'pago10',
|
||||
'xmlns': 'http://www.sat.gob.mx/Pagos',
|
||||
'schema': ' http://www.sat.gob.mx/Pagos http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos10.xsd',
|
||||
'version': '2.0',
|
||||
'prefix': 'pago20',
|
||||
'xmlns': 'http://www.sat.gob.mx/Pagos20',
|
||||
'schema': ' http://www.sat.gob.mx/Pagos20 http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd',
|
||||
},
|
||||
'divisas': {
|
||||
'version': '1.0',
|
||||
|
@ -568,14 +568,41 @@ class CFDI(object):
|
|||
|
||||
if 'pagos' in datos:
|
||||
datos = datos.pop('pagos')
|
||||
totales = datos.pop('totales')
|
||||
relacionados = datos.pop('relacionados')
|
||||
taxes_pay = datos.pop('taxes_pay')
|
||||
pre = SAT['pagos']['prefix']
|
||||
|
||||
attributes = {'Version': SAT['pagos']['version']}
|
||||
pagos = ET.SubElement(
|
||||
self._complemento, '{}:Pagos'.format(pre), attributes)
|
||||
|
||||
ET.SubElement(pagos, '{}:Totales'.format(pre), totales)
|
||||
|
||||
node_pago = ET.SubElement(pagos, '{}:Pago'.format(pre), datos)
|
||||
for row in relacionados:
|
||||
ET.SubElement(node_pago, '{}:DoctoRelacionado'.format(pre), row)
|
||||
taxes = row.pop('taxes')
|
||||
node = ET.SubElement(node_pago, f'{pre}:DoctoRelacionado', row)
|
||||
node_tax = ET.SubElement(node, f'{pre}:ImpuestosDR')
|
||||
if taxes['retenciones']:
|
||||
node = ET.SubElement(node_tax, f'{pre}:RetencionsDR')
|
||||
for tax in taxes['retenciones']:
|
||||
ET.SubElement(node, f'{pre}:RetencionDR', tax)
|
||||
if taxes['traslados']:
|
||||
node = ET.SubElement(node_tax, f'{pre}:TrasladosDR')
|
||||
for tax in taxes['traslados']:
|
||||
ET.SubElement(node, f'{pre}:TrasladoDR', tax)
|
||||
|
||||
node_tax = ET.SubElement(node_pago, f'{pre}:ImpuestosP')
|
||||
if taxes_pay['retenciones']:
|
||||
node = ET.SubElement(node_tax, f'{pre}:RetencionsP')
|
||||
for key, importe in taxes_pay['retenciones'].items():
|
||||
attr = {'ImpuestoP': key, 'ImporteP': importe}
|
||||
ET.SubElement(node, f'{pre}:RetencionP', attr)
|
||||
if taxes_pay['traslados']:
|
||||
node = ET.SubElement(node_tax, f'{pre}:TrasladosP')
|
||||
for key, tax in taxes_pay['traslados'].items():
|
||||
ET.SubElement(node, f'{pre}:TrasladoP', tax)
|
||||
|
||||
if 'leyendas' in datos:
|
||||
pre = SAT['leyendas']['prefix']
|
||||
|
|
|
@ -2762,6 +2762,7 @@ class Socios(BaseModel):
|
|||
uso_cfdi = ForeignKeyField(SATUsoCfdi, null=True)
|
||||
tags = ManyToManyField(Tags, related_name='socios_tags')
|
||||
plantilla = TextField(default='')
|
||||
regimen_fiscal = TextField(default='')
|
||||
|
||||
def __str__(self):
|
||||
t = '{} ({})'
|
||||
|
@ -7242,6 +7243,7 @@ class CfdiPagos(BaseModel):
|
|||
error = TextField(default='')
|
||||
cancelada = BooleanField(default=False)
|
||||
fecha_cancelacion = DateTimeField(null=True)
|
||||
receptor_regimen = TextField(default='')
|
||||
|
||||
class Meta:
|
||||
order_by = ('movimiento',)
|
||||
|
@ -7365,6 +7367,7 @@ class CfdiPagos(BaseModel):
|
|||
|
||||
partner = related[0].factura.cliente
|
||||
partner_name = related[0].factura.cliente.nombre
|
||||
receptor_regimen = related[0].factura.receptor_regimen
|
||||
|
||||
emisor = Emisor.select()[0]
|
||||
# ~ regimen_fiscal = related[0].factura.regimen_fiscal
|
||||
|
@ -7404,6 +7407,7 @@ class CfdiPagos(BaseModel):
|
|||
fields['folio'] = self._get_folio(self, serie)
|
||||
fields['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
|
||||
fields['regimen_fiscal'] = regimen_fiscal
|
||||
fields['receptor_regimen'] = receptor_regimen
|
||||
|
||||
with database_proxy.atomic() as txn:
|
||||
obj = CfdiPagos.create(**fields)
|
||||
|
@ -7421,7 +7425,85 @@ class CfdiPagos(BaseModel):
|
|||
data = {'ok': True, 'row': row, 'new': True}
|
||||
return data
|
||||
|
||||
def _get_taxes_by_pay(self, pay, taxes_pay):
|
||||
# ~ print(pay['ImpPagado']
|
||||
invoice = Facturas.get(Facturas.uuid==pay['IdDocumento'])
|
||||
impuestos = {}
|
||||
traslados = []
|
||||
retenciones = []
|
||||
|
||||
where = (FacturasImpuestos.factura==invoice)
|
||||
taxes = FacturasImpuestos.select().where(where)
|
||||
|
||||
for tax in taxes:
|
||||
if tax.impuesto.key == '000':
|
||||
# ~ tasa = str(round(tax.impuesto.tasa * 100, 2))
|
||||
# ~ simporte = FORMAT.format(tax.importe)
|
||||
# ~ if tax.impuesto.tipo == 'T':
|
||||
# ~ traslado = {
|
||||
# ~ 'ImpLocTrasladado': tax.impuesto.name,
|
||||
# ~ 'TasadeTraslado': tasa,
|
||||
# ~ 'Importe': simporte,
|
||||
# ~ }
|
||||
# ~ locales_trasladados.append(traslado)
|
||||
# ~ total_locales_trasladados += tax.importe
|
||||
# ~ else:
|
||||
# ~ retencion = {
|
||||
# ~ 'ImpLocRetenido': tax.impuesto.name,
|
||||
# ~ 'TasadeRetencion': tasa,
|
||||
# ~ 'Importe': simporte,
|
||||
# ~ }
|
||||
# ~ locales_retenciones.append(retencion)
|
||||
# ~ total_locales_retenciones += tax.importe
|
||||
continue
|
||||
|
||||
tipo_factor = 'Tasa'
|
||||
if tax.impuesto.factor != 'T':
|
||||
tipo_factor = 'Cuota'
|
||||
|
||||
# ~ if tax_decimals:
|
||||
# ~ xml_importe = FORMAT_TAX.format(tax.importe)
|
||||
# ~ xml_tax_base = FORMAT_TAX.format(tax.base)
|
||||
# ~ else:
|
||||
xml_importe = FORMAT.format(tax.importe)
|
||||
xml_tax_base = FORMAT.format(tax.base)
|
||||
|
||||
values = {
|
||||
"BaseDR": xml_tax_base,
|
||||
"ImpuestoDR": tax.impuesto.key,
|
||||
"TipoFactorDR": tipo_factor,
|
||||
"TasaOCuotaDR": str(tax.impuesto.tasa),
|
||||
"ImporteDR": xml_importe,
|
||||
}
|
||||
tax_key = tax.impuesto.key
|
||||
if tax.impuesto.tipo == 'T':
|
||||
traslados.append(values)
|
||||
if tax_key in taxes_pay['traslados']:
|
||||
taxes_pay['traslados'][tax_key]['ImporteP'] += tax.importe
|
||||
else:
|
||||
values = {
|
||||
"BaseP": tax.base,
|
||||
"ImpuestoP": tax.impuesto.key,
|
||||
"TipoFactorP": tipo_factor,
|
||||
"TasaOCuotaP": str(tax.impuesto.tasa),
|
||||
"ImporteP": tax.importe,
|
||||
}
|
||||
taxes_pay['traslados'][tax_key] = values
|
||||
else:
|
||||
retenciones.append(values)
|
||||
if tax_key in taxes_pay['retenciones']:
|
||||
taxes_pay['retenciones'][tax_key] += tax.importe
|
||||
else:
|
||||
taxes_pay['retenciones'][tax_key] = tax.importe
|
||||
|
||||
impuestos['traslados'] = traslados
|
||||
impuestos['retenciones'] = retenciones
|
||||
|
||||
return impuestos
|
||||
|
||||
def _get_related_xml(self, id_mov, currency):
|
||||
TAX_IVA_16 = '002|0.160000'
|
||||
|
||||
filters = (FacturasPagos.movimiento==id_mov)
|
||||
related = tuple(FacturasPagos.select(
|
||||
Facturas.uuid.alias('IdDocumento'),
|
||||
|
@ -7438,13 +7520,21 @@ class CfdiPagos(BaseModel):
|
|||
.where(filters)
|
||||
.dicts())
|
||||
|
||||
taxes_pay = {'retenciones': {}, 'traslados': {}, 'totales': {}}
|
||||
|
||||
for r in related:
|
||||
r['taxes'] = self._get_taxes_by_pay(self, r, taxes_pay)
|
||||
# ~ print('\n\nMONEDA', currency, r['MonedaDR'])
|
||||
r['IdDocumento'] = str(r['IdDocumento'])
|
||||
r['Folio'] = str(r['Folio'])
|
||||
r['NumParcialidad'] = str(r['NumParcialidad'])
|
||||
r['TipoCambioDR'] = FORMAT6.format(r['TipoCambioDR'])
|
||||
r['MetodoDePagoDR'] = DEFAULT_CFDIPAY['WAYPAY']
|
||||
# ~ r['MetodoDePagoDR'] = DEFAULT_CFDIPAY['WAYPAY']
|
||||
|
||||
# REVISAR
|
||||
r['EquivalenciaDR'] = '1'
|
||||
r['ObjetoImpDR'] = '02'
|
||||
|
||||
r['ImpSaldoAnt'] = FORMAT.format(r['ImpSaldoAnt'])
|
||||
r['ImpPagado'] = FORMAT.format(r['ImpPagado'])
|
||||
if round(r['ImpSaldoInsoluto'], 2) == 0.0:
|
||||
|
@ -7456,7 +7546,28 @@ class CfdiPagos(BaseModel):
|
|||
if not r['Serie']:
|
||||
del r['Serie']
|
||||
|
||||
return related
|
||||
total_tax_iva_16_base = 0
|
||||
total_tax_iva_16_importe = 0
|
||||
|
||||
for key, importe in taxes_pay['retenciones'].items():
|
||||
taxes_pay['retenciones'][key] = FORMAT.format(importe)
|
||||
for k, tax in taxes_pay['traslados'].items():
|
||||
tax_type = taxes_pay['traslados'][k]['ImpuestoP']
|
||||
tax_tasa = taxes_pay['traslados'][k]['TasaOCuotaP']
|
||||
tax_base = taxes_pay['traslados'][k]['BaseP']
|
||||
importe = taxes_pay['traslados'][k]['ImporteP']
|
||||
if f'{tax_type}|{tax_tasa}' == TAX_IVA_16:
|
||||
total_tax_iva_16_base += tax_base
|
||||
total_tax_iva_16_importe += importe
|
||||
taxes_pay['traslados'][k]['BaseP'] = FORMAT.format(tax_base)
|
||||
taxes_pay['traslados'][k]['ImporteP'] = FORMAT.format(importe)
|
||||
|
||||
taxes_pay['totales'] = {
|
||||
'TotalTrasladosBaseIVA16': FORMAT.format(total_tax_iva_16_base),
|
||||
'TotalTrasladosImpuestoIVA16': FORMAT.format(total_tax_iva_16_importe),
|
||||
}
|
||||
|
||||
return related, taxes_pay
|
||||
|
||||
def _generate_xml(self, invoice):
|
||||
emisor = Emisor.select()[0]
|
||||
|
@ -7469,9 +7580,9 @@ class CfdiPagos(BaseModel):
|
|||
cfdi['Folio'] = str(invoice.folio)
|
||||
cfdi['Fecha'] = invoice.fecha.isoformat()[:19]
|
||||
cfdi['NoCertificado'] = certificado.serie
|
||||
# ~ cfdi['Certificado'] = cert.cer_txt
|
||||
cfdi['SubTotal'] = '0'
|
||||
cfdi['Moneda'] = DEFAULT_CFDIPAY['CURRENCY']
|
||||
# ~ cfdi['TipoCambio'] = DEFAULT_CFDIPAY['TC']
|
||||
cfdi['Total'] = '0'
|
||||
cfdi['TipoDeComprobante'] = invoice.tipo_comprobante
|
||||
cfdi['LugarExpedicion'] = invoice.lugar_expedicion
|
||||
|
@ -7492,6 +7603,8 @@ class CfdiPagos(BaseModel):
|
|||
'Rfc': invoice.socio.rfc,
|
||||
'Nombre': invoice.socio.nombre,
|
||||
'UsoCFDI': DEFAULT_CFDIPAY['USED'],
|
||||
'DomicilioFiscalReceptor': invoice.socio.codigo_postal,
|
||||
'RegimenFiscalReceptor': invoice.receptor_regimen
|
||||
}
|
||||
if invoice.socio.tipo_persona == 4:
|
||||
if invoice.socio.pais:
|
||||
|
@ -7506,19 +7619,23 @@ class CfdiPagos(BaseModel):
|
|||
'Descripcion': DEFAULT_CFDIPAY['DESCRIPTION'],
|
||||
'ValorUnitario': '0',
|
||||
'Importe': '0',
|
||||
'ObjetoImp': '01',
|
||||
},)
|
||||
|
||||
impuestos = {}
|
||||
|
||||
mov = invoice.movimiento
|
||||
currency = mov.moneda
|
||||
related_docs = self._get_related_xml(self, invoice.movimiento, currency)
|
||||
related_docs, taxes_pay = self._get_related_xml(self, invoice.movimiento, currency)
|
||||
totales = taxes_pay.pop('totales')
|
||||
pagos = {
|
||||
'FechaPago': mov.fecha.isoformat()[:19],
|
||||
'FormaDePagoP': mov.forma_pago.key,
|
||||
'MonedaP': currency,
|
||||
'TipoCambioP': '1',
|
||||
'Monto': FORMAT.format(mov.deposito),
|
||||
'relacionados': related_docs,
|
||||
'taxes_pay': taxes_pay,
|
||||
}
|
||||
if mov.numero_operacion:
|
||||
pagos['NumOperacion'] = mov.numero_operacion
|
||||
|
@ -7533,10 +7650,12 @@ class CfdiPagos(BaseModel):
|
|||
pagos['RfcEmisorCtaBen'] = mov.cuenta.banco.rfc
|
||||
pagos['CtaBeneficiario'] = mov.cuenta.cuenta
|
||||
|
||||
|
||||
if currency != CURRENCY_MN:
|
||||
pagos['TipoCambioP'] = FORMAT_TAX.format(mov.tipo_cambio)
|
||||
|
||||
totales['MontoTotalPagos'] = pagos['Monto']
|
||||
pagos['totales'] = totales
|
||||
|
||||
complementos = {'pagos': pagos}
|
||||
data = {
|
||||
'comprobante': cfdi,
|
||||
|
@ -10751,6 +10870,10 @@ def _migrate_tables(rfc=''):
|
|||
correo_facturasp = TextField(default='')
|
||||
migrations.append(
|
||||
migrator.add_column('socios', 'correo_facturasp', correo_facturasp))
|
||||
if not 'regimen_fiscal' in columns:
|
||||
regimen_fiscal = TextField(default='')
|
||||
migrations.append(
|
||||
migrator.add_column('socios', 'regimen_fiscal', regimen_fiscal))
|
||||
|
||||
columns = [c.name for c in database_proxy.get_columns('folios')]
|
||||
if not 'plantilla' in columns:
|
||||
|
@ -10800,6 +10923,9 @@ def _migrate_tables(rfc=''):
|
|||
migrations.append(migrator.add_column('cfdipagos', 'socio_id', socio))
|
||||
migrations.append(migrator.drop_column('cfdipagos', 'cancelado'))
|
||||
migrations.append(migrator.add_column('cfdipagos', 'cancelada', cancelada))
|
||||
if not 'receptor_regimen' in columns:
|
||||
receptor_regimen = TextField(default='')
|
||||
migrations.append(migrator.add_column('cfdipagos', 'receptor_regimen', receptor_regimen))
|
||||
|
||||
if not 'fecha_cancelacion' in columns:
|
||||
fecha_cancelacion = DateTimeField(null=True)
|
||||
|
|
|
@ -170,7 +170,8 @@ DEFAULT_CFDIPAY = {
|
|||
'TYPE': 'P',
|
||||
'WAYPAY': 'PPD',
|
||||
'CURRENCY': 'XXX',
|
||||
'USED': 'P01',
|
||||
'TC': '1',
|
||||
'USED': 'CP01',
|
||||
'KEYSAT': '84111506',
|
||||
'UNITKEY': 'ACT',
|
||||
'DESCRIPTION': 'Pago',
|
||||
|
@ -183,7 +184,7 @@ PUBLIC = 'Público en general'
|
|||
DEFAULT_SAT_NOMINA = {
|
||||
'SERIE': 'N',
|
||||
'FORMA_PAGO': '99',
|
||||
'USO_CFDI': 'P01',
|
||||
'USO_CFDI': 'CN01',
|
||||
'CLAVE': '84111505',
|
||||
'UNIDAD': 'ACT',
|
||||
'DESCRIPCION': 'Pago de nómina',
|
||||
|
|
Loading…
Reference in New Issue