mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2026-10-25 23:40:05 -05:00
Merge branch 'master-upstream-patch-06072022' into 'master'
[PATCH] Upstream patch - 06072022 See merge request flectra-hq/flectra!735
This commit is contained in:
commit
73ec8575c6
38 files changed
+2404
-543
No files matched your search
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'])
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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."
|
||||
|
@@ -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
@@ -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>
|
||||
@@ -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)
|
||||
@@ -25,5 +25,6 @@ E-invoice implementation
|
||||
'demo': [
|
||||
'data/account_invoice_demo.xml',
|
||||
],
|
||||
'post_init_hook': '_l10n_it_edi_post_init',
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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', '<=', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
domain="[('lot_id.alert_date', '<=', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user