diff --git a/sii/resource.py b/sii/resource.py index 773d2f6..015ae23 100644 --- a/sii/resource.py +++ b/sii/resource.py @@ -10,6 +10,7 @@ SIGN = {'N': 1, 'R': 1, 'A': -1, 'B': -1, 'RA': 1, 'C': 1, 'G': 1} # 'BRA': -1 +TIPO_IMPOSITIVA_NO_VIGENTES = {'5.00': '2024-09-30'} def is_inversion_sujeto_pasivo(tax_name): regex_isp = r'inv.*sujeto pasivo' @@ -342,10 +343,6 @@ def get_fact_rect_sustitucion_fields(invoice, opcion=False): rectificativa_fields = { 'TipoRectificativa': 'S' # Por sustitución } - - if 'out_' in invoice.type: - pass - #rectificativa_fields['FechaOperacion'] = get_fecha_operacion_rec(invoice) if opcion == 1: factura_rectificada = invoice.rectifying_id @@ -434,11 +431,24 @@ def get_factura_emitida(invoice, rect_sust_opc1=False, rect_sust_opc2=False): factura_expedida['DatosInmueble'] = { 'DetalleInmueble': detalle_inmueble } + + DetalleIVA = factura_expedida['TipoDesglose'].get( + 'DesgloseFactura', {}).get('Sujeta', {}).get('NoExenta', {}).get( + 'DesgloseIVA', {}).get('DetalleIVA', []) + tipo_impositivo_no_vigente = False + if DetalleIVA: + for detalle in DetalleIVA: + tipo_imp_detalle = '{}'.format(detalle.get('TipoImpositivo', None)) + if tipo_imp_detalle in TIPO_IMPOSITIVA_NO_VIGENTES: + tipo_impositivo_no_vigente = TIPO_IMPOSITIVA_NO_VIGENTES[tipo_imp_detalle] + break + if invoice.rectificative_type in ('A', 'B'): - pass - # factura_expedida.update( - # {'FechaOperacion': get_fecha_operacion_rec(invoice)} - # ) + if tipo_impositivo_no_vigente: + if invoice.date_invoice > tipo_impositivo_no_vigente: + factura_expedida.update( + {'FechaOperacion': get_fecha_operacion_rec(invoice)} + ) if rectificativa: opcion = 0 if rect_sust_opc1: @@ -446,7 +456,12 @@ def get_factura_emitida(invoice, rect_sust_opc1=False, rect_sust_opc2=False): elif rect_sust_opc2: opcion = 2 vals = get_fact_rect_sustitucion_fields(invoice, opcion=opcion) - + if opcion == 2: + if tipo_impositivo_no_vigente: + if invoice.date_invoice > tipo_impositivo_no_vigente: + factura_expedida.update( + {'FechaOperacion': get_fecha_operacion_rec(invoice)} + ) fact_rect = invoice.rectifying_id if fact_rect and fact_rect.sii_registered: numero_factura = fact_rect.number diff --git a/spec/serialization_spec.py b/spec/serialization_spec.py index 3e295db..179a1c7 100644 --- a/spec/serialization_spec.py +++ b/spec/serialization_spec.py @@ -929,22 +929,20 @@ def group_by_tax_rate(iva_values, in_invoice): ['RegistroLRFacturasEmitidas'] ) with context('en los datos de abonadora'): - with it('la FechaOperacion debe ser por factura original'): - pass - # expect( - # self.fact_refund_emit['FacturaExpedida']['FechaOperacion'] - # ).to(equal('31-12-2016')) + with it('la FechaOperacion NO debe ser informado'): + expect( + self.fact_refund_emit['FacturaExpedida'].get('FechaOperacion', False) + ).to(equal(False)) with context('en los datos de rectificación'): with it('el TipoRectificativa debe ser por sustitución (S)'): expect( self.fact_rect_emit['FacturaExpedida']['TipoRectificativa'] ).to(equal('S')) - with it('la FechaOperacion debe ser por factura original'): - pass - # expect( - # self.fact_rect_emit['FacturaExpedida']['FechaOperacion'] - # ).to(equal('31-12-2016')) + with it('la FechaOperacion NO debe ser informado'): + expect( + self.fact_rect_emit['FacturaExpedida'].get('FechaOperacion', False) + ).to(equal(False)) with before.all: self.importe_rectificacion = ( @@ -992,6 +990,181 @@ def group_by_tax_rate(iva_values, in_invoice): self.out_refund.tax_line[0].tax_id.amount * 100 )) + with description('en los datos de una factura rectificativa emitida con IVA 5% fecha postrior vigencia'): + with before.all: + self.out_refund, self.out_b_inovice = self.data_gen.get_out_refund_invoice_iva5() + self.out_refund_obj = SII(self.out_refund).generate_object() + self.out_b_inovice_obj = SII(self.out_b_inovice).generate_object() + self.fact_rect_emit = ( + self.out_refund_obj['SuministroLRFacturasEmitidas'] + ['RegistroLRFacturasEmitidas'] + ) + self.fact_refund_emit = ( + self.out_b_inovice_obj['SuministroLRFacturasEmitidas'] + ['RegistroLRFacturasEmitidas'] + ) + with context('en los datos de abonadora'): + with it('la FechaOperacion debe ser por factura original'): + expect( + self.fact_refund_emit['FacturaExpedida']['FechaOperacion'] + ).to(equal('01-01-2023')) + with context('en los datos de rectificación'): + with it('el TipoRectificativa debe ser por sustitución (S)'): + expect( + self.fact_rect_emit['FacturaExpedida']['TipoRectificativa'] + ).to(equal('S')) + + with it('la FechaOperacion debe ser por factura original'): + expect( + self.fact_rect_emit['FacturaExpedida']['FechaOperacion'] + ).to(equal('01-01-2023')) + + with before.all: + self.importe_rectificacion = ( + self.fact_rect_emit['FacturaExpedida'] + ['ImporteRectificacion'] + ) + + with it('la BaseRectificada debe ser 0'): + expect( + self.importe_rectificacion['BaseRectificada'] + ).to(equal(0)) + + with it('la CuotaRectificada debe ser 0'): + expect( + self.importe_rectificacion['CuotaRectificada'] + ).to(equal(0)) + + with context('en los detalles del IVA'): + with before.all: + detalle_iva = ( + self.fact_rect_emit['FacturaExpedida']['TipoDesglose'] + ['DesgloseFactura']['Sujeta']['NoExenta']['DesgloseIVA'] + ['DetalleIVA'] + ) + self.grouped_detalle_iva = group_by_tax_rate( + detalle_iva, in_invoice=False + ) + + with it('la BaseImponible debe ser la original'): + expect( + self.grouped_detalle_iva[5.0]['BaseImponible'] + ).to(equal( + self.out_refund.tax_line[0].base + )) + with it('la CuotaRepercutida debe ser la original'): + expect( + self.grouped_detalle_iva[5.0]['CuotaRepercutida'] + ).to(equal( + -1 * abs(self.out_refund.tax_line[0].tax_amount) + )) + with it('el TipoImpositivo debe ser la original'): + expect( + self.grouped_detalle_iva[5.0]['TipoImpositivo'] + ).to(equal( + self.out_refund.tax_line[0].tax_id.amount * 100 + )) + + with description('en los datos de una factura rectificativa emitida con IVA 5% fecha en vigencia'): + with before.all: + self.out_refund, self.out_b_inovice = self.data_gen.get_out_refund_invoice_iva5(fecha_facturas_recti='2024-03-18') + self.out_refund_obj = SII(self.out_refund).generate_object() + self.out_b_inovice_obj = SII(self.out_b_inovice).generate_object() + self.fact_rect_emit = ( + self.out_refund_obj['SuministroLRFacturasEmitidas'] + ['RegistroLRFacturasEmitidas'] + ) + self.fact_refund_emit = ( + self.out_b_inovice_obj['SuministroLRFacturasEmitidas'] + ['RegistroLRFacturasEmitidas'] + ) + with context('en los datos de abonadora'): + with it('la FechaOperacion debe ser por factura original'): + expect( + self.fact_refund_emit['FacturaExpedida'].get( + 'FechaOperacion', False) + ).to(equal(False)) + with context('en los datos de rectificación'): + with it('el TipoRectificativa debe ser por sustitución (S)'): + expect( + self.fact_rect_emit['FacturaExpedida']['TipoRectificativa'] + ).to(equal('S')) + + with it('la FechaOperacion no debe existir'): + expect( + self.fact_rect_emit['FacturaExpedida'].get('FechaOperacion', False) + ).to(equal(False)) + + with before.all: + self.importe_rectificacion = ( + self.fact_rect_emit['FacturaExpedida'] + ['ImporteRectificacion'] + ) + + with it('la BaseRectificada debe ser 0'): + expect( + self.importe_rectificacion['BaseRectificada'] + ).to(equal(0)) + + with it('la CuotaRectificada debe ser 0'): + expect( + self.importe_rectificacion['CuotaRectificada'] + ).to(equal(0)) + + with context('en los detalles del IVA'): + with before.all: + detalle_iva = ( + self.fact_rect_emit['FacturaExpedida']['TipoDesglose'] + ['DesgloseFactura']['Sujeta']['NoExenta']['DesgloseIVA'] + ['DetalleIVA'] + ) + self.grouped_detalle_iva = group_by_tax_rate( + detalle_iva, in_invoice=False + ) + + with it('la BaseImponible debe ser la original'): + expect( + self.grouped_detalle_iva[5.0]['BaseImponible'] + ).to(equal( + self.out_refund.tax_line[0].base + )) + with it('la CuotaRepercutida debe ser la original'): + expect( + self.grouped_detalle_iva[5.0]['CuotaRepercutida'] + ).to(equal( + -1 * abs(self.out_refund.tax_line[0].tax_amount) + )) + with it('el TipoImpositivo debe ser la original'): + expect( + self.grouped_detalle_iva[5.0]['TipoImpositivo'] + ).to(equal( + self.out_refund.tax_line[0].tax_id.amount * 100 + )) + + with description('en los datos de una factura rectificativa de una rectificativa emitida iva 5%'): + with before.all: + self.out_refund, self.out_b_inovice = self.data_gen.get_out_refund_invoice_iva5_multi() + self.out_refund_obj = SII(self.out_refund).generate_object() + self.out_b_inovice_obj = SII(self.out_b_inovice).generate_object() + self.fact_rect_emit = ( + self.out_refund_obj['SuministroLRFacturasEmitidas'] + ['RegistroLRFacturasEmitidas'] + ) + self.fact_refund_emit = ( + self.out_b_inovice_obj['SuministroLRFacturasEmitidas'] + ['RegistroLRFacturasEmitidas'] + ) + with context('en los datos de abonadora'): + with it('la FechaOperacion debe ser por factura original'): + expect( + self.fact_refund_emit['FacturaExpedida']['FechaOperacion'] + ).to(equal('01-01-2023')) + with context('en los datos de rectificación'): + with it('la FechaOperacion debe ser por factura original'): + expect( + self.fact_rect_emit['FacturaExpedida']['FechaOperacion'] + ).to(equal('01-01-2023')) + with description('en los datos de una factura rectificativa de una rectificativa emitida'): with before.all: self.out_refund, self.out_b_inovice = self.data_gen.get_out_refund_mulitple_invoice() @@ -1007,16 +1180,14 @@ def group_by_tax_rate(iva_values, in_invoice): ) with context('en los datos de abonadora'): with it('la FechaOperacion debe ser por factura original'): - pass - # expect( - # self.fact_refund_emit['FacturaExpedida']['FechaOperacion'] - # ).to(equal('07-12-2023')) + expect( + self.fact_refund_emit['FacturaExpedida'].get('FechaOperacion', False) + ).to(equal(False)) with context('en los datos de rectificación'): - with it('la FechaOperacion debe ser por factura original'): - pass - # expect( - # self.fact_rect_emit['FacturaExpedida']['FechaOperacion'] - # ).to(equal('07-12-2023')) + with it('la FechaOperacion NO debe existir'): + expect( + self.fact_rect_emit['FacturaExpedida'].get('FechaOperacion', False) + ).to(equal(False)) with description('en los datos de una factura rectificativa recibida'): with before.all: diff --git a/spec/testing_data.py b/spec/testing_data.py index 79fcd4c..a1b284e 100644 --- a/spec/testing_data.py +++ b/spec/testing_data.py @@ -13,11 +13,13 @@ def __init__(self, invoice_registered=False, contraparte_registered=True): self.article = Article(tipo_factura='R1', tipo_rectificativa='I') name_iva_21 = 'IVA 21%' name_iva_4 = 'IVA 4%' + name_iva_5 = 'IVA 5%' name_ibi = 'IBI 15%' name_iva_exento = 'IVA Exento' tax_ibi = Tax(name=name_ibi, amount=0.15, type='percent') tax_iva_21 = Tax(name=name_iva_21, amount=0.21, type='percent') - tax_iva_4 = Tax(name=name_iva_21, amount=0.04, type='percent') + tax_iva_4 = Tax(name=name_iva_4, amount=0.04, type='percent') + self.tax_iva_5 = Tax(name=name_iva_5, amount=0.05, type='percent') tax_iva_exento = Tax(name=name_iva_exento, amount=0, type='percent') self.invoice_line = [ InvoiceLine(price_subtotal=100, invoice_line_tax_id=[tax_iva_21]), @@ -428,7 +430,244 @@ def get_out_refund_invoice(self): ) return r_invoice, b_invoice - def get_out_refund_mulitple_invoice(self): + def get_out_refund_invoice_iva5(self, fecha_facturas_recti=None): + journal = Journal( + name=u'Factura de Energía Rectificativa Emitida' + ) + base_bruta = 10 + invoice_tax_iva_5 = InvoiceTax( + name=self.tax_iva_5.name, base=base_bruta, + tax_amount=base_bruta * self.tax_iva_5.amount, tax_id=self.tax_iva_5 + ) + tax_line = [ + invoice_tax_iva_5 + ] + n_total_amount = 0 + n_total_tax = 0 + for tl in tax_line: + n_total_amount += tl.base + tl.tax_amount + n_total_tax += tl.tax_amount + + n_date_invoice = '2023-01-01' + n_period = Period(name='01/2023') + + if fecha_facturas_recti: + b_date_invoice = fecha_facturas_recti + y,m,d = fecha_facturas_recti.split('-') + b_period = Period(name='{}/{}'.format(m,y)) + else: + b_date_invoice = '2024-10-01' + b_period = Period(name='01/2024') + refund_tax_line = tax_line[:] + for invoice_tax in refund_tax_line: + invoice_tax.tax_amount = -1 * abs(invoice_tax.tax_amount) + + orig_invoice = Invoice( + invoice_type='out_invoice', + journal_id=journal, + rectificative_type='N', + rectifying_id=False, + number='FEmitRectificada{}'.format(self.invoice_number), + partner_id=self.partner_invoice, + address_contact_id=self.address_contact_id, + company_id=self.company, + amount_total=n_total_amount, + amount_untaxed=base_bruta, + amount_tax=n_total_tax, + period_id=n_period, + date_invoice=n_date_invoice, + tax_line=tax_line, + invoice_line=[], # no se usa + sii_registered=self.sii_registered, + fiscal_position=self.fiscal_position, + sii_description=self.sii_description, + sii_out_clave_regimen_especial=self.sii_out_clave_regimen_especial, + ) + + r_invoice = Invoice( + invoice_type='out_invoice', + journal_id=journal, + rectificative_type='R', + rectifying_id=orig_invoice, + number='FRectEmit{}'.format(self.invoice_number), + partner_id=self.partner_invoice, + address_contact_id=self.address_contact_id, + company_id=self.company, + amount_total=n_total_amount, + amount_untaxed=base_bruta, + amount_tax=n_total_tax, + period_id=b_period, + date_invoice=b_date_invoice, + tax_line=tax_line, + invoice_line=self.invoice_line, + sii_registered=self.sii_registered, + fiscal_position=self.fiscal_position, + sii_description=self.sii_description, + sii_out_clave_regimen_especial=self.sii_out_clave_regimen_especial, + ) + b_invoice = Invoice( + invoice_type='out_refund', + journal_id=journal, + rectificative_type='B', + rectifying_id=orig_invoice, + number='FEmitRectificada{}'.format(self.invoice_number), + partner_id=self.partner_invoice, + address_contact_id=self.address_contact_id, + company_id=self.company, + amount_total=-1 * n_total_amount, + amount_untaxed=-1 * base_bruta, + amount_tax=-1 * n_total_tax, + period_id=b_period, + date_invoice=b_date_invoice, + tax_line=refund_tax_line, + invoice_line=[], # no se usa + sii_registered=self.sii_registered, + fiscal_position=self.fiscal_position, + sii_description=self.sii_description, + sii_out_clave_regimen_especial=self.sii_out_clave_regimen_especial, + ) + return r_invoice, b_invoice + + def get_out_refund_invoice_iva5_multi(self, fecha_facturas_recti=None): + journal = Journal( + name=u'Factura de Energía Rectificativa Emitida' + ) + base_bruta = 10 + invoice_tax_iva_5 = InvoiceTax( + name=self.tax_iva_5.name, base=base_bruta, + tax_amount=base_bruta * self.tax_iva_5.amount, tax_id=self.tax_iva_5 + ) + tax_line = [ + invoice_tax_iva_5 + ] + n_total_amount = 0 + n_total_tax = 0 + for tl in tax_line: + n_total_amount += tl.base + tl.tax_amount + n_total_tax += tl.tax_amount + + n_date_invoice = '2023-01-01' + n_period = Period(name='01/2023') + + if fecha_facturas_recti: + b_date_invoice = fecha_facturas_recti + y,m,d = fecha_facturas_recti.split('-') + b_period = Period(name='{}/{}'.format(m,y)) + else: + b_date_invoice = '2024-10-01' + b_period = Period(name='01/2024') + refund_tax_line = tax_line[:] + for invoice_tax in refund_tax_line: + invoice_tax.tax_amount = -1 * abs(invoice_tax.tax_amount) + + orig_invoice = Invoice( + invoice_type='out_invoice', + journal_id=journal, + rectificative_type='N', + rectifying_id=False, + number='FEmitRectificada{}'.format(self.invoice_number), + partner_id=self.partner_invoice, + address_contact_id=self.address_contact_id, + company_id=self.company, + amount_total=n_total_amount, + amount_untaxed=base_bruta, + amount_tax=n_total_tax, + period_id=n_period, + date_invoice=n_date_invoice, + tax_line=tax_line, + invoice_line=[], # no se usa + sii_registered=self.sii_registered, + fiscal_position=self.fiscal_position, + sii_description=self.sii_description, + sii_out_clave_regimen_especial=self.sii_out_clave_regimen_especial, + ) + + r_invoice = Invoice( + invoice_type='out_invoice', + journal_id=journal, + rectificative_type='R', + rectifying_id=orig_invoice, + number='FRectEmit{}'.format(self.invoice_number), + partner_id=self.partner_invoice, + address_contact_id=self.address_contact_id, + company_id=self.company, + amount_total=n_total_amount, + amount_untaxed=base_bruta, + amount_tax=n_total_tax, + period_id=b_period, + date_invoice=b_date_invoice, + tax_line=tax_line, + invoice_line=self.invoice_line, + sii_registered=self.sii_registered, + fiscal_position=self.fiscal_position, + sii_description=self.sii_description, + sii_out_clave_regimen_especial=self.sii_out_clave_regimen_especial, + ) + b_invoice = Invoice( + invoice_type='out_refund', + journal_id=journal, + rectificative_type='B', + rectifying_id=orig_invoice, + number='FEmitRectificada{}'.format(self.invoice_number), + partner_id=self.partner_invoice, + address_contact_id=self.address_contact_id, + company_id=self.company, + amount_total=-1 * n_total_amount, + amount_untaxed=-1 * base_bruta, + amount_tax=-1 * n_total_tax, + period_id=b_period, + date_invoice=b_date_invoice, + tax_line=refund_tax_line, + invoice_line=[], # no se usa + sii_registered=self.sii_registered, + fiscal_position=self.fiscal_position, + sii_description=self.sii_description, + sii_out_clave_regimen_especial=self.sii_out_clave_regimen_especial, + ) + r2_invoice = Invoice( + invoice_type='out_invoice', + journal_id=journal, + rectificative_type='R', + rectifying_id=r_invoice, + number='FRectEmit{}'.format(self.invoice_number), + partner_id=self.partner_invoice, + address_contact_id=self.address_contact_id, + company_id=self.company, + amount_total=n_total_amount, + amount_untaxed=base_bruta, + amount_tax=n_total_tax, + period_id=b_period, + date_invoice=b_date_invoice, + tax_line=tax_line, + invoice_line=self.invoice_line, + sii_registered=self.sii_registered, + fiscal_position=self.fiscal_position, + sii_description=self.sii_description, + sii_out_clave_regimen_especial=self.sii_out_clave_regimen_especial, + ) + b2_invoice = Invoice( + invoice_type='out_refund', + journal_id=journal, + rectificative_type='B', + rectifying_id=r_invoice, + number='FEmitRectificada{}'.format(self.invoice_number), + partner_id=self.partner_invoice, + address_contact_id=self.address_contact_id, + company_id=self.company, + amount_total=-1 * n_total_amount, + amount_untaxed=-1 * base_bruta, + amount_tax=-1 * n_total_tax, + period_id=b_period, + date_invoice=b_date_invoice, + tax_line=refund_tax_line, + invoice_line=[], # no se usa + sii_registered=self.sii_registered, + fiscal_position=self.fiscal_position, + sii_description=self.sii_description, + sii_out_clave_regimen_especial=self.sii_out_clave_regimen_especial, + ) + return r2_invoice, b2_invoice + def get_out_refund_mulitple_invoice(self, fecha_facturas_recti=None): journal = Journal( name=u'Factura de Energía Rectificativa Emitida' )