290 lines
13 KiB
Python
290 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from collections import namedtuple
|
|
from lxml import etree
|
|
|
|
from odoo import fields
|
|
from odoo.tests import tagged
|
|
from odoo.addons.l10n_it_edi.tests.common import TestItEdi
|
|
|
|
|
|
@tagged('post_install_l10n', 'post_install', '-at_install')
|
|
class TestItEdiReverseCharge(TestItEdi):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
|
|
# Helper functions -----------
|
|
def get_tag_ids(tag_codes):
|
|
""" Helper function to define tag ids for taxes """
|
|
return cls.env['account.account.tag'].search([
|
|
('applicability', '=', 'taxes'),
|
|
('country_id.code', '=', 'IT'),
|
|
('name', 'in', tag_codes)]).ids
|
|
|
|
RepartitionLine = namedtuple('Line', 'factor_percent repartition_type tag_ids')
|
|
def repartition_lines(*lines):
|
|
""" Helper function to define repartition lines in taxes """
|
|
return [(5, 0, 0)] + [(0, 0, {**line._asdict(), 'tag_ids': get_tag_ids(line[2])}) for line in lines]
|
|
|
|
ProductLine = namedtuple('Line', 'data name product_id')
|
|
def product_lines(*lines):
|
|
""" Helper function to define move lines based on a product """
|
|
return [(0, 0, {**line[0], 'name': line[1], 'product_id': line[2]}) for line in lines]
|
|
|
|
# Company -----------
|
|
cls.company.partner_id.l10n_it_pa_index = "0803HR0"
|
|
|
|
# Partner -----------
|
|
cls.french_partner = cls.env['res.partner'].create({
|
|
'name': 'Alessi',
|
|
'vat': 'FR15437982937',
|
|
'country_id': cls.env.ref('base.fr').id,
|
|
'street': 'Avenue Test rue',
|
|
'zip': '84000',
|
|
'city': 'Avignon',
|
|
'is_company': True
|
|
})
|
|
|
|
cls.san_marino_partner = cls.env['res.partner'].create({
|
|
'name': 'Prospectra',
|
|
'vat': 'SM6784',
|
|
'country_id': cls.env.ref('base.sm').id,
|
|
'street': 'Via Ventotto Luglio 212 Centro Uffici',
|
|
'zip': '47893',
|
|
'city': 'San Marino',
|
|
'company_id': cls.company.id,
|
|
'is_company': True,
|
|
})
|
|
|
|
# Taxes -----------
|
|
tax_data = {
|
|
'name': 'Tax 4% (Goods) Reverse Charge',
|
|
'amount': 4.0,
|
|
'amount_type': 'percent',
|
|
'type_tax_use': 'purchase',
|
|
'invoice_repartition_line_ids': repartition_lines(
|
|
RepartitionLine(100, 'base', ('+03', '+vj9')),
|
|
RepartitionLine(100, 'tax', ('+5v',)),
|
|
RepartitionLine(-100, 'tax', ('-4v',))),
|
|
'refund_repartition_line_ids': repartition_lines(
|
|
RepartitionLine(100, 'base', ('-03', '-vj9')),
|
|
RepartitionLine(100, 'tax', False),
|
|
RepartitionLine(-100, 'tax', False)),
|
|
}
|
|
# Purchase tax 4% with Reverse Charge
|
|
cls.purchase_tax_4p = cls.env['account.tax'].with_company(cls.company).create(tax_data)
|
|
cls.line_tax_4p = cls.standard_line.copy()
|
|
cls.line_tax_4p['tax_ids'] = [(6, 0, cls.purchase_tax_4p.ids)]
|
|
|
|
# Purchase tax 4% with Reverse Charge, targeting the tax grid for import of goods
|
|
# already in Italy in a VAT deposit
|
|
tax_data_4p_already_in_italy = {
|
|
**tax_data,
|
|
'name': 'Tax 4% purchase Reverse Charge, in Italy',
|
|
'invoice_repartition_line_ids': repartition_lines(
|
|
RepartitionLine(100, 'base', ('+03', '+vj3')),
|
|
RepartitionLine(100, 'tax', ('+5v',)),
|
|
RepartitionLine(-100, 'tax', ('-4v',))),
|
|
'refund_repartition_line_ids': repartition_lines(
|
|
RepartitionLine(100, 'base', ('-03', '-vj3')),
|
|
RepartitionLine(100, 'tax', False),
|
|
RepartitionLine(-100, 'tax', False)),
|
|
}
|
|
|
|
cls.purchase_tax_4p_already_in_italy = cls.env['account.tax'].with_company(cls.company).create(tax_data_4p_already_in_italy)
|
|
cls.line_tax_4p_already_in_italy = cls.standard_line.copy()
|
|
cls.line_tax_4p_already_in_italy['tax_ids'] = [(6, 0, cls.purchase_tax_4p_already_in_italy.ids)]
|
|
|
|
# Purchase tax 22% with Reverse Charge
|
|
tax_data_22p = {**tax_data, 'name': 'Tax 22% purchase Reverse Charge', 'amount': 22.0}
|
|
cls.purchase_tax_22p = cls.env['account.tax'].with_company(cls.company).create(tax_data_22p)
|
|
cls.line_tax_22p = cls.standard_line.copy()
|
|
cls.line_tax_22p['tax_ids'] = [(6, 0, cls.purchase_tax_22p.ids)]
|
|
|
|
# Export tax 0%
|
|
tax_data_0v = {**tax_data, "type_tax_use": "sale", "amount": 0}
|
|
cls.sale_tax_0v = cls.env['account.tax'].with_company(cls.company).create(tax_data_0v)
|
|
cls.line_tax_sale = cls.standard_line.copy()
|
|
cls.line_tax_sale['tax_ids'] = [(6, 0, cls.sale_tax_0v.ids)]
|
|
|
|
# Products -----------
|
|
# Product A with 0% sale export and tax 4% reverse carge purchase tax
|
|
product_a = cls.env['product.product'].with_company(cls.company).create({
|
|
'name': 'product_a',
|
|
'lst_price': 1000.0,
|
|
'standard_price': 800.0,
|
|
'type': 'consu',
|
|
'taxes_id': [(6, 0, cls.sale_tax_0v.ids)],
|
|
'supplier_taxes_id': [(6, 0, cls.purchase_tax_4p.ids)],
|
|
})
|
|
# Product B with 0% sale export and tax 4% reverse charge purchase tax
|
|
product_b = cls.env['product.product'].with_company(cls.company).create({
|
|
'name': 'product_b',
|
|
'lst_price': 1000.0,
|
|
'standard_price': 800.0,
|
|
'type': 'consu',
|
|
'taxes_id': [(6, 0, cls.sale_tax_0v.ids)],
|
|
'supplier_taxes_id': [(6, 0, cls.purchase_tax_4p.ids)],
|
|
})
|
|
|
|
# Moves -----------
|
|
# Export invoice
|
|
cls.reverse_charge_invoice = cls.env['account.move'].with_company(cls.company).create({
|
|
'company_id': cls.company.id,
|
|
'move_type': 'out_invoice',
|
|
'invoice_date': fields.Date.from_string('2022-03-24'),
|
|
'invoice_date_due': fields.Date.from_string('2022-03-24'),
|
|
'partner_id': cls.french_partner.id,
|
|
'partner_bank_id': cls.test_bank.id,
|
|
'invoice_line_ids': product_lines(
|
|
ProductLine(cls.line_tax_sale, 'Product A', product_a.id),
|
|
ProductLine(cls.line_tax_sale, 'Product B', product_b.id)
|
|
),
|
|
})
|
|
|
|
# Import bill #1
|
|
bill_data = {
|
|
'company_id': cls.company.id,
|
|
'move_type': 'in_invoice',
|
|
'invoice_date': fields.Date.from_string('2022-03-24'),
|
|
'invoice_date_due': fields.Date.from_string('2022-03-24'),
|
|
'partner_id': cls.french_partner.id,
|
|
'partner_bank_id': cls.test_bank.id,
|
|
'invoice_line_ids': product_lines(
|
|
ProductLine(cls.line_tax_22p, 'Product A', product_a.id),
|
|
ProductLine(cls.line_tax_4p, 'Product B, taxed 4%', product_b.id)
|
|
)
|
|
}
|
|
cls.reverse_charge_bill = cls.env['account.move'].with_company(cls.company).create(bill_data)
|
|
|
|
# Import bill #2
|
|
bill_data_2 = {
|
|
**bill_data,
|
|
'invoice_line_ids': product_lines(
|
|
ProductLine(cls.line_tax_22p, 'Product A', product_a.id),
|
|
ProductLine(cls.line_tax_4p_already_in_italy, 'Product B, taxed 4% Already in Italy', product_b.id),
|
|
),
|
|
}
|
|
cls.reverse_charge_bill_2 = cls.env['account.move'].with_company(cls.company).create(bill_data_2)
|
|
cls.reverse_charge_refund = cls.reverse_charge_bill.with_company(cls.company)._reverse_moves([{
|
|
'invoice_date': fields.Date.from_string('2022-03-24'),
|
|
}])
|
|
|
|
# Import bill San Marino
|
|
bill_data_san_marino = {
|
|
'company_id': cls.company.id,
|
|
'move_type': 'in_invoice',
|
|
'invoice_date': fields.Date.from_string('2022-03-24'),
|
|
'invoice_date_due': fields.Date.from_string('2022-03-24'),
|
|
'partner_id': cls.san_marino_partner.id,
|
|
'partner_bank_id': cls.test_bank.id,
|
|
'invoice_line_ids': product_lines(
|
|
ProductLine(cls.line_tax_22p, 'Product A', product_a.id),
|
|
ProductLine(cls.line_tax_4p, 'Product B, taxed 4%', product_b.id)
|
|
)
|
|
}
|
|
cls.reverse_charge_bill_san_marino = cls.env['account.move'].with_company(cls.company).create(bill_data_san_marino)
|
|
|
|
# Posting moves -----------
|
|
cls.reverse_charge_invoice._post()
|
|
cls.reverse_charge_bill._post()
|
|
cls.reverse_charge_bill_2._post()
|
|
cls.reverse_charge_bill_san_marino._post()
|
|
cls.reverse_charge_refund._post()
|
|
|
|
def test_reverse_charge_invoice(self):
|
|
self._test_invoice_with_sample_file(self.reverse_charge_invoice, "reverse_charge_invoice.xml")
|
|
|
|
def test_reverse_charge_bill(self):
|
|
self._test_invoice_with_sample_file(self.reverse_charge_bill, "reverse_charge_bill.xml")
|
|
|
|
def test_reverse_charge_bill_2(self):
|
|
self._test_invoice_with_sample_file(
|
|
self.reverse_charge_bill_2,
|
|
"reverse_charge_bill.xml",
|
|
xpaths_result={
|
|
"//DatiGeneraliDocumento/Numero": "<Numero/>",
|
|
"(//DettaglioLinee/Descrizione)[2]": "<Descrizione/>",
|
|
},
|
|
xpaths_file={
|
|
"//DatiGeneraliDocumento/TipoDocumento": "<TipoDocumento>TD19</TipoDocumento>",
|
|
"//DatiGeneraliDocumento/Numero": "<Numero/>",
|
|
"(//DettaglioLinee/Descrizione)[2]": "<Descrizione/>",
|
|
}
|
|
)
|
|
|
|
|
|
def test_reverse_charge_bill_san_marino(self):
|
|
self._test_invoice_with_sample_file(
|
|
self.reverse_charge_bill_san_marino,
|
|
"reverse_charge_bill.xml",
|
|
xpaths_result={
|
|
"//DatiGeneraliDocumento/Numero": "<Numero/>",
|
|
"(//DettaglioLinee/Descrizione)[2]": "<Descrizione/>",
|
|
},
|
|
xpaths_file={
|
|
"//CedentePrestatore": """
|
|
<CedentePrestatore>
|
|
<DatiAnagrafici>
|
|
<IdFiscaleIVA>
|
|
<IdPaese>SM</IdPaese>
|
|
<IdCodice>6784</IdCodice>
|
|
</IdFiscaleIVA>
|
|
<Anagrafica>
|
|
<Denominazione>Prospectra</Denominazione>
|
|
</Anagrafica>
|
|
<RegimeFiscale>RF18</RegimeFiscale>
|
|
</DatiAnagrafici>
|
|
<Sede>
|
|
<Indirizzo>Via Ventotto Luglio 212 Centro Uffici</Indirizzo>
|
|
<CAP>00000</CAP>
|
|
<Comune>San Marino</Comune>
|
|
<Nazione>SM</Nazione>
|
|
</Sede>
|
|
</CedentePrestatore>
|
|
""",
|
|
"//DatiGeneraliDocumento/TipoDocumento": "<TipoDocumento>TD28</TipoDocumento>",
|
|
"//DatiGeneraliDocumento/Numero": "<Numero/>",
|
|
"(//DettaglioLinee/Descrizione)[2]": "<Descrizione/>",
|
|
}
|
|
)
|
|
|
|
def test_reverse_charge_refund(self):
|
|
self._test_invoice_with_sample_file(
|
|
self.reverse_charge_refund,
|
|
"reverse_charge_bill.xml",
|
|
xpaths_result={
|
|
"//DatiGeneraliDocumento/Numero": "<Numero/>",
|
|
"//DatiPagamento/DettaglioPagamento/DataScadenzaPagamento": "<DataScadenzaPagamento/>",
|
|
},
|
|
xpaths_file={
|
|
"//DatiGenerali": f"""
|
|
<DatiGenerali>
|
|
<DatiGeneraliDocumento>
|
|
<TipoDocumento>TD18</TipoDocumento>
|
|
<Divisa>EUR</Divisa>
|
|
<Data>2022-03-24</Data>
|
|
<Numero/>
|
|
<ImportoTotaleDocumento>-1808.91</ImportoTotaleDocumento>
|
|
</DatiGeneraliDocumento>
|
|
<DatiFattureCollegate>
|
|
<IdDocumento>{self.reverse_charge_bill.name}</IdDocumento>
|
|
<Data>{self.reverse_charge_refund.invoice_date}</Data>
|
|
</DatiFattureCollegate>
|
|
</DatiGenerali>
|
|
""",
|
|
"//DatiPagamento/DettaglioPagamento/DataScadenzaPagamento": "<DataScadenzaPagamento/>",
|
|
"(//DettaglioLinee/PrezzoUnitario)[1]": "<PrezzoUnitario>-800.400000</PrezzoUnitario>",
|
|
"(//DettaglioLinee/PrezzoUnitario)[2]": "<PrezzoUnitario>-800.400000</PrezzoUnitario>",
|
|
"(//DettaglioLinee/PrezzoTotale)[1]": "<PrezzoTotale>-800.40</PrezzoTotale>",
|
|
"(//DettaglioLinee/PrezzoTotale)[2]": "<PrezzoTotale>-800.40</PrezzoTotale>",
|
|
"(//DatiRiepilogo/ImponibileImporto)[1]": "<ImponibileImporto>-800.40</ImponibileImporto>",
|
|
"(//DatiRiepilogo/ImponibileImporto)[2]": "<ImponibileImporto>-800.40</ImponibileImporto>",
|
|
"(//DatiRiepilogo/Imposta)[1]": "<Imposta>-176.09</Imposta>",
|
|
"(//DatiRiepilogo/Imposta)[2]": "<Imposta>-32.02</Imposta>",
|
|
}
|
|
)
|