Timbrado de Complemento de Pagos 2.0

This commit is contained in:
El Mau 2022-02-22 22:52:47 -06:00
parent a7bc6d6f6c
commit 7ca87d1811
3 changed files with 166 additions and 12 deletions

View File

@ -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']

View File

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

View File

@ -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',