diff --git a/l10n_it_sdi_channel/__manifest__.py b/l10n_it_sdi_channel/__manifest__.py
index 44102362438e..c16f07a4cc2b 100644
--- a/l10n_it_sdi_channel/__manifest__.py
+++ b/l10n_it_sdi_channel/__manifest__.py
@@ -25,6 +25,7 @@
"data": [
"security/ir.model.access.csv",
"security/security.xml",
+ "views/account_move_views.xml",
"views/sdi_view.xml",
"views/company_view.xml",
"views/fatturapa_attachment_views.xml",
diff --git a/l10n_it_sdi_channel/models/__init__.py b/l10n_it_sdi_channel/models/__init__.py
index 6523ee902c8c..8147d519b05a 100644
--- a/l10n_it_sdi_channel/models/__init__.py
+++ b/l10n_it_sdi_channel/models/__init__.py
@@ -1,5 +1,6 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+from . import account_move
from . import fatturapa_attachment_out
from . import company
from . import sdi
diff --git a/l10n_it_sdi_channel/models/account_move.py b/l10n_it_sdi_channel/models/account_move.py
new file mode 100644
index 000000000000..d0a4f76e47d8
--- /dev/null
+++ b/l10n_it_sdi_channel/models/account_move.py
@@ -0,0 +1,43 @@
+# Copyright 2022 Simone Rubino - TAKOBI
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo import models
+
+
+class AccountMove(models.Model):
+ _inherit = "account.move"
+
+ def action_open_export_send_sdi(self):
+ """Validate, export and send to SdI the invoices."""
+ # Validate
+ self.action_post()
+
+ # Export
+ export_action = self.env["ir.actions.act_window"]._for_xml_id(
+ "l10n_it_fatturapa_out.action_wizard_export_fatturapa",
+ )
+ export_wizard_model = export_action.get("res_model")
+ export_wizard = (
+ self.env[export_wizard_model]
+ .with_context(
+ active_model=self._name,
+ active_ids=self.ids,
+ )
+ .create([{}])
+ )
+ export_result = export_wizard.exportFatturaPA()
+
+ # Get the exported attachments
+ attachment_model = self.env[export_result.get("res_model")]
+ exported_attachments_domain = export_result.get("domain")
+ if not exported_attachments_domain:
+ exported_attachments_domain = [
+ ("id", "=", export_result.get("res_id")),
+ ]
+ exported_attachments = attachment_model.search(
+ exported_attachments_domain,
+ )
+
+ # Send
+ send_result = exported_attachments.send_to_sdi()
+ return send_result
diff --git a/l10n_it_sdi_channel/models/company.py b/l10n_it_sdi_channel/models/company.py
index dfe0217cc412..f3a684da77db 100644
--- a/l10n_it_sdi_channel/models/company.py
+++ b/l10n_it_sdi_channel/models/company.py
@@ -32,3 +32,9 @@ class AccountConfigSettings(models.TransientModel):
related="company_id.e_invoice_user_id",
readonly=False,
)
+ group_sdi_channel_validate_send = fields.Boolean(
+ string="Validate, export and send invoices",
+ help="Allow users to validate, export and send invoices to SdI "
+ "in one click.",
+ implied_group="l10n_it_sdi_channel.res_groups_validate_send",
+ )
diff --git a/l10n_it_sdi_channel/security/security.xml b/l10n_it_sdi_channel/security/security.xml
index 62bc9515d6a3..b0739be9c24c 100644
--- a/l10n_it_sdi_channel/security/security.xml
+++ b/l10n_it_sdi_channel/security/security.xml
@@ -10,4 +10,7 @@
>['|',('company_id','=',False),('company_id','in',company_ids)]
+
+ Validate, export and send invoices
+
diff --git a/l10n_it_sdi_channel/tests/__init__.py b/l10n_it_sdi_channel/tests/__init__.py
new file mode 100644
index 000000000000..6b95a3395eeb
--- /dev/null
+++ b/l10n_it_sdi_channel/tests/__init__.py
@@ -0,0 +1,3 @@
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from . import test_account_invoice
diff --git a/l10n_it_sdi_channel/tests/test_account_invoice.py b/l10n_it_sdi_channel/tests/test_account_invoice.py
new file mode 100644
index 000000000000..cb9e165b5d3a
--- /dev/null
+++ b/l10n_it_sdi_channel/tests/test_account_invoice.py
@@ -0,0 +1,81 @@
+# Copyright 2022 Simone Rubino - TAKOBI
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo.tests import tagged
+
+from odoo.addons.l10n_it_fatturapa_out.tests.fatturapa_common import FatturaPACommon
+
+
+@tagged("post_install", "-at_install")
+class TestAccountInvoice(FatturaPACommon):
+ def setUp(self):
+ super().setUp()
+ # XXX - a company named "YourCompany" alread exists
+ # we move it out of the way but we should do better here
+ self.env.company.sudo().search([("name", "=", "YourCompany")]).write(
+ {"name": "YourCompany_"}
+ )
+ self.env.company.name = "YourCompany"
+ self.env.company.vat = "IT06363391001"
+ self.env.company.fatturapa_art73 = True
+ self.env.company.partner_id.street = "Via Milano, 1"
+ self.env.company.partner_id.city = "Roma"
+ self.env.company.partner_id.state_id = self.env.ref("base.state_us_2").id
+ self.env.company.partner_id.zip = "00100"
+ self.env.company.partner_id.phone = "06543534343"
+ self.env.company.email = "info@yourcompany.example.com"
+ self.env.company.partner_id.country_id = self.env.ref("base.it").id
+ self.env.company.fatturapa_fiscal_position_id = self.env.ref(
+ "l10n_it_fatturapa.fatturapa_RF01"
+ ).id
+
+ def test_action_open_export_send_sdi(self):
+ """
+ Check that the "Validate, export and send to SdI" button
+ validates the invoice and exports the attachment.
+ """
+ # Arrange: create a draft invoice with no attachment
+ invoice = self._create_invoice()
+ self.assertEqual(invoice.state, "draft")
+ self.assertFalse(invoice.fatturapa_attachment_out_id)
+
+ # Act: open, export and send.
+ # This raises an exception because there is no channel,
+ # we can't create a channel yet
+ # because channel types are defined by depending modules
+ with self.assertRaises(ValueError) as ve:
+ invoice.action_open_export_send_sdi()
+ exc_message = ve.exception.args[0]
+
+ # Assert: we are missing the SdI channel,
+ # but invoice is validated and attachment has been created
+ self.assertIn("Expected singleton", exc_message)
+ self.assertIn("sdi.channel", exc_message)
+ self.assertEqual(invoice.state, "posted")
+ self.assertTrue(invoice.fatturapa_attachment_out_id)
+
+ def test_action_open_export_send_sdi_ui(self):
+ """
+ Check that the "Validate, export and send to SdI" button
+ clicked in the UI (where an exception causes a ROLLBACK)
+ does not validate the invoice or export the attachment.
+ """
+ # Arrange: create a draft invoice with no attachment
+ invoice = self._create_invoice()
+ self.assertEqual(invoice.state, "draft")
+ self.assertFalse(invoice.fatturapa_attachment_out_id)
+
+ # Act: open, export and send.
+ # This raises an exception because there is no channel,
+ # we can't create a channel yet
+ # because channel types are defined by depending modules
+ with self.assertRaises(ValueError) as ve, self.env.cr.savepoint():
+ invoice.action_open_export_send_sdi()
+ exc_message = ve.exception.args[0]
+
+ # Assert: we are missing the SdI channel,
+ # but invoice is still in draft and attachment has not been created
+ self.assertIn("Expected singleton", exc_message)
+ self.assertIn("sdi.channel", exc_message)
+ self.assertEqual(invoice.state, "draft")
+ self.assertFalse(invoice.fatturapa_attachment_out_id)
diff --git a/l10n_it_sdi_channel/views/account_move_views.xml b/l10n_it_sdi_channel/views/account_move_views.xml
new file mode 100644
index 000000000000..f7e48b702da5
--- /dev/null
+++ b/l10n_it_sdi_channel/views/account_move_views.xml
@@ -0,0 +1,36 @@
+
+
+
+
+ Add SdI channel edits to invoice's form view
+ account.move
+
+
+
+
+
+
diff --git a/l10n_it_sdi_channel/views/company_view.xml b/l10n_it_sdi_channel/views/company_view.xml
index 68799078b5ed..75e071298339 100644
--- a/l10n_it_sdi_channel/views/company_view.xml
+++ b/l10n_it_sdi_channel/views/company_view.xml
@@ -43,6 +43,15 @@
+
+
+
+
+
+
+ Allow users to validate, export and send invoices to SdI in one click.
+
+