Merge branch 'master-upstream-patch-06072022' into 'master'

[PATCH] Upstream patch - 06072022

See merge request flectra-hq/flectra!735
This commit is contained in:
Parthiv Patel committed 2022-07-06 10:55:27 +00:00
commit 73ec8575c6
38 files changed
+2404 -543

No files matched your search

+4 -4
View File
@@ -392,7 +392,7 @@ class AccountMove(models.Model):
if self.is_sale_document(include_receipts=True):
return invoice_date
elif self.is_purchase_document(include_receipts=True):
highest_name = self.highest_name or self._get_last_sequence(relaxed=True)
highest_name = self.highest_name or self._get_last_sequence(relaxed=True, lock=False)
number_reset = self._deduce_sequence_number_reset(highest_name)
if not highest_name or number_reset == 'month':
if (today.year, today.month) > (invoice_date.year, invoice_date.month):
@@ -427,7 +427,7 @@ class AccountMove(models.Model):
if new_currency != self.currency_id:
self.currency_id = new_currency
self._onchange_currency()
if self.state == 'draft' and self._get_last_sequence() and self.name and self.name != '/':
if self.state == 'draft' and self._get_last_sequence(lock=False) and self.name and self.name != '/':
self.name = '/'
@api.onchange('partner_id')
@@ -1182,7 +1182,7 @@ class AccountMove(models.Model):
)
)
self = self.sorted(lambda m: (m.date, m.ref or '', m.id))
highest_name = self[0]._get_last_sequence() if self else False
highest_name = self[0]._get_last_sequence(lock=False) if self else False
# Group the moves by journal and month
for move in self:
@@ -1242,7 +1242,7 @@ class AccountMove(models.Model):
@api.depends('journal_id', 'date')
def _compute_highest_name(self):
for record in self:
record.highest_name = record._get_last_sequence()
record.highest_name = record._get_last_sequence(lock=False)
@api.onchange('name', 'highest_name')
def _onchange_name_warning(self):
+13 -11
View File
@@ -132,7 +132,7 @@ class SequenceMixin(models.AbstractModel):
self.ensure_one()
return "00000000"
def _get_last_sequence(self, relaxed=False):
def _get_last_sequence(self, relaxed=False, lock=True):
"""Retrieve the previous sequence.
This is done by taking the number with the greatest alphabetical value within
@@ -160,20 +160,22 @@ class SequenceMixin(models.AbstractModel):
where_string += " AND id != %(id)s "
param['id'] = self.id or self.id.origin
query = """
UPDATE {table} SET write_date = write_date WHERE id = (
SELECT id FROM {table}
query = f"""
SELECT {{field}} FROM {self._table}
{where_string}
AND sequence_prefix = (SELECT sequence_prefix FROM {table} {where_string} ORDER BY id DESC LIMIT 1)
AND sequence_prefix = (SELECT sequence_prefix FROM {self._table} {where_string} ORDER BY id DESC LIMIT 1)
ORDER BY sequence_number DESC
LIMIT 1
"""
if lock:
query = f"""
UPDATE {self._table} SET write_date = write_date WHERE id = (
{query.format(field='id')}
)
RETURNING {field};
""".format(
table=self._table,
where_string=where_string,
field=self._sequence_field,
)
RETURNING {self._sequence_field};
"""
else:
query = query.format(field=self._sequence_field)
self.flush([self._sequence_field, 'sequence_number', 'sequence_prefix'])
self.env.cr.execute(query, param)
+96 -49
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from flectra.addons.account.tests.common import AccountTestInvoicingCommon
from flectra.tests import tagged
from flectra.tests.common import Form
from flectra.tests.common import Form, TransactionCase
from flectra import fields, api, SUPERUSER_ID
from flectra.exceptions import ValidationError
from flectra.tools import mute_logger
@@ -348,54 +348,6 @@ class TestSequenceMixin(AccountTestInvoicingCommon):
reset_never = set_sequence(self.test_move.date + relativedelta(years=2), 'MISC/00100')
test_date(reset_never.date, 'MISC/00101') # Keep the new prefix in 2018
def test_sequence_concurency(self):
"""Computing the same name in concurent transactions is not allowed."""
with self.env.registry.cursor() as cr0,\
self.env.registry.cursor() as cr1,\
self.env.registry.cursor() as cr2:
env0 = api.Environment(cr0, SUPERUSER_ID, {})
env1 = api.Environment(cr1, SUPERUSER_ID, {})
env2 = api.Environment(cr2, SUPERUSER_ID, {})
journal = env0['account.journal'].create({
'name': 'concurency_test',
'code': 'CT',
'type': 'general',
})
account = env0['account.account'].create({
'code': 'CT',
'name': 'CT',
'user_type_id': env0.ref('account.data_account_type_fixed_assets').id,
})
moves = env0['account.move'].create([{
'journal_id': journal.id,
'date': fields.Date.from_string('2016-01-01'),
'line_ids': [(0, 0, {'name': 'name', 'account_id': account.id})]
}] * 3)
moves.name = '/'
moves[0].action_post()
self.assertEqual(moves.mapped('name'), ['CT/2016/01/0001', '/', '/'])
env0.cr.commit()
# start the transactions here on cr2 to simulate concurency with cr1
env2.cr.execute('SELECT 1')
move = env1['account.move'].browse(moves[1].id)
move.action_post()
env1.cr.commit()
move = env2['account.move'].browse(moves[2].id)
with self.assertRaises(psycopg2.OperationalError), env2.cr.savepoint(), mute_logger('flectra.sql_db'):
move.action_post()
self.assertEqual(moves.mapped('name'), ['CT/2016/01/0001', 'CT/2016/01/0002', '/'])
moves.button_draft()
moves.posted_before = False
moves.unlink()
journal.unlink()
account.unlink()
env0.cr.commit()
def test_resequence_clash(self):
"""Resequence doesn't clash when it uses a name set in the same batch
but that will be overriden later."""
@@ -427,3 +379,98 @@ class TestSequenceMixin(AccountTestInvoicingCommon):
self.assertEqual(move.name, 'MISC/2021/10/0001')
with Form(move) as move_form:
move_form.journal_id = journal
@tagged('post_install', '-at_install')
class TestSequenceMixinConcurrency(TransactionCase):
def setUp(self):
super().setUp()
with self.env.registry.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})
journal = env['account.journal'].create({
'name': 'concurency_test',
'code': 'CT',
'type': 'general',
})
account = env['account.account'].create({
'code': 'CT',
'name': 'CT',
'user_type_id': env.ref('account.data_account_type_fixed_assets').id,
})
moves = env['account.move'].create([{
'journal_id': journal.id,
'date': fields.Date.from_string('2016-01-01'),
'line_ids': [(0, 0, {'name': 'name', 'account_id': account.id})]
}] * 3)
moves.name = '/'
moves[0].action_post()
self.assertEqual(moves.mapped('name'), ['CT/2016/01/0001', '/', '/'])
env.cr.commit()
self.data = {
'move_ids': moves.ids,
'account_id': account.id,
'journal_id': journal.id,
'envs': [
api.Environment(self.env.registry.cursor(), SUPERUSER_ID, {}),
api.Environment(self.env.registry.cursor(), SUPERUSER_ID, {}),
api.Environment(self.env.registry.cursor(), SUPERUSER_ID, {}),
],
}
self.addCleanup(self.cleanUp)
def cleanUp(self):
with self.env.registry.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})
moves = env['account.move'].browse(self.data['move_ids'])
moves.button_draft()
moves.posted_before = False
moves.unlink()
journal = env['account.journal'].browse(self.data['journal_id'])
journal.unlink()
account = env['account.account'].browse(self.data['account_id'])
account.unlink()
env.cr.commit()
for env in self.data['envs']:
env.cr.close()
def test_sequence_concurency(self):
"""Computing the same name in concurent transactions is not allowed."""
env0, env1, env2 = self.data['envs']
# start the transactions here on cr1 to simulate concurency with cr2
env1.cr.execute('SELECT 1')
# post in cr2
move = env2['account.move'].browse(self.data['move_ids'][1])
move.action_post()
env2.cr.commit()
# try to post in cr1, should fail because this transaction started before the post in cr2
move = env1['account.move'].browse(self.data['move_ids'][2])
with self.assertRaises(psycopg2.OperationalError), mute_logger('flectra.sql_db'):
move.action_post()
# check the values
moves = env0['account.move'].browse(self.data['move_ids'])
self.assertEqual(moves.mapped('name'), ['CT/2016/01/0001', 'CT/2016/01/0002', '/'])
def test_sequence_concurency_no_useless_lock(self):
"""Do not lock needlessly when the sequence is not computed"""
env0, env1, env2 = self.data['envs']
# start the transactions here on cr1 to simulate concurency with cr2
env1.cr.execute('SELECT 1')
# get the last sequence in cr1 (for instance opening a form view)
move = env2['account.move'].browse(self.data['move_ids'][1])
move.highest_name
env2.cr.commit()
# post in cr1, should work even though cr2 read values
move = env1['account.move'].browse(self.data['move_ids'][2])
move.action_post()
env1.cr.commit()
# check the values
moves = env0['account.move'].browse(self.data['move_ids'])
self.assertEqual(moves.mapped('name'), ['CT/2016/01/0001', '/', 'CT/2016/01/0002'])
+2 -1
View File
@@ -199,7 +199,8 @@ class GoogleSync(models.AbstractModel):
return
with google_calendar_token(self.env.user.sudo()) as token:
if token:
google_id = google_service.insert(values, token=token, timeout=timeout)
send_updates = self._context.get('send_updates', True)
google_id = google_service.with_context(send_updates=send_updates).insert(values, token=token, timeout=timeout)
self.write({
'google_id': google_id,
'need_sync': False,
+3 -2
View File
@@ -96,13 +96,14 @@ class User(models.Model):
synced_events = self.env['calendar.event']._sync_google2flectra(events - recurrences, default_reminders=default_reminders)
# Flectra -> Google
send_updates = not full_sync
recurrences = self.env['calendar.recurrence']._get_records_to_sync(full_sync=full_sync)
recurrences -= synced_recurrences
recurrences._sync_flectra2google(calendar_service)
recurrences.with_context(send_updates=send_updates)._sync_flectra2google(calendar_service)
synced_events |= recurrences.calendar_event_ids - recurrences._get_outliers()
synced_events |= synced_recurrences.calendar_event_ids - synced_recurrences._get_outliers()
events = self.env['calendar.event']._get_records_to_sync(full_sync=full_sync)
(events - synced_events)._sync_flectra2google(calendar_service)
(events - synced_events).with_context(send_updates=send_updates)._sync_flectra2google(calendar_service)
return bool(events | synced_events) or bool(recurrences | synced_recurrences)
@@ -58,7 +58,8 @@ class GoogleCalendarService():
@requires_auth_token
def insert(self, values, token=None, timeout=TIMEOUT):
url = "/calendar/v3/calendars/primary/events?sendUpdates=all"
send_updates = self._context.get('send_updates', True)
url = "/calendar/v3/calendars/primary/events?sendUpdates=%s" % ("all" if send_updates else "none")
headers = {'Content-type': 'application/json', 'Authorization': 'Bearer %s' % token}
if not values.get('id'):
values['id'] = uuid4().hex
+1 -1
View File
@@ -30,7 +30,7 @@
<div class="o_kanban_card_header">
<div class="o_kanban_card_header_title">
<div class="o_primary col-12">
<span><t t-esc="record.name.value"/></span>
<span class="o_text_overflow"><t t-esc="record.name.value"/></span>
</div>
<div class="o_kanban_record_subtitle col-12 text-muted">
<field name="user_id" />
@@ -64,3 +64,22 @@ registerClassPatchModel('mail.message', 'im_livechat/static/src/models/message/m
});
});
flectra.define('im_livechat/static/src/models/composer/composer.js', function (require) {
'use strict';
const { registerInstancePatchModel } = require('mail/static/src/model/model_core.js');
registerInstancePatchModel('mail.composer', 'im_livechat/static/src/models/composer/composer.js', {
/**
* @override
*/
_computeHasDropZone() {
if (this.thread && this.thread.channel_type === 'livechat') {
return false;
}
return this._super();
},
});
});
@@ -61,6 +61,24 @@ QUnit.test('livechat: no add attachment button', async function (assert) {
);
});
QUnit.test('livechat: disable attachment upload via drag and drop', async function (assert) {
assert.expect(2);
await this.start();
const thread = this.env.models['mail.thread'].create({
channel_type: 'livechat',
id: 10,
model: 'mail.channel',
});
await this.createComposerComponent(thread.composer);
assert.containsOnce(document.body, '.o_Composer', "should have a composer");
assert.containsNone(
document.body,
'.o_Composer_dropZone',
"composer linked to livechat should not have a dropzone"
);
});
});
});
});
+2 -2
View File
@@ -239,9 +239,9 @@ class AccountMove(models.Model):
return self._get_formatted_sequence()
return super()._get_starting_sequence()
def _get_last_sequence(self, relaxed=False):
def _get_last_sequence(self, relaxed=False, lock=True):
""" If use share sequences we need to recompute the sequence to add the proper document code prefix """
res = super()._get_last_sequence(relaxed=relaxed)
res = super()._get_last_sequence(relaxed=relaxed, lock=lock)
if res and self.journal_id.l10n_ar_share_sequences and self.l10n_latam_document_type_id.doc_code_prefix not in res:
res = self._get_formatted_sequence(number=self._l10n_ar_get_document_number_parts(
res.split()[-1], self.l10n_latam_document_type_id.code)['invoice_number'])
@@ -22,7 +22,7 @@
<field name="amount_type">percent</field>
<field name="amount">-47.0</field>
<field name="price_include" eval="0"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -34,7 +34,7 @@
'tag_ids': [(4, ref('tax_withheld_tag'))],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -659,7 +659,7 @@
<field name="amount">0</field>
<field name="price_include" eval="0"/>
<field name="tax_group_id" ref="tax_group_gst_0"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -670,7 +670,7 @@
'repartition_type': 'tax',
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -693,7 +693,7 @@
<field name="amount">0</field>
<field name="price_include" eval="0"/>
<field name="tax_group_id" ref="tax_group_gst_0"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -704,7 +704,7 @@
'repartition_type': 'tax',
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -727,7 +727,7 @@
<field name="amount">0</field>
<field name="price_include" eval="0"/>
<field name="tax_group_id" ref="tax_group_gst_0"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -739,7 +739,7 @@
'tag_ids': [(4, ref('service_tag'))],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -784,7 +784,7 @@
-->
<field name="price_include" eval="1"/>
<field name="tax_group_id" ref="tax_group_gst_100000000"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -795,7 +795,7 @@
'repartition_type': 'tax',
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -818,7 +818,7 @@
<field name="amount">100000000000</field>
<field name="price_include" eval="1"/>
<field name="tax_group_id" ref="tax_group_gst_100000000"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -829,7 +829,7 @@
'repartition_type': 'tax',
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -852,7 +852,7 @@
<field name="amount">100000000000</field>
<field name="price_include" eval="1"/>
<field name="tax_group_id" ref="tax_group_gst_100000000"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -864,7 +864,7 @@
'tag_ids': [(4, ref('service_tag'))],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -900,7 +900,7 @@
<field name="amount">10.0</field>
<field name="price_include" eval="0"/>
<field name="tax_group_id" ref="tax_group_gst_10"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -912,7 +912,7 @@
'plus_report_line_ids': [ref('account_tax_report_gstrpt_g11'), ref('account_tax_report_gstrpt_g13')],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -936,7 +936,7 @@
<field name="amount">10.0</field>
<field name="price_include" eval="0"/>
<field name="tax_group_id" ref="tax_group_gst_10"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -948,7 +948,7 @@
'plus_report_line_ids': [ref('account_tax_report_gstrpt_g11'), ref('account_tax_report_gstrpt_g13')],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -972,7 +972,7 @@
<field name="amount">10.0</field>
<field name="price_include" eval="0"/>
<field name="tax_group_id" ref="tax_group_gst_10"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -985,7 +985,7 @@
'tag_ids': [(4, ref('service_tag'))],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1022,7 +1022,7 @@
<field name="amount">10.0</field>
<field name="price_include" eval="0"/>
<field name="tax_group_id" ref="tax_group_gst_10"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1034,7 +1034,7 @@
'plus_report_line_ids': [ref('account_tax_report_gstrpt_g11'), ref('account_tax_report_gstrpt_g15')],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1058,7 +1058,7 @@
<field name="amount">10.0</field>
<field name="price_include" eval="0"/>
<field name="tax_group_id" ref="tax_group_gst_10"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1070,7 +1070,7 @@
'plus_report_line_ids': [ref('account_tax_report_gstrpt_g11'), ref('account_tax_report_gstrpt_g15')],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1094,7 +1094,7 @@
<field name="amount">10.0</field>
<field name="price_include" eval="0"/>
<field name="tax_group_id" ref="tax_group_gst_10"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1107,7 +1107,7 @@
'tag_ids': [(4, ref('service_tag'))],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1144,7 +1144,7 @@
<field name="amount">100000000</field>
<field name="price_include" eval="1"/>
<field name="tax_group_id" ref="tax_group_gst_100000000"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1156,7 +1156,7 @@
'plus_report_line_ids': [ref('account_tax_report_gstrpt_gstonly'), ref('account_tax_report_gstrpt_comparison_gl')],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1180,7 +1180,7 @@
<field name="amount">100000000</field>
<field name="price_include" eval="1"/>
<field name="tax_group_id" ref="tax_group_gst_100000000"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1192,7 +1192,7 @@
'plus_report_line_ids': [ref('account_tax_report_gstrpt_gstonly'), ref('account_tax_report_gstrpt_comparison_gl')],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1216,7 +1216,7 @@
<field name="amount">100000000</field>
<field name="price_include" eval="1"/>
<field name="tax_group_id" ref="tax_group_gst_100000000"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1229,7 +1229,7 @@
'tag_ids': [(4, ref('service_tag'))],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1266,7 +1266,7 @@
<field name="amount">10.0</field>
<field name="price_include" eval="0"/>
<field name="tax_group_id" ref="tax_group_gst_10"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1279,7 +1279,7 @@
'plus_report_line_ids': [ref('account_tax_report_gstrpt_g18'), ref('account_tax_report_gstrpt_comparison_gl')],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1304,7 +1304,7 @@
<field name="amount">10.0</field>
<field name="price_include" eval="0"/>
<field name="tax_group_id" ref="tax_group_gst_10"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1317,7 +1317,7 @@
'plus_report_line_ids': [ref('account_tax_report_gstrpt_g18'), ref('account_tax_report_gstrpt_comparison_gl')],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1342,7 +1342,7 @@
<field name="amount">10.0</field>
<field name="price_include" eval="0"/>
<field name="tax_group_id" ref="tax_group_gst_10"/>
<field name="invoice_repartition_line_ids" eval="[
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
@@ -1356,7 +1356,7 @@
'tag_ids': [(4, ref('service_tag'))],
}),
]"/>
<field name="refund_repartition_line_ids" eval="[
<field name="refund_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
'factor_percent': 100,
'repartition_type': 'base',
+2
View File
@@ -26,7 +26,9 @@ Italian accounting chart and localization.
'data/account_tax_template.xml',
'data/account.fiscal.position.template.csv',
'data/account.chart.template.csv',
'data/account_fiscal_position_tax_template_data.xml',
'data/account_chart_template_data.xml',
'data/report_invoice.xml'
],
'demo': [
'demo/demo_company.xml',
@@ -1,5 +1,5 @@
"name","chart_template_id:id","id","sequence","auto_apply","vat_required","country_id:id","country_group_id:id"
"Italia","l10n_it_chart_template_generic","it",1,1,1,base.it,
"Regime Extra comunitario","l10n_it_chart_template_generic","extra",4,1,,,
"Regime Intra comunitario privato","l10n_it_chart_template_generic","intra_private",2,1,,,base.europe
"Regime Intra comunitario","l10n_it_chart_template_generic","intra",3,1,1,,base.europe
"name","chart_template_id:id","id","sequence","auto_apply","vat_required","country_id:id","country_group_id:id","note"
"Italia","l10n_it_chart_template_generic","it",1,1,1,base.it,,
"Regime Extra comunitario","l10n_it_chart_template_generic","extra",4,1,,,,
"Regime Intra comunitario privato","l10n_it_chart_template_generic","intra_private",2,1,,,base.europe,
"Regime Intra comunitario","l10n_it_chart_template_generic","intra",3,1,1,,base.europe,"Fattura emessa ai sensi dell’art. 17, comma 2 del DPR 26/10/1972 n. 633, l’applicazione dell’IVA è a carico del destinatario."
1 name chart_template_id:id id sequence auto_apply vat_required country_id:id country_group_id:id note
2 Italia l10n_it_chart_template_generic it 1 1 1 base.it
3 Regime Extra comunitario l10n_it_chart_template_generic extra 4 1
4 Regime Intra comunitario privato l10n_it_chart_template_generic intra_private 2 1 base.europe
5 Regime Intra comunitario l10n_it_chart_template_generic intra 3 1 1 base.europe Fattura emessa ai sensi dell’art. 17, comma 2 del DPR 26/10/1972 n. 633, l’applicazione dell’IVA è a carico del destinatario.
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<!-- account.fiscal.position.tax.template -->
<record id="afpttn_it_intra_1" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="22v"/>
<field name="tax_dest_id" ref="00eu"/>
</record>
<record id="afpttn_it_intra_2" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="10v"/>
<field name="tax_dest_id" ref="00eu"/>
</record>
<record id="afpttn_it_intra_3" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="5v"/>
<field name="tax_dest_id" ref="00eu"/>
</record>
<record id="afpttn_it_intra_4" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="4v"/>
<field name="tax_dest_id" ref="00eu"/>
</record>
<record id="afpttn_it_intra_5" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="00v"/>
<field name="tax_dest_id" ref="00eu"/>
</record>
<record id="afpttn_it_intra_6" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="22am"/>
<field name="tax_dest_id" ref="22rcm"/>
</record>
<record id="afpttn_it_intra_7" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="10am"/>
<field name="tax_dest_id" ref="10rcm"/>
</record>
<record id="afpttn_it_intra_8" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="5am"/>
<field name="tax_dest_id" ref="5rcm"/>
</record>
<record id="afpttn_it_intra_9" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="4am"/>
<field name="tax_dest_id" ref="4rcm"/>
</record>
<record id="afpttn_it_intra_10" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="00am"/>
<field name="tax_dest_id" ref="00rcm"/>
</record>
<record id="afpttn_it_intra_11" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="22as"/>
<field name="tax_dest_id" ref="22rcs"/>
</record>
<record id="afpttn_it_intra_12" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="10as"/>
<field name="tax_dest_id" ref="10rcs"/>
</record>
<record id="afpttn_it_intra_13" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="5as"/>
<field name="tax_dest_id" ref="5rcs"/>
</record>
<record id="afpttn_it_intra_14" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="4as"/>
<field name="tax_dest_id" ref="4rcs"/>
</record>
<record id="afpttn_it_intra_15" model="account.fiscal.position.tax.template">
<field name="position_id" ref="intra"/>
<field name="tax_src_id" ref="00as"/>
<field name="tax_dest_id" ref="00rcs"/>
</record>
</flectra>
@@ -186,4 +186,163 @@
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_reverse_charge_iva" model="account.tax.report.line">
<field name="name">Reverse Charge</field>
<field name="code">VJ</field>
<field name="sequence">5</field>
<field name="formula">None</field>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj1" model="account.tax.report.line">
<field name="name">VJ1 - Acquisti di beni dalla Città del Vaticano e da San Marino</field>
<field name="code">VJ1</field>
<field name="sequence">1</field>
<field name="tag_name">vj1</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj2" model="account.tax.report.line">
<field name="name">VJ2 - Estrazione di beni da depositi Iva</field>
<field name="code">VJ2</field>
<field name="sequence">2</field>
<field name="tag_name">vj2</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj3" model="account.tax.report.line">
<field name="name">VJ3 - Acquisti di beni giá presenti in Italia o servizi, da soggetti non residenti</field>
<field name="code">VJ3</field>
<field name="sequence">3</field>
<field name="tag_name">vj3</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj4" model="account.tax.report.line">
<field name="name">VJ4 - Compensi corrisposti ai rivenditori di biglietti di viaggio ed ai rivenditori di documenti di sosta </field>
<field name="code">VJ4</field>
<field name="sequence">4</field>
<field name="tag_name">vj4</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj5" model="account.tax.report.line">
<field name="name">VJ5 - Provvigioni corrisposte dalle agenzie di viaggio ai propri intermediari</field>
<field name="code">VJ5</field>
<field name="sequence">5</field>
<field name="tag_name">vj5</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj6" model="account.tax.report.line">
<field name="name">VJ6 - Acquisti di rottami e altri materiali di recupero</field>
<field name="code">VJ6</field>
<field name="sequence">6</field>
<field name="tag_name">vj6</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj7" model="account.tax.report.line">
<field name="name">VJ7 - Acquisti di oro industriale e argento puro effettuati in Italia</field>
<field name="code">VJ7</field>
<field name="sequence">7</field>
<field name="tag_name">vj7</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj8" model="account.tax.report.line">
<field name="name">VJ8 - Acquisti di oro da investimento effettuati in Italia</field>
<field name="code">VJ8</field>
<field name="sequence">8</field>
<field name="tag_name">vj8</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj9" model="account.tax.report.line">
<field name="name">VJ9 - Acquisti intracomunitari di beni</field>
<field name="code">VJ9</field>
<field name="sequence">9</field>
<field name="tag_name">vj9</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj10" model="account.tax.report.line">
<field name="name">VJ10 - Importazioni di rottami e altri materiali di recupero</field>
<field name="code">VJ10</field>
<field name="sequence">10</field>
<field name="tag_name">vj10</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj11" model="account.tax.report.line">
<field name="name">VJ11 - Importazioni di oro industriale e argento puro</field>
<field name="code">VJ11</field>
<field name="sequence">11</field>
<field name="tag_name">vj11</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj12" model="account.tax.report.line">
<field name="name">VJ12 - Subappalto di servizi in campo edile</field>
<field name="code">VJ12</field>
<field name="sequence">12</field>
<field name="tag_name">vj12</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj13" model="account.tax.report.line">
<field name="name">VJ13 - Acquisti di fabbricati o porzioni di fabbricati strumentali</field>
<field name="code">VJ13</field>
<field name="sequence">13</field>
<field name="tag_name">vj13</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj14" model="account.tax.report.line">
<field name="name">VJ14 - Acquisti di telefoni cellulari</field>
<field name="code">VJ14</field>
<field name="sequence">14</field>
<field name="tag_name">vj14</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj15" model="account.tax.report.line">
<field name="name">VJ15 - Acquisti di prodotti elettronici</field>
<field name="code">VJ15</field>
<field name="sequence">15</field>
<field name="tag_name">vj15</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj16" model="account.tax.report.line">
<field name="name">VJ16 - Prestazioni di servizi in campo edile</field>
<field name="code">VJ16</field>
<field name="sequence">16</field>
<field name="tag_name">vj16</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj17" model="account.tax.report.line">
<field name="name">VJ17 - Acquiti di beni e servizi del settore energetico</field>
<field name="code">VJ17</field>
<field name="sequence">17</field>
<field name="tag_name">vj17</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj18" model="account.tax.report.line">
<field name="name">VJ18 - acquisti effettuati dalle pubbliche amministrazioni titolari di partita IVA</field>
<field name="code">VJ18</field>
<field name="sequence">18</field>
<field name="tag_name">vj18</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
<record id="tax_report_line_vj19" model="account.tax.report.line">
<field name="name">VJ19 - Totale quadro VJ</field>
<field name="code">VJ19</field>
<field name="sequence">19</field>
<field name="formula">VJ1 + VJ2 + VJ3 + VJ4 + VJ5 + VJ6 + VJ7 + VJ8 + VJ9 + VJ10 + VJ11 + VJ12 + VJ13 + VJ14 + VJ15 + VJ16 + VJ17 + VJ18</field>
<field name="parent_id" ref="tax_report_line_reverse_charge_iva"/>
<field name="report_id" ref="tax_report_vat"/>
</record>
</flectra>
File diff suppressed because it is too large. Load diff
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<template id="report_invoice_document" inherit_id="account.report_invoice_document">
<xpath expr="//p[@name='note']" position="replace">
<p name="note">
<t t-if="o.move_type == 'out_invoice' and o.fiscal_position_id.note">
<span t-field="o.fiscal_position_id.note"/>
</t>
<t t-else="">
<span/>
</t>
</p>
</xpath>
</template>
</flectra>
+20
View File
@@ -3,3 +3,23 @@
from . import models
from . import tools
from flectra import api, SUPERUSER_ID
def _l10n_it_edi_update_export_tax(env):
chart_template = env.ref('l10n_it.l10n_it_chart_template_generic', raise_if_not_found=False)
if chart_template:
for company in env['res.company'].search([('chart_template_id', '=', chart_template.id)]):
tax = env.ref(f'l10n_it.{company.id}_00eu', raise_if_not_found=False)
if tax:
tax.write({
'l10n_it_has_exoneration': True,
'l10n_it_kind_exoneration': 'N3.2',
'l10n_it_law_reference': 'Art. 41, DL n. 331/93',
})
def _l10n_it_edi_post_init(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
_l10n_it_edi_update_export_tax(env)
+1
View File
@@ -25,5 +25,6 @@ E-invoice implementation
'demo': [
'data/account_invoice_demo.xml',
],
'post_init_hook': '_l10n_it_edi_post_init',
'license': 'LGPL-3',
}
+190 -191
View File
@@ -1,40 +1,195 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<data>
<template id="account_invoice_line_it_FatturaPA">
<t t-set="taxes" t-value="line.tax_ids.compute_all(line.price_unit)"/>
<DettaglioLinee>
<NumeroLinea t-esc="line_counter"/>
<CodiceArticolo t-if="line.product_id.barcode">
<!--2.2.1.3-->
<CodiceTipo>EAN</CodiceTipo>
<CodiceValore t-esc="format_alphanumeric(line.product_id.barcode)"/>
</CodiceArticolo>
<CodiceArticolo t-if="line.product_id.default_code">
<CodiceTipo>INTERNAL</CodiceTipo>
<CodiceValore t-esc="format_alphanumeric(line.product_id.default_code)"/>
</CodiceArticolo>
<Descrizione>
<t t-esc="format_alphanumeric(line.name[:1000])"/>
<t t-if="not line.name" t-esc="'NO NAME'"/>
</Descrizione>
<Quantita t-esc="format_numbers(line.quantity)"/>
<UnitaMisura t-if="line.product_uom_id.category_id != env.ref('uom.product_uom_categ_unit')" t-esc="format_alphanumeric(line.product_uom_id.name)"/>
<PrezzoUnitario t-esc="'%.06f' % (line.price_subtotal / (( 1 - (line.discount or 0.0) / 100.0) * line.quantity) if line.quantity and line.discount != 100.0 else line.price_unit)"/>
<ScontoMaggiorazione t-if="line.discount != 0">
<!-- [2.2.1.10] -->
<Tipo t-esc="discount_type(line.discount)"/>
<Percentuale t-esc="format_numbers(abs(line.discount))"/>
</ScontoMaggiorazione>
<PrezzoTotale t-esc="format_monetary(line.price_subtotal, currency)"/>
<!-- without tax, must include any discounts and any extra charge-->
<AliquotaIVA t-if="line.tax_ids.amount_type == 'percent'" t-esc="format_numbers(line.tax_ids.amount)"/>
<AliquotaIVA t-if="line.tax_ids.amount_type != 'percent'" t-esc="'0.00'"/>
<Natura t-if="line.tax_ids.l10n_it_has_exoneration" t-esc="line.tax_ids.l10n_it_kind_exoneration"/>
</DettaglioLinee>
</template>
<template id="account_invoice_it_FatturaPA_sede">
<template id="account_invoice_line_it_FatturaPA">
<DettaglioLinee>
<NumeroLinea t-esc="line_counter"/>
<CodiceArticolo t-if="line.product_id.barcode">
<CodiceTipo>EAN</CodiceTipo>
<CodiceValore t-esc="format_alphanumeric(line.product_id.barcode)"/>
</CodiceArticolo>
<CodiceArticolo t-elif="line.product_id.default_code">
<CodiceTipo>INTERNAL</CodiceTipo>
<CodiceValore t-esc="format_alphanumeric(line.product_id.default_code)"/>
</CodiceArticolo>
<Descrizione>
<t t-esc="format_alphanumeric(line.name[:1000])"/>
<t t-if="not line.name" t-esc="'NO NAME'"/>
</Descrizione>
<Quantita t-esc="format_numbers(line.quantity)"/>
<UnitaMisura t-if="line.product_uom_id.category_id != env.ref('uom.product_uom_categ_unit')" t-esc="format_alphanumeric(line.product_uom_id.name)"/>
<PrezzoUnitario t-esc="'%.06f' % (line.price_subtotal / (( 1 - (line.discount or 0.0) / 100.0) * line.quantity) if line.quantity and line.discount != 100.0 else line.price_unit)"/>
<ScontoMaggiorazione t-if="line.discount != 0">
<Tipo t-esc="discount_type(line.discount)"/>
<Percentuale t-esc="format_numbers(abs(line.discount))"/>
</ScontoMaggiorazione>
<PrezzoTotale t-esc="format_monetary(line.price_subtotal, currency)"/>
<AliquotaIVA t-if="line.tax_ids.amount_type == 'percent'" t-esc="format_numbers(line.tax_ids.amount)"/>
<AliquotaIVA t-elif="line.tax_ids.amount_type != 'percent'" t-esc="'0.00'"/>
<Natura t-if="line.tax_ids.l10n_it_has_exoneration" t-esc="line.tax_ids.l10n_it_kind_exoneration"/>
</DettaglioLinee>
</template>
<template id="account_invoice_it_FatturaPA_export">
<p:FatturaElettronica t-att-versione="formato_trasmissione" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2 http://www.fatturapa.gov.it/export/fatturazione/sdi/fatturapa/v1.2/Schema_del_file_xml_FatturaPA_versione_1.2.xsd">
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese t-esc="get_vat_country(sender.vat)"/>
<IdCodice t-esc="normalize_codice_fiscale(sender.l10n_it_codice_fiscale) or get_vat_number(sender.vat)"/>
</IdTrasmittente>
<ProgressivoInvio t-esc="format_alphanumeric(record.name.replace('/','')[-10:])"/>
<FormatoTrasmissione t-esc="formato_trasmissione"/>
<CodiceDestinatario t-esc="codice_destinatario"/>
<ContattiTrasmittente>
<Telefono t-if="sender_partner.phone" t-esc="format_alphanumeric(format_phone(sender_partner.phone))"/>
<Telefono t-elif="sender_partner.mobile" t-esc="format_alphanumeric(format_phone(sender_partner.mobile))"/>
<Email t-if="sender_partner.email" t-esc="sender_partner.email"/>
</ContattiTrasmittente>
<PECDestinatario t-if="not is_self_invoice and partner.l10n_it_pec_email" t-esc="partner.l10n_it_pec_email"/>
</DatiTrasmissione>
<CedentePrestatore>
<DatiAnagrafici>
<IdFiscaleIVA>
<IdPaese t-esc="get_vat_country(seller.vat)"/>
<IdCodice t-esc="get_vat_number(seller.vat)"/>
</IdFiscaleIVA>
<CodiceFiscale t-if="seller.l10n_it_codice_fiscale" t-esc="normalize_codice_fiscale(seller.l10n_it_codice_fiscale)"/>
<Anagrafica>
<Denominazione t-esc="format_alphanumeric(seller_partner.display_name[:80])"/>
</Anagrafica>
<RegimeFiscale t-esc="regime_fiscale"/>
</DatiAnagrafici>
<t t-call="l10n_it_edi.account_invoice_it_FatturaPA_sede">
<t t-set="partner" t-value="seller_partner"/>
</t>
<IscrizioneREA t-if="not is_self_invoice and company.l10n_it_has_eco_index">
<Ufficio t-esc="company.l10n_it_eco_index_office.code"/>
<NumeroREA t-esc="format_alphanumeric(company.l10n_it_eco_index_number)"/>
<CapitaleSociale t-if="company.l10n_it_eco_index_share_capital != 0" t-esc="format_numbers_two(company.l10n_it_eco_index_share_capital)"/>
<SocioUnico t-if="company.l10n_it_eco_index_sole_shareholder != 'NO'" t-esc="company.l10n_it_eco_index_sole_shareholder"/>
<StatoLiquidazione t-esc="company.l10n_it_eco_index_liquidation_state"/>
</IscrizioneREA>
</CedentePrestatore>
<CessionarioCommittente>
<DatiAnagrafici>
<IdFiscaleIVA t-if="buyer.vat and in_eu(buyer)">
<IdPaese t-esc="get_vat_country(buyer.vat)"/>
<IdCodice t-esc="get_vat_number(buyer.vat)"/>
</IdFiscaleIVA>
<IdFiscaleIVA t-if="buyer.vat and not in_eu(buyer)">
<IdPaese t-esc="buyer.country_id.code"/>
<IdCodice t-esc="'OO99999999999'"/>
</IdFiscaleIVA>
<IdFiscaleIVA t-if="not buyer.vat and buyer.country_id.code != 'IT'">
<IdPaese t-esc="buyer.country_id.code"/>
<IdCodice t-esc="'0000000'"/>
</IdFiscaleIVA>
<CodiceFiscale t-if="not buyer.vat and buyer.l10n_it_codice_fiscale" t-esc="normalize_codice_fiscale(buyer.l10n_it_codice_fiscale)"/>
<CodiceFiscale t-elif="not buyer.vat and not buyer.l10n_it_codice_fiscale" t-esc="99999999999"/>
<Anagrafica>
<t t-if="buyer_is_company">
<Denominazione t-esc="format_alphanumeric(buyer.display_name[:80])"/>
</t>
<t t-else="">
<Nome t-esc="format_alphanumeric(' '.join(buyer.name.split()[:1])[:60])"/>
<Cognome t-esc="format_alphanumeric(' '.join(buyer.name.split()[1:])[:60])"/>
</t>
</Anagrafica>
</DatiAnagrafici>
<t t-call="l10n_it_edi.account_invoice_it_FatturaPA_sede">
<t t-set="partner" t-value="buyer_partner"/>
</t>
</CessionarioCommittente>
<RappresentanteFiscale t-if="not is_self_invoice and representative">
<DatiAnagrafici>
<IdFiscaleIVA>
<IdPaese t-esc="get_vat_country(representative.vat)"/>
<IdCodice t-esc="get_vat_number(representative.vat)"/>
</IdFiscaleIVA>
<CodiceFiscale t-if="representative.l10n_it_codice_fiscale" t-esc="normalize_codice_fiscale(representative.l10n_it_codice_fiscale)"/>
<Anagrafica>
<t t-if="representative.is_company">
<Denominazione t-esc="format_alphanumeric(representative.display_name[:80])"/>
</t>
<t t-else="">
<Nome t-esc="format_alphanumeric(' '.join(representative.name.split()[:1])[:60])"/>
<Cognome t-esc="format_alphanumeric(' '.join(representative.name.split()[1:])[:60])"/>
</t>
</Anagrafica>
</DatiAnagrafici>
</RappresentanteFiscale>
</FatturaElettronicaHeader>
<FatturaElettronicaBody>
<DatiGenerali>
<DatiGeneraliDocumento>
<TipoDocumento t-esc="document_type"/>
<Divisa t-esc="currency.name"/>
<Data t-esc="format_date(record.invoice_date)"/>
<Numero t-esc="format_alphanumeric(record.name[-20:])"/>
<DatiBollo t-if="record.l10n_it_stamp_duty">
<BolloVirtuale>SI</BolloVirtuale>
<ImportoBollo t-esc="format_numbers(record.l10n_it_stamp_duty)"/>
</DatiBollo>
<ImportoTotaleDocumento t-esc="format_monetary(record.amount_total, currency)"/>
</DatiGeneraliDocumento>
<DatiOrdineAcquisto t-if="record.ref">
<IdDocumento t-esc="format_alphanumeric(record.ref[:20])"/>
</DatiOrdineAcquisto>
<DatiDDT t-if="record.l10n_it_ddt_id">
<NumeroDDT t-esc="format_alphanumeric(record.l10n_it_ddt_id.name[-20:])"/>
<DataDDT t-esc="format_date(record.l10n_it_ddt_id.date)"/>
</DatiDDT>
</DatiGenerali>
<DatiBeniServizi>
<t t-set="line_counter" t-value="0"/>
<t t-foreach="record.invoice_line_ids.filtered(lambda l: not l.display_type)" t-as="line">
<t t-set="line_counter" t-value="line_counter + 1"/>
<t t-set="taxes" t-value="line.tax_ids.compute_all(line.price_unit)"/>
<t t-call="l10n_it_edi.account_invoice_line_it_FatturaPA"/>
</t>
<t t-foreach="tax_details['tax_details']" t-as="tax_name">
<t t-set="tax_dict" t-value="tax_details['tax_details'][tax_name]"/>
<t t-set="tax" t-value="tax_dict['tax']"/>
<t t-set="has_exoneration" t-value="tax.l10n_it_has_exoneration"/>
<t t-set="kind_exoneration" t-value="tax.l10n_it_kind_exoneration"/>
<DatiRiepilogo>
<AliquotaIVA t-esc="format_numbers(tax.amount)"/>
<Arrotondamento t-if="tax_dict.get('rounding')" t-esc="format_numbers(tax_dict['rounding'])"/>
<ImponibileImporto t-esc="format_monetary(abs(tax_dict['base_amount_currency']), currency)"/>
<Imposta t-esc="format_monetary(abs(tax_dict['tax_amount_currency']), currency)"/>
<EsigibilitaIVA t-if="not has_exoneration or kind_exoneration == 'N6'" t-esc="tax.l10n_it_vat_due_date"/>
<Natura t-if="has_exoneration" t-esc="kind_exoneration"/>
<RiferimentoNormativo t-if="has_exoneration" t-esc="format_alphanumeric(tax.l10n_it_law_reference[:100])"/>
</DatiRiepilogo>
</t>
</DatiBeniServizi>
<DatiPagamento t-if="partner_bank and record.move_type != 'out_refund'">
<t t-set="payments" t-value="record.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))"/>
<CondizioniPagamento><t t-if="len(payments) == 1">TP02</t><t t-else="">TP01</t></CondizioniPagamento>
<t t-foreach="payments" t-as="payment">
<DettaglioPagamento>
<ModalitaPagamento>MP05</ModalitaPagamento>
<DataScadenzaPagamento t-esc="format_date(payment.date_maturity)"/>
<ImportoPagamento t-esc="format_monetary(abs(payment.price_total), currency)"/>
<IstitutoFinanziario t-if="partner_bank.bank_id" t-esc="format_alphanumeric(partner_bank.bank_id.name[:80])"/>
<IBAN t-if="partner_bank.acc_type == 'iban'" t-esc="partner_bank.sanitized_acc_number"/>
<BIC t-elif="partner_bank.acc_type == 'bank' and partner_bank.bank_id.bic" t-esc="partner_bank.bank_id.bic"/>
<CodicePagamento t-esc="format_alphanumeric(record.payment_reference[:60])"/>
</DettaglioPagamento>
</t>
</DatiPagamento>
<Allegati t-if="pdf">
<NomeAttachment t-esc="format_alphanumeric(pdf_name[:60])"/>
<FormatoAttachment>PDF</FormatoAttachment>
<Attachment t-esc="pdf"/>
</Allegati>
</FatturaElettronicaBody>
</p:FatturaElettronica>
</template>
<template id="account_invoice_it_FatturaPA_sede">
<Sede>
<Indirizzo><t t-if="partner.street" t-esc="format_alphanumeric(partner.street)"/> <t t-if="partner.street2" t-esc="format_alphanumeric(partner.street2)"/></Indirizzo>
<CAP><t t-if="partner.country_id.code != 'IT'" t-esc="'00000'"/><t t-else="" t-esc="partner.zip"/></CAP>
@@ -42,164 +197,8 @@
<Provincia t-if="partner.country_id.code == 'IT'" t-esc="partner.state_id.code"/>
<Nazione t-esc="partner.country_id.code"/>
</Sede>
</template>
</template>
<template id="account_invoice_it_FatturaPA_export">
<t t-set="currency" t-value="record.currency_id or record.company_currency_id"/>
<t t-set="bank" t-value="record.partner_bank_id"/>
<p:FatturaElettronica t-att-versione="formato_trasmissione" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2 http://www.fatturapa.gov.it/export/fatturazione/sdi/fatturapa/v1.2/Schema_del_file_xml_FatturaPA_versione_1.2.xsd">
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese t-esc="get_vat_country(record.company_id.vat)"/>
<IdCodice t-esc="normalize_codice_fiscale(record.company_id.l10n_it_codice_fiscale) or get_vat_number(record.company_id.vat)"/>
</IdTrasmittente>
<ProgressivoInvio t-esc="format_alphanumeric(record.name.replace('/','')[-10:])"/>
<FormatoTrasmissione t-esc="formato_trasmissione"/>
<CodiceDestinatario t-if="record.commercial_partner_id.l10n_it_pa_index" t-esc="record.commercial_partner_id.l10n_it_pa_index.upper()"/>
<CodiceDestinatario t-if="not record.commercial_partner_id.l10n_it_pa_index and record.commercial_partner_id.country_id.code == 'IT'" t-esc="'0000000'"/>
<CodiceDestinatario t-if="not record.commercial_partner_id.l10n_it_pa_index and record.commercial_partner_id.country_id.code != 'IT'" t-esc="'XXXXXXX'"/>
<ContattiTrasmittente>
<Telefono t-if="format_phone(record.company_id.partner_id.phone)" t-esc="format_alphanumeric(format_phone(record.company_id.partner_id.phone))"/>
<Telefono t-if="not format_phone(record.company_id.partner_id.phone) and format_phone(record.company_id.partner_id.mobile)" t-esc="format_alphanumeric(format_phone(record.company_id.partner_id.mobile))"/>
<Email t-if="record.company_id.email" t-esc="record.company_id.email"/>
</ContattiTrasmittente>
<PECDestinatario t-if="record.commercial_partner_id.l10n_it_pec_email" t-esc="record.commercial_partner_id.l10n_it_pec_email"/>
</DatiTrasmissione>
<CedentePrestatore>
<DatiAnagrafici>
<IdFiscaleIVA>
<IdPaese t-esc="get_vat_country(record.company_id.vat)"/>
<IdCodice t-esc="get_vat_number(record.company_id.vat)"/>
</IdFiscaleIVA>
<CodiceFiscale t-if="record.company_id.l10n_it_codice_fiscale" t-esc="normalize_codice_fiscale(record.company_id.l10n_it_codice_fiscale)"/>
<Anagrafica>
<Denominazione t-esc="format_alphanumeric(record.company_id.partner_id.display_name[:80])"/>
</Anagrafica>
<RegimeFiscale t-esc="record.company_id.l10n_it_tax_system"/>
</DatiAnagrafici>
<t t-call="l10n_it_edi.account_invoice_it_FatturaPA_sede">
<t t-set="partner" t-value="record.company_id.partner_id"/>
</t>
<IscrizioneREA t-if="record.company_id.l10n_it_has_eco_index">
<!--1.2.4-->
<Ufficio t-esc="record.company_id.l10n_it_eco_index_office.code"/>
<NumeroREA t-esc="format_alphanumeric(record.company_id.l10n_it_eco_index_number)"/>
<CapitaleSociale t-if="record.company_id.l10n_it_eco_index_share_capital != 0" t-esc="format_numbers_two(record.company_id.l10n_it_eco_index_share_capital)"/>
<SocioUnico t-if="record.company_id.l10n_it_eco_index_sole_shareholder != 'NO'" t-esc="record.company_id.l10n_it_eco_index_sole_shareholder"/>
<StatoLiquidazione t-esc="record.company_id.l10n_it_eco_index_liquidation_state"/>
</IscrizioneREA>
</CedentePrestatore>
<RappresentanteFiscale t-if="record.company_id.l10n_it_has_tax_representative">
<!--1.3-->
<DatiAnagrafici>
<IdFiscaleIVA>
<IdPaese t-esc="get_vat_country(record.company_id.l10n_it_tax_representative_partner_id.vat)"/>
<IdCodice t-esc="get_vat_number(record.company_id.l10n_it_tax_representative_partner_id.vat)"/>
</IdFiscaleIVA>
<CodiceFiscale t-if="record.company_id.l10n_it_tax_representative_partner_id.l10n_it_codice_fiscale" t-esc="normalize_codice_fiscale(record.company_id.l10n_it_tax_representative_partner_id.l10n_it_codice_fiscale)"/>
<Anagrafica>
<Denominazione t-if="record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="format_alphanumeric(record.company_id.l10n_it_tax_representative_partner_id.display_name[:80])"/>
<Nome t-if="not record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="format_alphanumeric(' '.join(record.company_id.l10n_it_tax_representative_partner_id.name.split()[:1])[:60])"/>
<Cognome t-if="not record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="format_alphanumeric(' '.join(record.company_id.l10n_it_tax_representative_partner_id.name.split()[1:])[:60])"/>
</Anagrafica>
</DatiAnagrafici>
</RappresentanteFiscale>
<CessionarioCommittente>
<DatiAnagrafici>
<IdFiscaleIVA t-if="record.commercial_partner_id.vat and in_eu(record.commercial_partner_id)">
<IdPaese t-esc="get_vat_country(record.commercial_partner_id.vat)"/>
<IdCodice t-esc="get_vat_number(record.commercial_partner_id.vat)"/>
</IdFiscaleIVA>
<IdFiscaleIVA t-if="record.commercial_partner_id.vat and not in_eu(record.commercial_partner_id)">
<IdPaese t-esc="record.commercial_partner_id.country_id.code"/>
<IdCodice t-esc="'OO99999999999'"/>
</IdFiscaleIVA>
<IdFiscaleIVA t-if="not record.commercial_partner_id.vat and record.commercial_partner_id.country_id.code != 'IT'">
<IdPaese t-esc="record.commercial_partner_id.country_id.code"/>
<IdCodice t-esc="'0000000'"/>
</IdFiscaleIVA>
<CodiceFiscale t-if="not record.commercial_partner_id.vat" t-esc="normalize_codice_fiscale(record.commercial_partner_id.l10n_it_codice_fiscale)"/>
<CodiceFiscale t-if="not record.commercial_partner_id.vat and not record.commercial_partner_id.l10n_it_codice_fiscale" t-esc="99999999999"/>
<Anagrafica>
<Denominazione t-if="record.commercial_partner_id.is_company" t-esc="format_alphanumeric(record.commercial_partner_id.display_name[:80])"/>
<Nome t-if="not record.commercial_partner_id.is_company" t-esc="format_alphanumeric(' '.join(record.commercial_partner_id.name.split()[:1])[:60])"/>
<Cognome t-if="not record.commercial_partner_id.is_company" t-esc="format_alphanumeric(' '.join(record.commercial_partner_id.name.split()[1:])[:60])"/>
</Anagrafica>
</DatiAnagrafici>
<t t-call="l10n_it_edi.account_invoice_it_FatturaPA_sede">
<t t-set="partner" t-value="record.commercial_partner_id"/>
</t>
</CessionarioCommittente>
</FatturaElettronicaHeader>
<FatturaElettronicaBody>
<DatiGenerali>
<DatiGeneraliDocumento>
<!--2.1.1-->
<TipoDocumento t-esc="document_type"/>
<Divisa t-esc="currency.name"/>
<Data t-esc="format_date(record.invoice_date)"/>
<Numero t-esc="format_alphanumeric(record.name[-20:])"/>
<DatiBollo t-if="record.l10n_it_stamp_duty">
<!--2.1.1.6-->
<BolloVirtuale>SI</BolloVirtuale>
<ImportoBollo t-esc="format_numbers(record.l10n_it_stamp_duty)"/>
</DatiBollo>
<ImportoTotaleDocumento t-esc="format_monetary(record.amount_total, currency)"/>
</DatiGeneraliDocumento>
<DatiOrdineAcquisto t-if="record.ref">
<IdDocumento t-esc="format_alphanumeric(record.ref[:20])"/>
</DatiOrdineAcquisto>
<DatiDDT t-if="record.l10n_it_ddt_id">
<!--2.1.8-->
<NumeroDDT t-esc="format_alphanumeric(record.l10n_it_ddt_id.name[-20:])"/>
<DataDDT t-esc="format_date(record.l10n_it_ddt_id.date)"/>
</DatiDDT>
</DatiGenerali>
<DatiBeniServizi>
<!-- Invoice lines. -->
<t t-set="line_counter" t-value="0"/>
<t t-foreach="record.invoice_line_ids.filtered(lambda l: not l.display_type)" t-as="line">
<t t-set="line_counter" t-value="line_counter + 1"/>
<t t-call="l10n_it_edi.account_invoice_line_it_FatturaPA"/>
</t>
<t t-foreach="tax_details['tax_details']" t-as="tax_name">
<t t-set="tax_dict" t-value="tax_details['tax_details'][tax_name]"/>
<DatiRiepilogo>
<!--2.2.2-->
<AliquotaIVA t-esc="format_numbers(tax_dict['tax'].amount)"/>
<Natura t-if="tax_dict['tax'].l10n_it_has_exoneration" t-esc="tax_dict['tax'].l10n_it_kind_exoneration"/>
<Arrotondamento t-if="tax_dict.get('rounding')" t-esc="format_numbers(tax_dict['rounding'])"/>
<ImponibileImporto t-esc="format_monetary(abs(tax_dict['base_amount_currency']), currency)"/>
<Imposta t-esc="format_monetary(abs(tax_dict['tax_amount_currency']), currency)"/>
<EsigibilitaIVA t-if="not tax_dict['tax'].l10n_it_has_exoneration or tax_dict['tax'].l10n_it_kind_exoneration=='N6'" t-esc="tax_dict['tax'].l10n_it_vat_due_date"/>
<RiferimentoNormativo t-if="tax_dict['tax'].l10n_it_has_exoneration" t-esc="format_alphanumeric(tax_dict['tax'].l10n_it_law_reference[:100])"/>
</DatiRiepilogo>
</t>
</DatiBeniServizi>
<t t-set="company_bank_account" t-value="record.partner_bank_id"/>
<DatiPagamento t-if="company_bank_account and record.move_type != 'out_refund'">
<t t-set="payments" t-value="record.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))"/>
<CondizioniPagamento><t t-if="len(payments) == 1">TP02</t><t t-else="">TP01</t></CondizioniPagamento>
<t t-foreach="payments" t-as="payment">
<DettaglioPagamento>
<ModalitaPagamento>MP05</ModalitaPagamento>
<DataScadenzaPagamento t-esc="format_date(payment.date_maturity)"/>
<ImportoPagamento t-esc="format_monetary(abs(payment.price_total), currency)"/>
<IstitutoFinanziario t-if="company_bank_account.bank_id" t-esc="format_alphanumeric(company_bank_account.bank_id.name[:80])"/>
<IBAN t-if="company_bank_account.acc_type == 'iban'" t-esc="company_bank_account.sanitized_acc_number"/>
<BIC t-if="company_bank_account.acc_type == 'bank' and company_bank_account.bank_id.bic" t-esc="company_bank_account.bank_id.bic"/>
<CodicePagamento t-esc="format_alphanumeric(record.payment_reference[:60])"/>
</DettaglioPagamento>
</t>
</DatiPagamento>
<Allegati t-if="pdf">
<NomeAttachment t-esc="format_alphanumeric(pdf_name[:60])"/>
<FormatoAttachment>PDF</FormatoAttachment>
<Attachment t-esc="pdf"/>
</Allegati>
</FatturaElettronicaBody>
</p:FatturaElettronica>
</template>
</data>
</flectra>
+115 -34
View File
@@ -65,10 +65,23 @@ class AccountEdiFormat(models.Model):
return errors
def _l10n_it_edi_is_self_invoice(self, invoice):
"""
Italian EDI requires Vendor bills coming from EU countries to be sent as self-invoices.
We recognize these cases based on the taxes that target the VJ tax grids, which imply
the use of VAT External Reverse Charge.
"""
report_lines_xmlids = invoice.line_ids.tax_tag_ids.tax_report_line_ids.mapped(lambda x: x.get_external_id().get(x.id, ''))
return (invoice.is_purchase_document()
and any([x.startswith("l10n_it.tax_report_line_vj") for x in report_lines_xmlids]))
def _l10n_it_edi_check_ordinary_invoice_configuration(self, invoice):
errors = []
seller = invoice.company_id
buyer = invoice.commercial_partner_id
is_self_invoice = self._l10n_it_edi_is_self_invoice(invoice)
if is_self_invoice:
seller, buyer = buyer, seller
# <1.1.1.1>
if not seller.country_id:
@@ -81,11 +94,11 @@ class AccountEdiFormat(models.Model):
errors.append(_("The maximum length for VAT number is 30. %s have a VAT number too long: %s.", seller.display_name, seller.vat))
# <1.2.1.2>
if not seller.l10n_it_codice_fiscale:
if not is_self_invoice and not seller.l10n_it_codice_fiscale:
errors.append(_("%s must have a codice fiscale number", seller.display_name))
# <1.2.1.8>
if not seller.l10n_it_tax_system:
if not is_self_invoice and not seller.l10n_it_tax_system:
errors.append(_("The seller's company must have a tax system."))
# <1.2.2>
@@ -100,17 +113,23 @@ class AccountEdiFormat(models.Model):
if not seller.country_id:
errors.append(_("%s must have a country.", seller.display_name))
if seller.l10n_it_has_tax_representative and not seller.l10n_it_tax_representative_partner_id.vat:
if not is_self_invoice and seller.l10n_it_has_tax_representative and not seller.l10n_it_tax_representative_partner_id.vat:
errors.append(_("Tax representative partner %s of %s must have a tax number.", seller.l10n_it_tax_representative_partner_id.display_name, seller.display_name))
# <1.4.1>
if not buyer.vat and not buyer.l10n_it_codice_fiscale and buyer.country_id.code == 'IT':
errors.append(_("The buyer, %s, or his company must have a VAT number and/or a tax code (Codice Fiscale).", buyer.display_name))
if is_self_invoice and self._l10n_it_edi_services_or_goods(invoice) == 'both':
errors.append(_("Cannot apply Reverse Charge to a bill which contains both services and goods."))
if is_self_invoice and not buyer.partner_id.l10n_it_pa_index:
errors.append(_("Vendor bills sent as self-invoices to the SdI require a valid PA Index (Codice Destinatario) on the company's contact."))
# <2.2.1>
for invoice_line in invoice.invoice_line_ids:
if not invoice_line.display_type and len(invoice_line.tax_ids) != 1:
raise UserError(_("You must select one and only one tax by line."))
errors.append(_("In line %s, you must select one and only one tax by line.", invoice_line.name))
for tax_line in invoice.line_ids.filtered(lambda line: line.tax_line_id):
if not tax_line.tax_line_id.l10n_it_kind_exoneration and tax_line.tax_line_id.amount == 0:
@@ -118,18 +137,51 @@ class AccountEdiFormat(models.Model):
return errors
def _l10n_it_edi_is_simplified(self, invoice):
"""
Simplified Invoices are a way for the invoice issuer to create an invoice with limited data.
Example: a consultant goes to the restaurant and wants the invoice instead of the receipt,
to be able to deduct the expense from his Taxes. The Italian State allows the restaurant
to issue a Simplified Invoice with the VAT number only, to speed up times, instead of
requiring the address and other informations about the buyer.
Only invoices under the threshold of 400 Euroes are allowed, to avoid this tool
be abused for bigger transactions, that would enable less transparency to tax institutions.
"""
buyer = invoice.commercial_partner_id
return all([
self.env.ref('l10n_it_edi.account_invoice_it_simplified_FatturaPA_export', raise_if_not_found=False),
not self._l10n_it_edi_is_self_invoice(invoice),
self._l10n_it_edi_check_buyer_invoice_configuration(invoice),
not buyer.country_id or buyer.country_id.code == 'IT',
buyer.l10n_it_codice_fiscale or (buyer.vat and (buyer.vat[:2].upper() == 'IT' or buyer.vat[:2].isdecimal())),
invoice.amount_total <= 400,
])
def _l10n_it_edi_check_simplified_invoice_configuration(self, invoice):
errors = self._l10n_it_edi_check_buyer_invoice_configuration(invoice)
template = self.env.ref('l10n_it_edi.account_invoice_it_simplified_FatturaPA_export', raise_if_not_found=False)
return [] if self._l10n_it_edi_is_simplified(invoice) else self._l10n_it_edi_check_buyer_invoice_configuration(invoice)
if errors and template:
buyer = invoice.commercial_partner_id
if ((not buyer.country_id or buyer.country_id.code == 'IT')
and (buyer.l10n_it_codice_fiscale or (buyer.vat and (buyer.vat[:2].upper() == 'IT' or buyer.vat[:2].isdecimal())))
and invoice.amount_total <= 400):
return []
def _l10n_it_edi_partner_in_eu(self, partner):
europe = self.env.ref('base.europe', raise_if_not_found=False)
country = partner.country_id
return not europe or not country or country in europe.country_ids
return errors
def _l10n_it_edi_services_or_goods(self, invoice):
"""
Services and goods have different tax grids when VAT is Reverse Charged, and they can't
be mixed in the same invoice, because the TipoDocumento depends on which which kind
of product is bought and it's unambiguous.
"""
scopes = []
for line in invoice.invoice_line_ids.filtered(lambda l: not l.display_type):
tax_ids_with_tax_scope = line.tax_ids.filtered(lambda x: x.tax_scope)
if tax_ids_with_tax_scope:
scopes += tax_ids_with_tax_scope.mapped('tax_scope')
else:
scopes.append(line.product_id and line.product_id.type or 'consu')
if set(scopes) == set(['consu', 'service']):
return "both"
return scopes and scopes.pop()
def _l10n_it_edi_check_buyer_invoice_configuration(self, invoice):
errors = []
@@ -138,32 +190,60 @@ class AccountEdiFormat(models.Model):
# <1.4.2>
if not buyer.street and not buyer.street2:
errors.append(_("%s must have a street.", buyer.display_name))
if not buyer.country_id:
errors.append(_("%s must have a country.", buyer.display_name))
if not buyer.zip:
errors.append(_("%s must have a post code.", buyer.display_name))
elif len(buyer.zip) != 5 and buyer.country_id.code == 'IT':
errors.append(_("%s must have a post code of length 5.", buyer.display_name))
if not buyer.city:
errors.append(_("%s must have a city.", buyer.display_name))
if not buyer.country_id:
errors.append(_("%s must have a country.", buyer.display_name))
return errors
def _l10n_it_get_document_type(self, invoice):
errors_buyer = self._l10n_it_edi_check_buyer_invoice_configuration(invoice)
if errors_buyer: # Simplified invoice case
if invoice.move_type == 'out_invoice':
return 'TD07'
elif invoice.move_type == 'out_refund':
return 'TD08'
def _l10n_it_goods_in_italy(self, invoice):
"""
There is a specific TipoDocumento (Document Type TD19) and tax grid (VJ3) for goods
that are phisically in Italy but are in a VAT deposit, meaning that the goods
have not passed customs.
"""
report_lines_xmlids = invoice.line_ids.tax_tag_ids.tax_report_line_ids.mapped(lambda x: x.get_external_id().get(x.id, ''))
return any([x == "l10n_it.tax_report_line_vj3" for x in report_lines_xmlids])
if invoice.move_type == 'out_invoice':
return 'TD01'
elif invoice.move_type == 'out_refund':
return 'TD04'
def _l10n_it_document_type_mapping(self):
return {
'TD01': dict(move_type='out_invoice', import_type='in_invoice'),
'TD04': dict(move_type='out_refund', import_type='in_refund'),
'TD07': dict(move_type='out_invoice', import_type='in_invoice', simplified=True),
'TD08': dict(move_type='out_refund', import_type='in_refund', simplified=True),
'TD09': dict(move_type='out_invoice', import_type='in_invoice', simplified=True),
'TD17': dict(move_type='in_invoice', import_type='out_invoice', self_invoice=True, services_or_goods="service"),
'TD18': dict(move_type='in_invoice', import_type='out_invoice', self_invoice=True, services_or_goods="consu", partner_in_eu=True),
'TD19': dict(move_type='in_invoice', import_type='out_invoice', self_invoice=True, services_or_goods="consu", goods_in_italy=True),
}
def _l10n_it_get_document_type(self, invoice):
is_simplified = self._l10n_it_edi_is_simplified(invoice)
is_self_invoice = self._l10n_it_edi_is_self_invoice(invoice)
services_or_goods = self._l10n_it_edi_services_or_goods(invoice)
goods_in_italy = services_or_goods == 'consu' and self._l10n_it_goods_in_italy(invoice)
partner_in_eu = self._l10n_it_edi_partner_in_eu(invoice.commercial_partner_id)
for code, infos in self._l10n_it_document_type_mapping().items():
info_services_or_goods = infos.get('services_or_goods', "both")
info_partner_in_eu = infos.get('partner_in_eu', False)
if all([
invoice.move_type == infos.get('move_type', False),
is_self_invoice == infos.get('self_invoice', False),
is_simplified == infos.get('simplified', False),
info_services_or_goods in ("both", services_or_goods),
info_partner_in_eu in (False, partner_in_eu),
goods_in_italy == infos.get('goods_in_italy', False),
]):
return code
return None
def _l10n_it_is_simplified_document_type(self, document_type):
return document_type in ['TD07', 'TD08', 'TD09']
return self._l10n_it_document_type_mapping().get(document_type, {}).get('simplified', False)
# -------------------------------------------------------------------------
# Export
@@ -179,14 +259,16 @@ class AccountEdiFormat(models.Model):
self.ensure_one()
if self.code != 'fattura_pa':
return super()._is_compatible_with_journal(journal)
return journal.type == 'sale' and journal.country_code == 'IT'
return journal.type in ('sale', 'purchase') and journal.country_code == 'IT'
def _l10n_it_edi_is_required_for_invoice(self, invoice):
""" Is the edi required for this invoice based on the method (here: PEC mail)
Deprecated: in future release PEC mail will be removed.
TO OVERRIDE
"""
return invoice.is_sale_document() and invoice.l10n_it_send_state not in ('sent', 'delivered', 'delivered_accepted') and invoice.country_code == 'IT'
return ((invoice.is_sale_document() or self._l10n_it_get_document_type(invoice))
and invoice.country_code == 'IT'
and invoice.l10n_it_send_state not in ('sent', 'delivered', 'delivered_accepted'))
def _is_required_for_invoice(self, invoice):
# OVERRIDE
@@ -325,12 +407,11 @@ class AccountEdiFormat(models.Model):
# TD09 == simplified debit note
# For unsupported document types, just assume in_invoice, and log that the type is unsupported
elements = tree.xpath('//DatiGeneraliDocumento/TipoDocumento')
move_type = 'in_invoice'
document_type = elements[0].text if elements else ''
if document_type and document_type in ['TD04', 'TD08']:
move_type = 'in_refund'
elif document_type and document_type not in ['TD01', 'TD07']:
_logger.info('Document type not managed: %s. Invoice type is set by default.', elements[0].text)
move_type = self._l10n_it_document_type_mapping().get(document_type, {}).get('import_type', False)
if not move_type:
move_type = "in_invoice"
_logger.info('Document type not managed: %s. Invoice type is set by default.', document_type)
simplified = self._l10n_it_is_simplified_document_type(document_type)
+35 -13
View File
@@ -135,18 +135,13 @@ class AccountMove(models.Model):
return 'IT'
return vat[:2].upper()
def in_eu(partner):
europe = self.env.ref('base.europe', raise_if_not_found=False)
country = partner.country_id
if not europe or not country or country in europe.country_ids:
return True
return False
def format_alphanumeric(text_to_convert):
return text_to_convert.encode('latin-1', 'replace').decode('latin-1') if text_to_convert else False
formato_trasmissione = "FPA12" if self._is_commercial_partner_pa() else "FPR12"
in_eu = self.env['account.edi.format']._l10n_it_edi_partner_in_eu
is_self_invoice = self.env['account.edi.format']._l10n_it_edi_is_self_invoice(self)
document_type = self.env['account.edi.format']._l10n_it_get_document_type(self)
if self.env['account.edi.format']._l10n_it_is_simplified_document_type(document_type):
formato_trasmissione = "FSM10"
@@ -165,7 +160,9 @@ class AccountMove(models.Model):
# Constraints within the edi make local rounding on price included taxes a problem.
# To solve this there is a <Arrotondamento> or 'rounding' field, such that:
# taxable base = sum(taxable base for each unit) + Arrotondamento
tax_details = self._prepare_edi_tax_details()
tax_details = self._prepare_edi_tax_details(
filter_to_apply=lambda l: l['tax_repartition_line_id'].factor_percent > 0
)
for _tax_name, tax_dict in tax_details['tax_details'].items():
base_amount = tax_dict['base_amount_currency']
tax_amount = tax_dict['tax_amount_currency']
@@ -176,27 +173,52 @@ class AccountMove(models.Model):
tax_dict['rounding'] = base_amount - (tax_amount * 100 / tax_rate)
tax_dict['base_amount_currency'] = base_amount - tax_dict['rounding']
company = self.company_id
partner = self.commercial_partner_id
buyer = partner if not is_self_invoice else company
seller = company if not is_self_invoice else partner
codice_destinatario = (
(is_self_invoice and company.partner_id.l10n_it_pa_index)
or partner.l10n_it_pa_index
or (partner.country_id.code == 'IT' and '0000000')
or 'XXXXXXX')
# Create file content.
template_values = {
'record': self,
'company': company,
'sender': company,
'sender_partner': company.partner_id,
'partner': partner,
'buyer': buyer,
'buyer_partner': partner if not is_self_invoice else company.partner_id,
'buyer_is_company': is_self_invoice or partner.is_company,
'seller': seller,
'seller_partner': company.partner_id if not is_self_invoice else partner,
'currency': self.currency_id or self.company_currency_id,
'representative': company.l10n_it_tax_representative_partner_id,
'codice_destinatario': codice_destinatario,
'regime_fiscale': company.l10n_it_tax_system if not is_self_invoice else 'RF01',
'is_self_invoice': is_self_invoice,
'partner_bank': self.partner_bank_id,
'format_date': format_date,
'format_monetary': format_monetary,
'format_numbers': format_numbers,
'format_numbers_two': format_numbers_two,
'format_phone': format_phone,
'format_alphanumeric': format_alphanumeric,
'normalize_codice_fiscale': self.env['res.partner']._l10n_it_normalize_codice_fiscale,
'discount_type': discount_type,
'get_vat_number': get_vat_number,
'get_vat_country': get_vat_country,
'in_eu': in_eu,
'abs': abs,
'formato_trasmissione': formato_trasmissione,
'document_type': document_type,
'pdf': pdf,
'pdf_name': pdf_name,
'tax_map': tax_map,
'tax_details': tax_details,
'abs': abs,
'normalize_codice_fiscale': partner._l10n_it_normalize_codice_fiscale,
'get_vat_number': get_vat_number,
'get_vat_country': get_vat_country,
'in_eu': in_eu
}
return template_values
@@ -110,7 +110,12 @@ class AccountEdiFormat(models.Model):
""" _is_required_for_invoice for SdiCoop.
OVERRIDE
"""
return invoice.is_sale_document() and invoice.country_code == 'IT'
is_self_invoice = self._l10n_it_edi_is_self_invoice(invoice)
return (
(invoice.is_sale_document() or (is_self_invoice and invoice.is_purchase_document()))
and invoice.l10n_it_send_state not in ('sent', 'delivered', 'delivered_accepted')
and invoice.country_code == 'IT'
)
def _support_batching(self, move=None, state=None, company=None):
# OVERRIDE
@@ -2,3 +2,4 @@
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import test_edi_xml
from . import test_edi_reverse_charge_xml
@@ -0,0 +1,104 @@
<p:FatturaElettronica xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2 http://www.fatturapa.gov.it/export/fatturazione/sdi/fatturapa/v1.2/Schema_del_file_xml_FatturaPA_versione_1.2.xsd" versione="FPR12">
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese>IT</IdPaese>
<IdCodice>01234560157</IdCodice>
</IdTrasmittente>
<ProgressivoInvio>2022030001</ProgressivoInvio>
<FormatoTrasmissione>FPR12</FormatoTrasmissione>
<CodiceDestinatario>0803HR0</CodiceDestinatario>
<ContattiTrasmittente>
</ContattiTrasmittente>
</DatiTrasmissione>
<CedentePrestatore>
<DatiAnagrafici>
<IdFiscaleIVA>
<IdPaese>FR</IdPaese>
<IdCodice>15437982937</IdCodice>
</IdFiscaleIVA>
<Anagrafica>
<Denominazione>Alessi</Denominazione>
</Anagrafica>
<RegimeFiscale>RF01</RegimeFiscale>
</DatiAnagrafici>
<Sede>
<Indirizzo>Avenue Test rue </Indirizzo>
<CAP>00000</CAP>
<Comune>Avignon</Comune>
<Nazione>FR</Nazione>
</Sede>
</CedentePrestatore>
<CessionarioCommittente>
<DatiAnagrafici>
<IdFiscaleIVA>
<IdPaese>IT</IdPaese>
<IdCodice>01234560157</IdCodice>
</IdFiscaleIVA>
<Anagrafica>
<Denominazione>company_2_data</Denominazione>
</Anagrafica>
</DatiAnagrafici>
<Sede>
<Indirizzo>1234 Test Street </Indirizzo>
<CAP>12345</CAP>
<Comune>Prova</Comune>
<Nazione>IT</Nazione>
</Sede>
</CessionarioCommittente>
</FatturaElettronicaHeader>
<FatturaElettronicaBody>
<DatiGenerali>
<DatiGeneraliDocumento>
<TipoDocumento>TD18</TipoDocumento>
<Divisa>EUR</Divisa>
<Data>2022-03-24</Data>
<Numero>BILL/2022/03/0001</Numero>
<ImportoTotaleDocumento>1600.80</ImportoTotaleDocumento>
</DatiGeneraliDocumento>
</DatiGenerali>
<DatiBeniServizi>
<DettaglioLinee>
<NumeroLinea>1</NumeroLinea>
<Descrizione>
Product A
</Descrizione>
<Quantita>1.00</Quantita>
<PrezzoUnitario>800.400000</PrezzoUnitario>
<PrezzoTotale>800.40</PrezzoTotale>
<AliquotaIVA>22.00</AliquotaIVA>
</DettaglioLinee>
<DettaglioLinee>
<NumeroLinea>2</NumeroLinea>
<Descrizione>
Product B, taxed 4%
</Descrizione>
<Quantita>1.00</Quantita>
<PrezzoUnitario>800.400000</PrezzoUnitario>
<PrezzoTotale>800.40</PrezzoTotale>
<AliquotaIVA>4.00</AliquotaIVA>
</DettaglioLinee>
<DatiRiepilogo>
<AliquotaIVA>22.00</AliquotaIVA>
<ImponibileImporto>800.40</ImponibileImporto>
<Imposta>176.09</Imposta>
<EsigibilitaIVA>I</EsigibilitaIVA>
</DatiRiepilogo>
<DatiRiepilogo>
<AliquotaIVA>4.00</AliquotaIVA>
<ImponibileImporto>800.40</ImponibileImporto>
<Imposta>32.02</Imposta>
<EsigibilitaIVA>I</EsigibilitaIVA>
</DatiRiepilogo>
</DatiBeniServizi>
<DatiPagamento>
<CondizioniPagamento>TP02</CondizioniPagamento>
<DettaglioPagamento>
<ModalitaPagamento>MP05</ModalitaPagamento>
<DataScadenzaPagamento>2022-03-24</DataScadenzaPagamento>
<ImportoPagamento>1600.80</ImportoPagamento>
</DettaglioPagamento>
</DatiPagamento>
<Allegati></Allegati>
</FatturaElettronicaBody>
</p:FatturaElettronica>
@@ -0,0 +1,100 @@
<p:FatturaElettronica xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2 http://www.fatturapa.gov.it/export/fatturazione/sdi/fatturapa/v1.2/Schema_del_file_xml_FatturaPA_versione_1.2.xsd" versione="FPR12">
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese>IT</IdPaese>
<IdCodice>01234560157</IdCodice>
</IdTrasmittente>
<ProgressivoInvio>2022030008</ProgressivoInvio>
<FormatoTrasmissione>FPR12</FormatoTrasmissione>
<CodiceDestinatario>XXXXXXX</CodiceDestinatario>
<ContattiTrasmittente>
</ContattiTrasmittente>
</DatiTrasmissione>
<CedentePrestatore>
<DatiAnagrafici>
<IdFiscaleIVA>
<IdPaese>IT</IdPaese>
<IdCodice>01234560157</IdCodice>
</IdFiscaleIVA>
<CodiceFiscale>01234560157</CodiceFiscale>
<Anagrafica>
<Denominazione>company_2_data</Denominazione>
</Anagrafica>
<RegimeFiscale>RF01</RegimeFiscale>
</DatiAnagrafici>
<Sede>
<Indirizzo>1234 Test Street </Indirizzo>
<CAP>12345</CAP>
<Comune>Prova</Comune>
<Nazione>IT</Nazione>
</Sede>
</CedentePrestatore>
<CessionarioCommittente>
<DatiAnagrafici>
<IdFiscaleIVA>
<IdPaese>FR</IdPaese>
<IdCodice>15437982937</IdCodice>
</IdFiscaleIVA>
<Anagrafica>
<Denominazione>Alessi</Denominazione>
</Anagrafica>
</DatiAnagrafici>
<Sede>
<Indirizzo>Avenue Test rue </Indirizzo>
<CAP>00000</CAP>
<Comune>Avignon</Comune>
<Nazione>FR</Nazione>
</Sede>
</CessionarioCommittente>
</FatturaElettronicaHeader>
<FatturaElettronicaBody>
<DatiGenerali>
<DatiGeneraliDocumento>
<TipoDocumento>TD01</TipoDocumento>
<Divisa>EUR</Divisa>
<Data>2022-03-24</Data>
<Numero>INV/2022/03/0008</Numero>
<ImportoTotaleDocumento>1600.80</ImportoTotaleDocumento>
</DatiGeneraliDocumento>
</DatiGenerali>
<DatiBeniServizi>
<DettaglioLinee>
<NumeroLinea>1</NumeroLinea>
<Descrizione>
Product A
</Descrizione>
<Quantita>1.00</Quantita>
<PrezzoUnitario>800.400000</PrezzoUnitario>
<PrezzoTotale>800.40</PrezzoTotale>
<AliquotaIVA>0.00</AliquotaIVA>
</DettaglioLinee>
<DettaglioLinee>
<NumeroLinea>2</NumeroLinea>
<Descrizione>
Product B
</Descrizione>
<Quantita>1.00</Quantita>
<PrezzoUnitario>800.400000</PrezzoUnitario>
<PrezzoTotale>800.40</PrezzoTotale>
<AliquotaIVA>0.00</AliquotaIVA>
</DettaglioLinee>
<DatiRiepilogo>
<AliquotaIVA>0.00</AliquotaIVA>
<ImponibileImporto>1600.80</ImponibileImporto>
<Imposta>0.00</Imposta>
<EsigibilitaIVA>I</EsigibilitaIVA>
</DatiRiepilogo>
</DatiBeniServizi>
<DatiPagamento>
<CondizioniPagamento>TP02</CondizioniPagamento>
<DettaglioPagamento>
<ModalitaPagamento>MP05</ModalitaPagamento>
<DataScadenzaPagamento>2022-03-24</DataScadenzaPagamento>
<ImportoPagamento>1600.80</ImportoPagamento>
<CodicePagamento>INV/2022/03/0008</CodicePagamento>
</DettaglioPagamento>
</DatiPagamento>
<Allegati></Allegati>
</FatturaElettronicaBody>
</p:FatturaElettronica>
@@ -0,0 +1,195 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from collections import namedtuple
from lxml import etree
from flectra import fields
from flectra.tests import tagged
from flectra.addons.l10n_it_edi_sdicoop.tests.test_edi_xml 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
})
# 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',)),
RepartitionLine(100, 'tax', ('+vj9',)),
RepartitionLine(-100, 'tax', False)),
'refund_repartition_line_ids': repartition_lines(
RepartitionLine(100, 'base', False),
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',)),
RepartitionLine(100, 'tax', ('+vj3',)),
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'),
'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'),
'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)
# Posting moves -----------
cls.reverse_charge_invoice._post()
cls.reverse_charge_bill._post()
cls.reverse_charge_bill_2._post()
def _cleanup_etree(self, content, xpaths=None):
xpaths = {
**(xpaths or {}),
'//FatturaElettronicaBody/Allegati': 'Allegati',
'//DatiTrasmissione/ProgressivoInvio': 'ProgressivoInvio',
}
return self.with_applied_xpath(
etree.fromstring(content),
"".join([f"<xpath expr='{x}' position='replace'>{y}</xpath>" for x, y in xpaths.items()])
)
def _test_invoice_with_sample_file(self, invoice, filename, xpaths_file=None, xpaths_result=None):
result = self._cleanup_etree(invoice._export_as_xml(), xpaths_result)
expected = self._cleanup_etree(self._get_test_file_content(filename), xpaths_file)
self.assertXmlTreeEqual(expected, result)
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/>",
}
)
@@ -174,8 +174,9 @@ class Activity extends Component {
* @private
* @param {MouseEvent} ev
*/
_onClickEdit(ev) {
this.activity.edit();
async _onClickEdit(ev) {
await this.activity.edit();
this.trigger('reload', { keepChanges: true });
}
/**
@@ -13,7 +13,7 @@
t-on-keydown="_onKeydown"
>
<t t-if="composer">
<t t-if="isDropZoneVisible.value">
<t t-if="isDropZoneVisible.value and composer.hasDropZone">
<DropZone
class="o_Composer_dropZone"
t-on-o-dropzone-files-dropped="_onDropZoneFilesDropped"
@@ -133,6 +133,8 @@ function factory(dependencies) {
/**
* Opens (legacy) form view dialog to edit current activity and updates
* the activity when dialog is closed.
*
* @return {Promise} promise that is fulfilled when the form has been closed
*/
edit() {
const action = {
@@ -148,9 +150,16 @@ function factory(dependencies) {
},
res_id: this.id,
};
this.env.bus.trigger('do-action', {
action,
options: { on_close: () => this.fetchAndUpdate() },
return new Promise(resolve => {
this.env.bus.trigger('do-action', {
action,
options: {
on_close: () => {
resolve();
this.fetchAndUpdate();
},
},
});
});
}
@@ -536,6 +536,14 @@ function factory(dependencies) {
return clear();
}
/**
* @private
* @return {boolean}
*/
_computeHasDropZone() {
return true;
}
/**
* @private
* @return {boolean}
@@ -1169,6 +1177,13 @@ function factory(dependencies) {
'suggestionDelimiter',
],
}),
hasDropZone: attr({
compute: '_computeHasDropZone',
dependencies: [
'thread',
'threadChannelType',
],
}),
/**
* This field determines whether some attachments linked to this
* composer are being uploaded.
@@ -1423,6 +1438,9 @@ function factory(dependencies) {
thread: one2one('mail.thread', {
inverse: 'composer',
}),
threadChannelType: attr({
related: 'thread.channel_type',
}),
};
Composer.modelName = 'mail.composer';
+1 -1
View File
@@ -48,7 +48,7 @@ class StockPickingType(models.Model):
remaining.count_mo_late = False
def get_mrp_stock_picking_action_picking_type(self):
action = self.env.ref('mrp.mrp_production_action_picking_deshboard').read()[0]
action = self.env["ir.actions.actions"]._for_xml_id('mrp.mrp_production_action_picking_deshboard')
if self:
action['display_name'] = self.display_name
return action
+2 -1
View File
@@ -891,7 +891,8 @@ class PaymentTransaction(models.Model):
if not self:
ten_minutes_ago = datetime.now() - relativedelta.relativedelta(minutes=10)
# we don't want to forever try to process a transaction that doesn't go through
retry_limit_date = datetime.now() - relativedelta.relativedelta(days=2)
# as for Paypal, it sometime takes 3 or 4 days for payment verification due to weekend. Set 4 here should be fine.
retry_limit_date = datetime.now() - relativedelta.relativedelta(days=4)
# we retrieve all the payment tx that need to be post processed
self = self.search([('state', '=', 'done'),
('is_processed', '=', False),
@@ -48,7 +48,7 @@
<xpath expr="//filter[@name='reserved']" position="after">
<separator/>
<filter string="Expiration Alerts" name="expiration_alerts"
domain="[('removal_date', '&lt;=', context_today().strftime('%Y-%m-%d'))]"/>
domain="[('lot_id.alert_date', '&lt;=', context_today().strftime('%Y-%m-%d'))]"/>
</xpath>
</field>
</record>
+13 -3
View File
@@ -623,6 +623,16 @@ class PurchaseOrder(models.Model):
}
one_week_ago = fields.Datetime.to_string(fields.Datetime.now() - relativedelta(days=7))
# Get list of translation values
Translation = self.env['ir.translation']
list_old_value_char = []
list_new_value_char = []
field_name = 'ir.model.fields.selection,name'
for lang in self.env['res.lang'].search_read([], ['code']):
list_old_value_char.append(Translation._get_source(field_name, 'model', lang['code'], source='RFQ'))
list_new_value_char.append(Translation._get_source(field_name, 'model', lang['code'], source='RFQ Sent'))
# This query is brittle since it depends on the label values of a selection field
# not changing, but we don't have a direct time tracker of when a state changes
query = """SELECT COUNT(1)
@@ -632,12 +642,12 @@ class PurchaseOrder(models.Model):
WHERE m.create_date >= %s
AND m.model = 'purchase.order'
AND m.message_type = 'notification'
AND v.old_value_char = 'RFQ'
AND v.new_value_char = 'RFQ Sent'
AND v.old_value_char IN %s
AND v.new_value_char IN %s
AND po.company_id = %s;
"""
self.env.cr.execute(query, (one_week_ago, self.env.company.id))
self.env.cr.execute(query, (one_week_ago, tuple(list_old_value_char), tuple(list_new_value_char), self.env.company.id))
res = self.env.cr.fetchone()
result['all_sent_rfqs'] = res[0] or 0
@@ -99,6 +99,8 @@
.o_kanban_record_title {
@include o-kanban-record-title($font-size: 13px);
overflow-wrap: break-word;
word-wrap: break-word;
}
.o_kanban_record_subtitle {
@@ -377,6 +379,8 @@
.oe_kanban_details {
width: 100%;
overflow-wrap: break-word;
word-wrap: break-word;
// Useful for the class 'o_text_overflow'
min-width: 0;
@@ -961,7 +961,7 @@ var ImageWidget = FileWidget.extend({
*/
_clear: function (type) {
// Not calling _super: we don't want to call the document widget's _clear method on images
var allImgClasses = /(^|\s+)(img|img-\S*|o_we_custom_image|rounded-circle|rounded|thumbnail|shadow)(?=\s|$)/g;
var allImgClasses = /(^|\s+)(img|img-\S*|o_we_custom_image|rounded-circle|rounded|thumbnail|shadow|w-25|w-50|w-75|w-100|o_modified_image_to_save)(?=\s|$)/g;
this.media.className = this.media.className && this.media.className.replace(allImgClasses, ' ');
},
});
@@ -24,7 +24,7 @@ options.registry.SnippetPopup = options.Class.extend({
iframe.src = media.dataset.oeExpression || media.dataset.src; // TODO still oeExpression to remove someday
});
});
this.$target.on('hidden.bs.modal.SnippetPopup', () => {
this.$target.on('hide.bs.modal.SnippetPopup', () => {
this.trigger_up('snippet_option_visibility_update', {show: false});
this._removeIframeSrc();
});