diff --git a/sale_automatic_workflow/README.rst b/sale_automatic_workflow/README.rst index 0655875884f..1f48a138503 100644 --- a/sale_automatic_workflow/README.rst +++ b/sale_automatic_workflow/README.rst @@ -7,7 +7,7 @@ Sale Automatic Workflow !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:2e02c7c445382c52a5a523e511d78a5d40cecafe955493cbe280b78597066a5a + !! source digest: sha256:6c461bb0f25bae8120a4bba197db362e406de92f6fa19a1a3f8609971609b1f8 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/sale_automatic_workflow/data/automatic_workflow_data.xml b/sale_automatic_workflow/data/automatic_workflow_data.xml index ece7f7aff48..859ec59937d 100644 --- a/sale_automatic_workflow/data/automatic_workflow_data.xml +++ b/sale_automatic_workflow/data/automatic_workflow_data.xml @@ -26,6 +26,14 @@ >[('state', '=', 'draft'), ('posted_before', '=', False)] + + Automatic Workflow Send Invoice Filter + account.move + [('state', '=', 'posted'), ('is_move_sent', '=', False), ('move_type', '=', 'out_invoice')] + + Automatic Workflow Sale Done Filter sale.order @@ -57,6 +65,11 @@ name="validate_invoice_filter_id" eval="automatic_workflow_validate_invoice_filter" /> + + diff --git a/sale_automatic_workflow/models/automatic_workflow_job.py b/sale_automatic_workflow/models/automatic_workflow_job.py index e1e30011fda..9e72922810c 100644 --- a/sale_automatic_workflow/models/automatic_workflow_job.py +++ b/sale_automatic_workflow/models/automatic_workflow_job.py @@ -112,14 +112,43 @@ def _validate_invoices(self, validate_invoice_filter): invoice.with_company(invoice.company_id), validate_invoice_filter ) + def _do_send_invoice(self, invoice, domain_filter): + """Validate an invoice, filter ensure no duplication""" + if not self.env["account.move"].search_count( + [("id", "=", invoice.id)] + domain_filter + ): + return f"{invoice.display_name} {invoice} job bypassed" + + move_template = self.env.ref("account.email_template_edi_invoice") + invoice_send_wizard = ( + self.env["account.move.send"] + .with_context(active_model="account.move", active_ids=invoice.ids) + .create({"checkbox_download": False, "mail_template_id": move_template.id}) + ) + + invoice_send_wizard.action_send_and_print(force_synchronous=True) + + return f"{invoice.display_name} {invoice} sent invoice successfully" + + @api.model + def _send_invoices(self, send_invoice_filter): + move_obj = self.env["account.move"] + invoices = move_obj.search(send_invoice_filter) + _logger.debug("Invoices to send: %s", invoices.ids) + for invoice in invoices: + with savepoint(self.env.cr): + self._do_send_invoice( + invoice.with_company(invoice.company_id), send_invoice_filter + ) + def _do_sale_done(self, sale, domain_filter): - """Lock a sales order, filter ensure no duplication""" + """Set a sales order to done, filter ensure no duplication""" if not self.env["sale.order"].search_count( [("id", "=", sale.id)] + domain_filter ): return f"{sale.display_name} {sale} job bypassed" - sale.action_lock() - return f"{sale.display_name} {sale} locked successfully" + sale.action_done() + return f"{sale.display_name} {sale} set done successfully" @api.model def _sale_done(self, sale_done_filter): @@ -194,6 +223,10 @@ def run_with_workflow(self, sale_workflow): safe_eval(sale_workflow.validate_invoice_filter_id.domain) + workflow_domain ) + if sale_workflow.send_invoice: + self._send_invoices( + safe_eval(sale_workflow.send_invoice_filter_id.domain) + workflow_domain + ) if sale_workflow.sale_done: self._sale_done( safe_eval(sale_workflow.sale_done_filter_id.domain) + workflow_domain diff --git a/sale_automatic_workflow/models/sale_workflow_process.py b/sale_automatic_workflow/models/sale_workflow_process.py index 622b992c5d0..a1bc575f7fe 100644 --- a/sale_automatic_workflow/models/sale_workflow_process.py +++ b/sale_automatic_workflow/models/sale_workflow_process.py @@ -45,6 +45,15 @@ def _default_filter(self, xmlid): string="Validate Invoice Filter Domain", related="validate_invoice_filter_id.domain", ) + send_invoice = fields.Boolean() + send_invoice_filter_domain = fields.Text( + string="Send Invoice Filter Domain", + related="send_invoice_filter_id.domain", + ) + validate_picking = fields.Boolean(string="Confirm and Transfer Picking") + picking_filter_domain = fields.Text( + string="Picking Filter Domain", related="picking_filter_id.domain" + ) invoice_date_is_order_date = fields.Boolean( string="Force Invoice Date", help="When checked, the invoice date will be " "the same than the order's date", @@ -79,6 +88,13 @@ def _default_filter(self, xmlid): "sale_automatic_workflow.automatic_workflow_order_filter" ), ) + picking_filter_id = fields.Many2one( + "ir.filters", + string="Picking Filter", + default=lambda self: self._default_filter( + "sale_automatic_workflow.automatic_workflow_picking_filter" + ), + ) create_invoice_filter_id = fields.Many2one( "ir.filters", string="Create Invoice Filter", @@ -93,6 +109,13 @@ def _default_filter(self, xmlid): "sale_automatic_workflow." "automatic_workflow_validate_invoice_filter" ), ) + send_invoice_filter_id = fields.Many2one( + "ir.filters", + string="Send Invoice Filter", + default=lambda self: self._default_filter( + "sale_automatic_workflow." "automatic_workflow_send_invoice_filter" + ), + ) sale_done_filter_id = fields.Many2one( "ir.filters", string="Sale Done Filter", diff --git a/sale_automatic_workflow/static/description/index.html b/sale_automatic_workflow/static/description/index.html index 546a3cd99ec..57a49f8ce0b 100644 --- a/sale_automatic_workflow/static/description/index.html +++ b/sale_automatic_workflow/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -366,7 +367,7 @@

Sale Automatic Workflow

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:2e02c7c445382c52a5a523e511d78a5d40cecafe955493cbe280b78597066a5a +!! source digest: sha256:6c461bb0f25bae8120a4bba197db362e406de92f6fa19a1a3f8609971609b1f8 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/sale-workflow Translate me on Weblate Try me on Runboat

Create workflows with more or less automatization and apply it on sales @@ -450,7 +451,9 @@

Other credits

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

diff --git a/sale_automatic_workflow/tests/common.py b/sale_automatic_workflow/tests/common.py index 1b4e360d38e..49ad2c8f298 100644 --- a/sale_automatic_workflow/tests/common.py +++ b/sale_automatic_workflow/tests/common.py @@ -13,13 +13,28 @@ class TestCommon(TransactionCase): def setUpClass(cls): super().setUpClass() cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.user = cls.env["res.users"].create( + { + "name": "Sales Person", + "login": "salesperson", + "password": "salesperson", + "groups_id": [ + (4, cls.env.ref("sales_team.group_sale_manager").id), + (4, cls.env.ref("account.group_account_manager").id), + ], + } + ) + cls.user.partner_id.email = "salesperson@example.com" class TestAutomaticWorkflowMixin: def create_sale_order(self, workflow, override=None, product_type="consu"): sale_obj = self.env["sale.order"] - partner_values = {"name": "Imperator Caius Julius Caesar Divus"} + partner_values = { + "name": "Imperator Caius Julius Caesar Divus", + "email": "test@example.com", + } partner = self.env["res.partner"].create(partner_values) product_values = {"name": "Bread", "list_price": 5, "type": product_type} @@ -54,6 +69,7 @@ def create_full_automatic(self, override=None): "validate_order": True, "create_invoice": True, "validate_invoice": True, + "send_invoice": True, "invoice_date_is_order_date": True, } ) diff --git a/sale_automatic_workflow/tests/test_automatic_workflow.py b/sale_automatic_workflow/tests/test_automatic_workflow.py index 8191fc4adcf..f395ceaab30 100644 --- a/sale_automatic_workflow/tests/test_automatic_workflow.py +++ b/sale_automatic_workflow/tests/test_automatic_workflow.py @@ -1,16 +1,20 @@ # Copyright 2014 Camptocamp SA (author: Guewen Baconnier) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging from datetime import timedelta from unittest import mock from odoo import fields from odoo.tests import tagged +from odoo.tools.safe_eval import safe_eval from .common import TestAutomaticWorkflowMixin, TestCommon +_logger = logging.getLogger(__name__) -@tagged("post_install", "-at_install") + +@tagged("post_install", "-at_install", "mail_composer") class TestAutomaticWorkflow(TestCommon, TestAutomaticWorkflowMixin): def setUp(self): super().setUp() @@ -152,3 +156,67 @@ def test_automatic_sale_order_confirmation_mail(self): lambda x: x.subtype_id == self.env.ref("mail.mt_comment") ) ) + + def test_automatic_invoice_send_mail(self): + workflow = self.create_full_automatic() + workflow.send_invoice = False + sale = self.create_sale_order(workflow) + sale.user_id = self.user.id + sale._onchange_workflow_process_id() + self.run_job() + invoice = sale.invoice_ids + invoice.message_subscribe(partner_ids=[invoice.partner_id.id]) + invoice.company_id.invoice_is_email = True + previous_message_ids = invoice.message_ids + workflow.send_invoice = True + sale._onchange_workflow_process_id() + self.run_job() + + new_messages = self.env["mail.message"].search( + [ + ("id", "in", invoice.message_ids.ids), + ("id", "not in", previous_message_ids.ids), + ] + ) + + self.assertTrue( + new_messages.filtered( + lambda x: x.subtype_id == self.env.ref("mail.mt_comment") + ) + ) + + def test_job_bypassing(self): + workflow = self.create_full_automatic() + workflow_job = self.env["automatic.workflow.job"] + sale = self.create_sale_order(workflow) + sale._onchange_workflow_process_id() + + create_invoice_filter = [ + ("state", "in", ["sale", "done"]), + ("invoice_status", "=", "to invoice"), + ("workflow_process_id", "=", sale.workflow_process_id.id), + ] + order_filter = safe_eval(workflow.order_filter_id.domain) + validate_invoice_filter = safe_eval(workflow.validate_invoice_filter_id.domain) + send_invoice_filter = safe_eval(workflow.send_invoice_filter_id.domain) + + # Trigger everything, then check if sale and invoice jobs are bypassed + self.run_job() + + invoice = sale.invoice_ids + + res_so_validate = workflow_job._do_validate_sale_order(sale, order_filter) + # TODO send confirmation bypassing is not working yet, needs fix + workflow_job._do_send_order_confirmation_mail(sale) + res_create_invoice = workflow_job._do_create_invoice( + sale, create_invoice_filter + ) + res_validate_invoice = workflow_job._do_validate_invoice( + invoice, validate_invoice_filter + ) + res_send_invoice = workflow_job._do_send_invoice(invoice, send_invoice_filter) + + self.assertIn("job bypassed", res_so_validate) + self.assertIn("job bypassed", res_create_invoice) + self.assertIn("job bypassed", res_validate_invoice) + self.assertIn("job bypassed", res_send_invoice) diff --git a/sale_automatic_workflow/views/sale_workflow_process_views.xml b/sale_automatic_workflow/views/sale_workflow_process_views.xml index 50e246d4787..c2638e9ac81 100644 --- a/sale_automatic_workflow/views/sale_workflow_process_views.xml +++ b/sale_automatic_workflow/views/sale_workflow_process_views.xml @@ -160,6 +160,44 @@
+
+
+
+
+
+