Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sale_automatic_workflow/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions sale_automatic_workflow/data/automatic_workflow_data.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
>[('state', '=', 'draft'), ('posted_before', '=', False)]</field>
<field name="user_id" ref="base.user_root" />
</record>
<record id="automatic_workflow_send_invoice_filter" model="ir.filters">
<field name="name">Automatic Workflow Send Invoice Filter</field>
<field name="model_id">account.move</field>
<field
name="domain"
>[('state', '=', 'posted'), ('is_move_sent', '=', False), ('move_type', '=', 'out_invoice')]</field>
<field name="user_id" ref="base.user_root" />
</record>
<record id="automatic_workflow_sale_done_filter" model="ir.filters">
<field name="name">Automatic Workflow Sale Done Filter</field>
<field name="model_id">sale.order</field>
Expand Down Expand Up @@ -57,6 +65,11 @@
name="validate_invoice_filter_id"
eval="automatic_workflow_validate_invoice_filter"
/>
<field name="send_invoice" eval="1" />
<field
name="send_invoice_filter_id"
eval="automatic_workflow_send_invoice_filter"
/>
<field name="invoice_date_is_order_date" eval="0" />
<field name="sale_done" eval="0" />
<field name="sale_done_filter_id" eval="automatic_workflow_sale_done_filter" />
Expand Down
39 changes: 36 additions & 3 deletions sale_automatic_workflow/models/automatic_workflow_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,43 @@
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"

Check warning on line 151 in sale_automatic_workflow/models/automatic_workflow_job.py

View check run for this annotation

Codecov / codecov/patch

sale_automatic_workflow/models/automatic_workflow_job.py#L150-L151

Added lines #L150 - L151 were not covered by tests

@api.model
def _sale_done(self, sale_done_filter):
Expand Down Expand Up @@ -194,6 +223,10 @@
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
Expand Down
23 changes: 23 additions & 0 deletions sale_automatic_workflow/models/sale_workflow_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down
13 changes: 8 additions & 5 deletions sale_automatic_workflow/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@

/*
:Author: David Goodger ([email protected])
: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.
Expand Down Expand Up @@ -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 }
Expand All @@ -300,7 +301,7 @@
span.pre {
white-space: pre }

span.problematic {
span.problematic, pre.problematic {
color: red }

span.section-subtitle {
Expand Down Expand Up @@ -366,7 +367,7 @@ <h1 class="title">Sale Automatic Workflow</h1>
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:2e02c7c445382c52a5a523e511d78a5d40cecafe955493cbe280b78597066a5a
!! source digest: sha256:6c461bb0f25bae8120a4bba197db362e406de92f6fa19a1a3f8609971609b1f8
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/sale-workflow/tree/17.0/sale_automatic_workflow"><img alt="OCA/sale-workflow" src="https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/sale-workflow-17-0/sale-workflow-17-0-sale_automatic_workflow"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Create workflows with more or less automatization and apply it on sales
Expand Down Expand Up @@ -450,7 +451,9 @@ <h2><a class="toc-backref" href="#toc-entry-5">Other credits</a></h2>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>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.</p>
Expand Down
18 changes: 17 additions & 1 deletion sale_automatic_workflow/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "[email protected]"


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": "[email protected]",
}
partner = self.env["res.partner"].create(partner_values)

product_values = {"name": "Bread", "list_price": 5, "type": product_type}
Expand Down Expand Up @@ -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,
}
)
Expand Down
70 changes: 69 additions & 1 deletion sale_automatic_workflow/tests/test_automatic_workflow.py
Original file line number Diff line number Diff line change
@@ -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()
Expand Down Expand Up @@ -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)
38 changes: 38 additions & 0 deletions sale_automatic_workflow/views/sale_workflow_process_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,44 @@
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4">
<label
for="send_invoice"
class="col-lg-7 o_light_label"
/>
<field name="send_invoice" nolabel="1" />
</div>
<div class="col-sm-8">
<label
for="send_invoice_filter_id"
required="send_invoice"
invisible="not send_invoice"
/>
<div
required="send_invoice"
invisible="not send_invoice"
>
<field
name="send_invoice_filter_domain"
widget="domain"
invisible="not send_invoice_filter_id"
options="{'model': 'account.move'}"
/>
<div class="oe_edit_only oe_inline">
Set selection based on a search filter:
<field
name="send_invoice_filter_id"
domain="[('model_id', '=', 'account.move')]"
class="oe_inline"
context="{'default_model_id': 'account.move', 'default_active': False, 'active_test': False}"
can_create="true"
can_write="true"
/>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4">
<label
Expand Down
Loading