Skip to content

Commit bf6e70a

Browse files
Merge pull request #2 from factorlibre/16.0_intrastat_product_zero_price_transaction_23
[IMP] intrastat_product: use transaction 23 and sale price for zero-p…
2 parents 014e298 + 21fafc3 commit bf6e70a

File tree

3 files changed

+124
-18
lines changed

3 files changed

+124
-18
lines changed

intrastat_product/models/intrastat_product_declaration.py

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -803,27 +803,56 @@ def _gather_invoices(self, notedict):
803803
)
804804

805805
for line_vals in lines_current_invoice:
806-
if (
807-
not line_vals["amount_company_currency"]
808-
and not line_vals["amount_accessory_cost_company_currency"]
809-
and not self.company_id.intrastat_include_zero_price_lines
810-
):
811-
inv_line = self.env["account.move.line"].browse(
812-
line_vals["invoice_line_id"]
813-
)
814-
_logger.info(
815-
"Skipping invoice line %s qty %s "
816-
"of invoice %s. Reason: price_subtotal = 0 "
817-
"and accessory costs = 0",
818-
inv_line.name,
819-
inv_line.quantity,
820-
inv_line.move_id.name,
821-
)
806+
if not self._should_include_zero_price_line(line_vals):
822807
continue
823808
lines.append(line_vals)
824809

825810
return lines
826811

812+
def _should_include_zero_price_line(self, line_vals):
813+
"""Check if a zero-price line should be included in the declaration.
814+
815+
Returns True if the line should be included, False if it should be
816+
skipped. When included, updates line values with transaction 23
817+
and the product's sale price.
818+
"""
819+
if (
820+
line_vals["amount_company_currency"]
821+
or line_vals["amount_accessory_cost_company_currency"]
822+
):
823+
return True
824+
if not self.company_id.intrastat_include_zero_price_lines:
825+
inv_line = self.env["account.move.line"].browse(
826+
line_vals["invoice_line_id"]
827+
)
828+
_logger.info(
829+
"Skipping invoice line %s qty %s "
830+
"of invoice %s. Reason: price_subtotal = 0 "
831+
"and accessory costs = 0",
832+
inv_line.name,
833+
inv_line.quantity,
834+
inv_line.move_id.name,
835+
)
836+
return False
837+
self._update_zero_price_line_vals(line_vals)
838+
return True
839+
840+
def _update_zero_price_line_vals(self, line_vals):
841+
"""Update zero-price line values for Intrastat declaration.
842+
843+
When zero-price lines are included (e.g. warranty replacements),
844+
they must use transaction code 23 and the product's sale price
845+
as fiscal value, per EU Intrastat regulation.
846+
"""
847+
tr_23 = self.env.ref("intrastat_product.intrastat_transaction_23")
848+
line_vals["transaction_id"] = tr_23.id
849+
inv_line = self.env["account.move.line"].browse(line_vals["invoice_line_id"])
850+
product = inv_line.product_id
851+
if product and product.list_price:
852+
line_vals["amount_company_currency"] = (
853+
product.list_price * inv_line.quantity
854+
)
855+
827856
def _prepare_html_note(self, notedict, key2label):
828857
note = ""
829858
for key, entries in notedict.items():

intrastat_product/models/res_company.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ class ResCompany(models.Model):
4040
)
4141
intrastat_include_zero_price_lines = fields.Boolean(
4242
string="Include Intrastat Lines with Zero Price",
43-
help="If checked, Intrastat lines with zero price will be added to the declaration,"
44-
"zero price lines are normally for repairing and maintenance operations.",
43+
help="If checked, Intrastat lines with zero price will be added to the "
44+
"declaration with transaction code 23 (warranty replacement) and the "
45+
"product's sale price as fiscal value. "
46+
"Zero price lines are normally for repairing and maintenance operations.",
4547
default=False,
4648
)
4749

intrastat_product/tests/test_intrastat_product.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
44
from psycopg2 import IntegrityError
55

6+
from odoo import Command
67
from odoo.exceptions import UserError, ValidationError
78
from odoo.tests import Form
89
from odoo.tests.common import TransactionCase
@@ -158,6 +159,80 @@ def test_invoice_report_with_intrastat_lines(self):
158159
self.invoice.compute_intrastat_lines()
159160
self._test_invoice_report(2)
160161

162+
def test_zero_price_lines_transaction_23(self):
163+
"""Zero price lines included in Intrastat must use transaction 23
164+
and the product's sale price as fiscal value."""
165+
self.demo_company.intrastat_include_zero_price_lines = True
166+
product_c3po = self.product_c3po.product_variant_ids[0]
167+
list_price = product_c3po.list_price
168+
self.assertTrue(list_price, "Product must have a list_price for this test")
169+
invoice = self.env["account.move"].create(
170+
{
171+
"move_type": "out_invoice",
172+
"partner_id": self.partner.id,
173+
"fiscal_position_id": self.position.id,
174+
"invoice_line_ids": [
175+
Command.create(
176+
{
177+
"product_id": product_c3po.id,
178+
"quantity": 3,
179+
"price_unit": 0.0,
180+
"name": "Warranty replacement",
181+
},
182+
)
183+
],
184+
}
185+
)
186+
invoice.action_post()
187+
declaration = self.declaration_obj.create(
188+
{
189+
"company_id": self.demo_company.id,
190+
"declaration_type": "dispatches",
191+
}
192+
)
193+
declaration.action_gather()
194+
zero_lines = declaration.computation_line_ids.filtered(
195+
lambda l: l.invoice_line_id.move_id == invoice
196+
)
197+
self.assertEqual(len(zero_lines), 1)
198+
tr_23 = self.env.ref("intrastat_product.intrastat_transaction_23")
199+
self.assertEqual(zero_lines.transaction_id, tr_23)
200+
self.assertEqual(zero_lines.amount_company_currency, list_price * 3)
201+
202+
def test_zero_price_lines_excluded_by_default(self):
203+
"""Zero price lines must be excluded when config is disabled."""
204+
self.demo_company.intrastat_include_zero_price_lines = False
205+
product_c3po = self.product_c3po.product_variant_ids[0]
206+
invoice = self.env["account.move"].create(
207+
{
208+
"move_type": "out_invoice",
209+
"partner_id": self.partner.id,
210+
"fiscal_position_id": self.position.id,
211+
"invoice_line_ids": [
212+
Command.create(
213+
{
214+
"product_id": product_c3po.id,
215+
"quantity": 1,
216+
"price_unit": 0.0,
217+
"name": "Warranty replacement",
218+
},
219+
)
220+
],
221+
}
222+
)
223+
invoice.action_post()
224+
declaration = self.declaration_obj.create(
225+
{
226+
"company_id": self.demo_company.id,
227+
"declaration_type": "dispatches",
228+
}
229+
)
230+
declaration.action_gather()
231+
zero_lines = declaration.computation_line_ids.filtered(
232+
lambda l: l.invoice_line_id.move_id == invoice
233+
)
234+
self.assertFalse(zero_lines)
235+
161236

162237
class TestIntrastatProductCase(TestIntrastatProduct, TransactionCase):
163238
"""Test Intrastat Product"""

0 commit comments

Comments
 (0)